diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md index e877c868d..2d75e47dd 100644 --- a/docs/api-guide/requests.md +++ b/docs/api-guide/requests.md @@ -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]. diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index d42000260..680b36176 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -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 diff --git a/rest_framework/request.py b/rest_framework/request.py index 17ceadb08..b9ddc9553 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -341,7 +341,7 @@ class Request: 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) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 9eb4c5653..a8f63e224 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -46,6 +46,7 @@ DEFAULTS = { '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, diff --git a/tests/test_request.py b/tests/test_request.py index 8f55d00ed..2dff1c6fa 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -56,14 +56,14 @@ class TestContentParsing(TestCase): 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): """ diff --git a/tests/test_testing.py b/tests/test_testing.py index cc60e4f00..99c6a1d7a 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -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) @@ -191,7 +192,26 @@ class TestAPITestClient(TestCase): 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,