Skip to content

Commit c26eb31

Browse files
Do not treat missing non-form data as empty dict
This allows views to distinguish missing payload from empty payload. Related: #3647, #4566
1 parent 764dabd commit c26eb31

File tree

6 files changed

+33
-4
lines changed

6 files changed

+33
-4
lines changed

docs/api-guide/requests.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ REST framework's Request objects provide flexible request parsing that allows yo
2424
* It includes all parsed content, including *file and non-file* inputs.
2525
* It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests.
2626
* It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming JSON data in the same way that you handle incoming form data.
27+
* If the client does not send any data and does not specify form encoding, the value of `.data` is determined by the `DEFAULT_MISSING_DATA` setting. If form encoding is used and no data is sent, `.data` will be an empty Django `QueryDict`.
2728

2829
For more details see the [parsers documentation].
2930

docs/api-guide/settings.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ A view inspector class that will be used for schema generation.
103103

104104
Default: `'rest_framework.schemas.openapi.AutoSchema'`
105105

106+
#### DEFAULT_MISSING_DATA
107+
108+
The value that should be used for `request.data` when the client did not send any data in the request body. This
109+
setting applies only if the client did not send a header indicating form encoding.
110+
111+
Default: `None`
112+
106113
---
107114

108115
## Generic view settings

rest_framework/request.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def _parse(self):
334334
if media_type and is_form_media_type(media_type):
335335
empty_data = QueryDict('', encoding=self._request._encoding)
336336
else:
337-
empty_data = {}
337+
empty_data = api_settings.DEFAULT_MISSING_DATA
338338
empty_files = MultiValueDict()
339339
return (empty_data, empty_files)
340340

rest_framework/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
4747
'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
4848
'DEFAULT_VERSIONING_CLASS': None,
49+
'DEFAULT_MISSING_DATA': None,
4950

5051
# Generic view behavior
5152
'DEFAULT_PAGINATION_CLASS': None,

tests/test_request.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@ def test_standard_behaviour_determines_no_content_GET(self):
5656
Ensure request.data returns empty QueryDict for GET request.
5757
"""
5858
request = Request(factory.get('/'))
59-
assert request.data == {}
59+
assert request.data is None
6060

6161
def test_standard_behaviour_determines_no_content_HEAD(self):
6262
"""
6363
Ensure request.data returns empty QueryDict for HEAD request.
6464
"""
6565
request = Request(factory.head('/'))
66-
assert request.data == {}
66+
assert request.data is None
6767

6868
def test_request_DATA_with_form_content(self):
6969
"""

tests/test_testing.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class BasicSerializer(serializers.Serializer):
4242
@api_view(['POST'])
4343
def post_view(request):
4444
serializer = BasicSerializer(data=request.data)
45+
serializer.allow_null = ('allow_null' in request.query_params)
4546
serializer.is_valid(raise_exception=True)
4647
return Response(serializer.validated_data)
4748

@@ -191,7 +192,26 @@ def test_invalid_multipart_data(self):
191192
path='/view/', data={'valid': 123, 'invalid': {'a': 123}}
192193
)
193194

194-
def test_empty_post_uses_default_boolean_value(self):
195+
def test_missing_post_payload_causes_400(self):
196+
response = self.client.post(
197+
'/post-view/',
198+
data=None,
199+
content_type='application/json'
200+
)
201+
assert response.status_code == 400
202+
assert response.data['non_field_errors'] == ['No data provided']
203+
204+
def test_missing_post_payload_allow_null_causes_200(self):
205+
response = self.client.post(
206+
'/post-view/?allow_null=1',
207+
data=None,
208+
content_type='application/json'
209+
)
210+
assert response.status_code == 200
211+
assert response.data is None
212+
213+
@override_settings(REST_FRAMEWORK={'DEFAULT_MISSING_DATA': {}})
214+
def test_missing_post_payload_coerced_dict_uses_default_boolean_value(self):
195215
response = self.client.post(
196216
'/post-view/',
197217
data=None,

0 commit comments

Comments
 (0)