Skip to content

Commit

Permalink
Fallback behavior for request parsing when request.POST already acces…
Browse files Browse the repository at this point in the history
…sed. (#4500)
  • Loading branch information
tomchristie committed Sep 21, 2016
1 parent e82ee91 commit be74d11
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 4 deletions.
27 changes: 24 additions & 3 deletions rest_framework/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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

Expand Down Expand Up @@ -263,19 +264,39 @@ def _load_stream(self):

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):
"""
Parse the request content, returning a two-tuple of (data, files)
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)
Expand Down
43 changes: 42 additions & 1 deletion tests/test_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
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):
Expand Down Expand Up @@ -122,3 +127,39 @@ def test_get_encoded_filename(self):

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

0 comments on commit be74d11

Please sign in to comment.