Skip to content

Commit be74d11

Browse files
Fallback behavior for request parsing when request.POST already accessed. (encode#4500)
1 parent e82ee91 commit be74d11

File tree

2 files changed

+66
-4
lines changed

2 files changed

+66
-4
lines changed

rest_framework/request.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from django.conf import settings
1616
from django.http import QueryDict
1717
from django.http.multipartparser import parse_header
18+
from django.http.request import RawPostDataException
1819
from django.utils import six
1920
from django.utils.datastructures import MultiValueDict
2021

@@ -263,19 +264,39 @@ def _load_stream(self):
263264

264265
if content_length == 0:
265266
self._stream = None
266-
elif hasattr(self._request, 'read'):
267+
elif not self._request._read_started:
267268
self._stream = self._request
268269
else:
269-
self._stream = six.BytesIO(self.raw_post_data)
270+
self._stream = six.BytesIO(self.body)
271+
272+
def _supports_form_parsing(self):
273+
"""
274+
Return True if this requests supports parsing form data.
275+
"""
276+
form_media = (
277+
'application/x-www-form-urlencoded',
278+
'multipart/form-data'
279+
)
280+
return any([parser.media_type in form_media for parser in self.parsers])
270281

271282
def _parse(self):
272283
"""
273284
Parse the request content, returning a two-tuple of (data, files)
274285
275286
May raise an `UnsupportedMediaType`, or `ParseError` exception.
276287
"""
277-
stream = self.stream
278288
media_type = self.content_type
289+
try:
290+
stream = self.stream
291+
except RawPostDataException:
292+
if not hasattr(self._request, '_post'):
293+
raise
294+
# If request.POST has been accessed in middleware, and a method='POST'
295+
# request was made with 'multipart/form-data', then the request stream
296+
# will already have been exhausted.
297+
if self._supports_form_parsing():
298+
return (self._request.POST, self._request.FILES)
299+
stream = None
279300

280301
if stream is None or media_type is None:
281302
empty_data = QueryDict('', encoding=self._request._encoding)

tests/test_parsers.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@
77
from django.core.files.uploadhandler import (
88
MemoryFileUploadHandler, TemporaryFileUploadHandler
99
)
10+
from django.http.request import RawPostDataException
1011
from django.test import TestCase
1112
from django.utils.six.moves import StringIO
1213

1314
from rest_framework.exceptions import ParseError
14-
from rest_framework.parsers import FileUploadParser, FormParser
15+
from rest_framework.parsers import (
16+
FileUploadParser, FormParser, JSONParser, MultiPartParser
17+
)
18+
from rest_framework.request import Request
19+
from rest_framework.test import APIRequestFactory
1520

1621

1722
class Form(forms.Form):
@@ -122,3 +127,39 @@ def test_get_encoded_filename(self):
122127

123128
def __replace_content_disposition(self, disposition):
124129
self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition
130+
131+
132+
class TestPOSTAccessed(TestCase):
133+
def setUp(self):
134+
self.factory = APIRequestFactory()
135+
136+
def test_post_accessed_in_post_method(self):
137+
django_request = self.factory.post('/', {'foo': 'bar'})
138+
request = Request(django_request, parsers=[FormParser(), MultiPartParser()])
139+
django_request.POST
140+
assert request.POST == {'foo': ['bar']}
141+
assert request.data == {'foo': ['bar']}
142+
143+
def test_post_accessed_in_post_method_with_json_parser(self):
144+
django_request = self.factory.post('/', {'foo': 'bar'})
145+
request = Request(django_request, parsers=[JSONParser()])
146+
django_request.POST
147+
assert request.POST == {}
148+
assert request.data == {}
149+
150+
def test_post_accessed_in_put_method(self):
151+
django_request = self.factory.put('/', {'foo': 'bar'})
152+
request = Request(django_request, parsers=[FormParser(), MultiPartParser()])
153+
django_request.POST
154+
assert request.POST == {'foo': ['bar']}
155+
assert request.data == {'foo': ['bar']}
156+
157+
def test_request_read_before_parsing(self):
158+
django_request = self.factory.put('/', {'foo': 'bar'})
159+
request = Request(django_request, parsers=[FormParser(), MultiPartParser()])
160+
django_request.read()
161+
with pytest.raises(RawPostDataException):
162+
request.POST
163+
with pytest.raises(RawPostDataException):
164+
request.POST
165+
request.data

0 commit comments

Comments
 (0)