From 4579eae56fbbe16df153c8d561fd981964ddb41c Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Fri, 15 Mar 2019 14:10:42 -0700 Subject: [PATCH] support django 2.1 test client json data automatically serialized --- docs/api-guide/testing.md | 7 +++++-- rest_framework/test.py | 17 +++++++++++++---- tests/test_testing.py | 24 +++++++++++++++++++++--- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/docs/api-guide/testing.md b/docs/api-guide/testing.md index 261df80f27..ed585faf24 100644 --- a/docs/api-guide/testing.md +++ b/docs/api-guide/testing.md @@ -25,9 +25,12 @@ The `APIRequestFactory` class supports an almost identical API to Django's stand factory = APIRequestFactory() request = factory.post('/notes/', {'title': 'new idea'}) + # Using the standard RequestFactory API to encode JSON data + request = factory.post('/notes/', {'title': 'new idea'}, content_type='application/json') + #### Using the `format` argument -Methods which create a request body, such as `post`, `put` and `patch`, include a `format` argument, which make it easy to generate requests using a content type other than multipart form data. For example: +Methods which create a request body, such as `post`, `put` and `patch`, include a `format` argument, which make it easy to generate requests using a wide set of request formats. When using this argument, the factory will select an appropriate renderer and its configured `content_type`. For example: # Create a JSON POST request factory = APIRequestFactory() @@ -41,7 +44,7 @@ To support a wider set of request formats, or change the default format, [see th If you need to explicitly encode the request body, you can do so by setting the `content_type` flag. For example: - request = factory.post('/notes/', json.dumps({'title': 'new idea'}), content_type='application/json') + request = factory.post('/notes/', yaml.dump({'title': 'new idea'}), content_type='application/yaml') #### PUT and PATCH with form data diff --git a/rest_framework/test.py b/rest_framework/test.py index e939adcd7e..730b7708e2 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -151,14 +151,19 @@ def _encode_data(self, data, format=None, content_type=None): Encode the data returning a two tuple of (bytes, content_type) """ - if data is None: - return ('', content_type) - assert format is None or content_type is None, ( 'You may not set both `format` and `content_type`.' ) if content_type: + try: + data = self._encode_json(data, content_type) + except AttributeError: + pass + + if data is None: + data = '' + # Content type specified explicitly, treat data as a raw bytestring ret = force_bytes(data, settings.DEFAULT_CHARSET) @@ -176,7 +181,6 @@ def _encode_data(self, data, format=None, content_type=None): # Use format and render the data into a bytestring renderer = self.renderer_classes[format]() - ret = renderer.render(data) # Determine the content-type header from the renderer content_type = renderer.media_type @@ -185,6 +189,11 @@ def _encode_data(self, data, format=None, content_type=None): content_type, renderer.charset ) + if data is None: + ret = '' + else: + ret = renderer.render(data) + # Coerce text to bytes if required. if isinstance(ret, str): ret = ret.encode(renderer.charset) diff --git a/tests/test_testing.py b/tests/test_testing.py index a7e00ab63e..62dd24dfc6 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -8,9 +8,9 @@ from django.test import TestCase, override_settings from django.urls import path -from rest_framework import fields, serializers +from rest_framework import fields, parsers, serializers from rest_framework.authtoken.models import Token -from rest_framework.decorators import api_view +from rest_framework.decorators import api_view, parser_classes from rest_framework.response import Response from rest_framework.test import ( APIClient, APIRequestFactory, URLPatternsTestCase, force_authenticate @@ -50,6 +50,12 @@ class BasicSerializer(serializers.Serializer): flag = fields.BooleanField(default=lambda: True) +@api_view(['POST']) +@parser_classes((parsers.JSONParser,)) +def post_json_view(request): + return Response(request.data) + + @api_view(['POST']) def post_view(request): serializer = BasicSerializer(data=request.data) @@ -62,7 +68,8 @@ def post_view(request): path('session-view/', session_view), path('redirect-view/', redirect_view), path('redirect-view//', redirect_307_308_view), - path('post-view/', post_view) + path('post-json-view/', post_json_view), + path('post-view/', post_view), ] @@ -236,6 +243,17 @@ def test_empty_post_uses_default_boolean_value(self): assert response.status_code == 200 assert response.data == {"flag": True} + def test_post_encodes_data_based_on_json_content_type(self): + data = {'data': True} + response = self.client.post( + '/post-json-view/', + data=data, + content_type='application/json' + ) + + assert response.status_code == 200 + assert response.data == data + class TestAPIRequestFactory(TestCase): def test_csrf_exempt_by_default(self):