diff --git a/rest_framework/request.py b/rest_framework/request.py index 355cccad7..0a827728a 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -15,6 +15,7 @@ import sys from django.conf import settings from django.http import QueryDict from django.http.multipartparser import parse_header +from django.http.request import RawPostDataException from django.utils import six from django.utils.datastructures import MultiValueDict @@ -263,10 +264,20 @@ class Request(object): if content_length == 0: self._stream = None - elif hasattr(self._request, 'read'): + elif not self._request._read_started: self._stream = self._request else: - self._stream = six.BytesIO(self.raw_post_data) + self._stream = six.BytesIO(self.body) + + def _supports_form_parsing(self): + """ + Return True if this requests supports parsing form data. + """ + form_media = ( + 'application/x-www-form-urlencoded', + 'multipart/form-data' + ) + return any([parser.media_type in form_media for parser in self.parsers]) def _parse(self): """ @@ -274,8 +285,18 @@ class Request(object): May raise an `UnsupportedMediaType`, or `ParseError` exception. """ - stream = self.stream media_type = self.content_type + try: + stream = self.stream + except RawPostDataException: + if not hasattr(self._request, '_post'): + raise + # If request.POST has been accessed in middleware, and a method='POST' + # request was made with 'multipart/form-data', then the request stream + # will already have been exhausted. + if self._supports_form_parsing(): + return (self._request.POST, self._request.FILES) + stream = None if stream is None or media_type is None: empty_data = QueryDict('', encoding=self._request._encoding) diff --git a/tests/test_parsers.py b/tests/test_parsers.py index f3af6817f..5052e2e53 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -7,11 +7,16 @@ from django import forms from django.core.files.uploadhandler import ( MemoryFileUploadHandler, TemporaryFileUploadHandler ) +from django.http.request import RawPostDataException from django.test import TestCase from django.utils.six.moves import StringIO from rest_framework.exceptions import ParseError -from rest_framework.parsers import FileUploadParser, FormParser +from rest_framework.parsers import ( + FileUploadParser, FormParser, JSONParser, MultiPartParser +) +from rest_framework.request import Request +from rest_framework.test import APIRequestFactory class Form(forms.Form): @@ -122,3 +127,39 @@ class TestFileUploadParser(TestCase): def __replace_content_disposition(self, disposition): self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition + + +class TestPOSTAccessed(TestCase): + def setUp(self): + self.factory = APIRequestFactory() + + def test_post_accessed_in_post_method(self): + django_request = self.factory.post('/', {'foo': 'bar'}) + request = Request(django_request, parsers=[FormParser(), MultiPartParser()]) + django_request.POST + assert request.POST == {'foo': ['bar']} + assert request.data == {'foo': ['bar']} + + def test_post_accessed_in_post_method_with_json_parser(self): + django_request = self.factory.post('/', {'foo': 'bar'}) + request = Request(django_request, parsers=[JSONParser()]) + django_request.POST + assert request.POST == {} + assert request.data == {} + + def test_post_accessed_in_put_method(self): + django_request = self.factory.put('/', {'foo': 'bar'}) + request = Request(django_request, parsers=[FormParser(), MultiPartParser()]) + django_request.POST + assert request.POST == {'foo': ['bar']} + assert request.data == {'foo': ['bar']} + + def test_request_read_before_parsing(self): + django_request = self.factory.put('/', {'foo': 'bar'}) + request = Request(django_request, parsers=[FormParser(), MultiPartParser()]) + django_request.read() + with pytest.raises(RawPostDataException): + request.POST + with pytest.raises(RawPostDataException): + request.POST + request.data