Skip to content

Commit

Permalink
Do not treat missing non-form data as empty dict
Browse files Browse the repository at this point in the history
This allows views to distinguish missing payload from empty payload.

Related: encode#3647, encode#4566
  • Loading branch information
peterthomassen committed Jun 18, 2021
1 parent 24a938a commit 8b52dc0
Show file tree
Hide file tree
Showing 6 changed files with 33 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/api-guide/requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ REST framework's Request objects provide flexible request parsing that allows yo
* It includes all parsed content, including *file and non-file* inputs.
* It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests.
* It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming [JSON data] similarly to how you handle incoming [form data].
* 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`.

For more details see the [parsers documentation].

Expand Down
7 changes: 7 additions & 0 deletions docs/api-guide/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ A view inspector class that will be used for schema generation.

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

#### DEFAULT_MISSING_DATA

The value that should be used for `request.data` when the client did not send any data in the request body. This
setting applies only if the client did not send a header indicating form encoding.

Default: `None`

---

## Generic view settings
Expand Down
2 changes: 1 addition & 1 deletion rest_framework/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ def _parse(self):
if media_type and is_form_media_type(media_type):
empty_data = QueryDict('', encoding=self._request._encoding)
else:
empty_data = {}
empty_data = api_settings.DEFAULT_MISSING_DATA
empty_files = MultiValueDict()
return (empty_data, empty_files)

Expand Down
1 change: 1 addition & 0 deletions rest_framework/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
'DEFAULT_VERSIONING_CLASS': None,
'DEFAULT_MISSING_DATA': None,

# Generic view behavior
'DEFAULT_PAGINATION_CLASS': None,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ def test_standard_behaviour_determines_no_content_GET(self):
Ensure request.data returns empty QueryDict for GET request.
"""
request = Request(factory.get('/'))
assert request.data == {}
assert request.data is None

def test_standard_behaviour_determines_no_content_HEAD(self):
"""
Ensure request.data returns empty QueryDict for HEAD request.
"""
request = Request(factory.head('/'))
assert request.data == {}
assert request.data is None

def test_request_DATA_with_form_content(self):
"""
Expand Down
22 changes: 21 additions & 1 deletion tests/test_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class BasicSerializer(serializers.Serializer):
@api_view(['POST'])
def post_view(request):
serializer = BasicSerializer(data=request.data)
serializer.allow_null = ('allow_null' in request.query_params)
serializer.is_valid(raise_exception=True)
return Response(serializer.validated_data)

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

def test_empty_post_uses_default_boolean_value(self):
def test_missing_post_payload_causes_400(self):
response = self.client.post(
'/post-view/',
data=None,
content_type='application/json'
)
assert response.status_code == 400
assert response.data['non_field_errors'] == ['No data provided']

def test_missing_post_payload_allow_null_causes_200(self):
response = self.client.post(
'/post-view/?allow_null=1',
data=None,
content_type='application/json'
)
assert response.status_code == 200
assert response.data is None

@override_settings(REST_FRAMEWORK={'DEFAULT_MISSING_DATA': {}})
def test_missing_post_payload_coerced_dict_uses_default_boolean_value(self):
response = self.client.post(
'/post-view/',
data=None,
Expand Down

0 comments on commit 8b52dc0

Please sign in to comment.