Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fallback behavior for request parsing when request.POST already accessed #4500

Merged
merged 1 commit into from
Sep 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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