Repository: encode/django-rest-framework

Issue:
Failing test for #5582
## Description

A failing test for #5582. Works on 3.6.3, doesn't work on 3.6.4 and 3.7.0-3.7.3.

### Potential solution

Adding
```python
self._request._post = self._data
```
to `Request._load_data_and_files` makes the test pass, but I am not 100% sure this is the correct fix.
DRF 3.6.4/3.7.0 breaks reading request.POST if request.body was read
## Checklist

- [x] I have verified that that issue exists against the `master` branch of Django REST framework.
- [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [x] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [x] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](http://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)
- [x] I have reduced the issue to the simplest possible case.
- [x] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Steps to reproduce
- Create any DRF view
- Add a middleware or a signal handler that uses `request.POST`

## Expected behavior
- `request.POST` works fine

## Actual behavior
- `AttributeError` is raised by Django's `HttpRequest._load_post_or_files`

----

The reason the exception is raised is because Django's `WSGIReqeust.POST` checks `if hasattr(self, '_post')` (which it doesn't have) and proceeds to `_load_post_and_data`, which in turn calls `parse_file_upload` which tries to set `self.upload_handlers`, but the `upload_handler.setter` checks `if hasattr(self, '_files')`. At this point, `_files` has already been [set by DRF](https://github.com/encode/django-rest-framework/blob/master/rest_framework/request.py#L255) (to make closing file handles possible), so it raises an exception.

I think the solution to this is to set `request._post` in addition to `request._files` to preserve the interface. I am not sure if there's an easy way to set it to the original `POST` as it seems DRF only calls `_load_data_and_files`, which deserializes the data etc.

----

edited for formatting (@rpkilby)


Discussion:

For context, this was changed in #5262, which superseded #5255. Amusingly enough, the original PR did set `_post` in addition to `_files`. 

@D3X - would you be able to reduce this into a failing test case? 
@rpkilby Thanks for the prompt response. I'll try to submit a PR later tonight.

(Misclicked on the mobile version and closed the ticket by mistake)
@rpkilby I played with it a bit and it seems it only breaks if `request.body` was read before DRF did its magic.

Also have added a failing test PR, as you can see above.
This doesn't look very new to me. Did you had that issue in prior versions ?
@xordoquy No. I bumped into it today, while trying to update my project's dependencies, including updating DRF from 3.6.3 to 3.6.4 (can't go to 3.7 yet). It has worked fine before that.

Resolving patch:
diff --git a/rest_framework/request.py b/rest_framework/request.py
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -250,9 +250,10 @@ def _load_data_and_files(self):
             else:
                 self._full_data = self._data
 
-            # copy files refs to the underlying request so that closable
+            # copy data & files refs to the underlying request so that closable
             # objects are handled appropriately.
-            self._request._files = self._files
+            self._request._post = self.POST
+            self._request._files = self.FILES
 
     def _load_stream(self):
         """


Test patch:
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -1,34 +1,76 @@
 from django.conf.urls import url
 from django.contrib.auth.models import User
+from django.http import HttpRequest
 from django.test import override_settings
 
 from rest_framework.authentication import TokenAuthentication
 from rest_framework.authtoken.models import Token
+from rest_framework.request import is_form_media_type
+from rest_framework.response import Response
 from rest_framework.test import APITestCase
 from rest_framework.views import APIView
 
+
+class PostView(APIView):
+    def post(self, request):
+        return Response(data=request.data, status=200)
+
+
 urlpatterns = [
-    url(r'^$', APIView.as_view(authentication_classes=(TokenAuthentication,))),
+    url(r'^auth$', APIView.as_view(authentication_classes=(TokenAuthentication,))),
+    url(r'^post$', PostView.as_view()),
 ]
 
 
-class MyMiddleware(object):
+class RequestUserMiddleware(object):
+    def __init__(self, get_response):
+        self.get_response = get_response
 
-    def process_response(self, request, response):
+    def __call__(self, request):
+        response = self.get_response(request)
         assert hasattr(request, 'user'), '`user` is not set on request'
-        assert request.user.is_authenticated(), '`user` is not authenticated'
+        assert request.user.is_authenticated, '`user` is not authenticated'
+
+        return response
+
+
+class RequestPOSTMiddleware(object):
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+    def __call__(self, request):
+        assert isinstance(request, HttpRequest)
+
+        # Parse body with underlying Django request
+        request.body
+
+        # Process request with DRF view
+        response = self.get_response(request)
+
+        # Ensure request.POST is set as appropriate
+        if is_form_media_type(request.content_type):
+            assert request.POST == {'foo': ['bar']}
+        else:
+            assert request.POST == {}
+
         return response
 
 
 @override_settings(ROOT_URLCONF='tests.test_middleware')
 class TestMiddleware(APITestCase):
+
+    @override_settings(MIDDLEWARE=('tests.test_middleware.RequestUserMiddleware',))
     def test_middleware_can_access_user_when_processing_response(self):
         user = User.objects.create_user('john', 'john@example.com', 'password')
         key = 'abcd1234'
         Token.objects.create(key=key, user=user)
 
-        with self.settings(
-            MIDDLEWARE_CLASSES=('tests.test_middleware.MyMiddleware',)
-        ):
-            auth = 'Token ' + key
-            self.client.get('/', HTTP_AUTHORIZATION=auth)
+        self.client.get('/auth', HTTP_AUTHORIZATION='Token %s' % key)
+
+    @override_settings(MIDDLEWARE=('tests.test_middleware.RequestPOSTMiddleware',))
+    def test_middleware_can_access_request_post_when_processing_response(self):
+        response = self.client.post('/post', {'foo': 'bar'})
+        assert response.status_code == 200
+
+        response = self.client.post('/post', {'foo': 'bar'}, format='json')
+        assert response.status_code == 200