From ff6b51999868c4d6d4d4b61233f8cc201249b94b Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Tue, 7 Jan 2025 21:59:48 +0400 Subject: [PATCH 1/4] Fixed regression that tests using format still work Error only occurred on tests which return no content and use a renderer without charset (e.g. JSONRenderer) --- rest_framework/test.py | 2 +- tests/test_testing.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index 730b7708e2..690303fbf6 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -195,7 +195,7 @@ def _encode_data(self, data, format=None, content_type=None): ret = renderer.render(data) # Coerce text to bytes if required. - if isinstance(ret, str): + if isinstance(ret, str) and renderer.charset: ret = ret.encode(renderer.charset) return ret, content_type diff --git a/tests/test_testing.py b/tests/test_testing.py index 62dd24dfc6..dd67b058a2 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, parsers, serializers +from rest_framework import fields, parsers, renderers, serializers, status from rest_framework.authtoken.models import Token -from rest_framework.decorators import api_view, parser_classes +from rest_framework.decorators import api_view, parser_classes, renderer_classes from rest_framework.response import Response from rest_framework.test import ( APIClient, APIRequestFactory, URLPatternsTestCase, force_authenticate @@ -55,6 +55,10 @@ class BasicSerializer(serializers.Serializer): def post_json_view(request): return Response(request.data) +@api_view(['DELETE']) +@renderer_classes((renderers.JSONRenderer, )) +def delete_json_view(request): + return Response(status=status.HTTP_204_NO_CONTENT) @api_view(['POST']) def post_view(request): @@ -69,6 +73,7 @@ def post_view(request): path('redirect-view/', redirect_view), path('redirect-view//', redirect_307_308_view), path('post-json-view/', post_json_view), + path('delete-json-view/', delete_json_view), path('post-view/', post_view), ] @@ -254,6 +259,11 @@ def test_post_encodes_data_based_on_json_content_type(self): assert response.status_code == 200 assert response.data == data + def test_delete_based_on_format(self): + response = self.client.delete('/delete-json-view/', format='json') + assert response.status_code == status.HTTP_204_NO_CONTENT + assert response.data is None + class TestAPIRequestFactory(TestCase): def test_csrf_exempt_by_default(self): From c05926b851bbb3bced20a23278acbdfc7006af3c Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Tue, 7 Jan 2025 22:11:41 +0400 Subject: [PATCH 2/4] Fixed linting --- tests/test_testing.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_testing.py b/tests/test_testing.py index dd67b058a2..26a6e8ffb9 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -10,7 +10,9 @@ from rest_framework import fields, parsers, renderers, serializers, status from rest_framework.authtoken.models import Token -from rest_framework.decorators import api_view, parser_classes, renderer_classes +from rest_framework.decorators import ( + api_view, parser_classes, renderer_classes +) from rest_framework.response import Response from rest_framework.test import ( APIClient, APIRequestFactory, URLPatternsTestCase, force_authenticate @@ -55,11 +57,13 @@ class BasicSerializer(serializers.Serializer): def post_json_view(request): return Response(request.data) + @api_view(['DELETE']) @renderer_classes((renderers.JSONRenderer, )) def delete_json_view(request): return Response(status=status.HTTP_204_NO_CONTENT) + @api_view(['POST']) def post_view(request): serializer = BasicSerializer(data=request.data) From 1b5a84ab8b8c2a662f3c29c5f7abdc8b0d6a3815 Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Wed, 8 Jan 2025 10:47:06 +0400 Subject: [PATCH 3/4] Used early return as before --- rest_framework/test.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index 690303fbf6..a919f989d2 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -150,6 +150,8 @@ 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 (b'', content_type) assert format is None or content_type is None, ( 'You may not set both `format` and `content_type`.' @@ -161,9 +163,6 @@ def _encode_data(self, data, format=None, content_type=None): 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) @@ -181,6 +180,7 @@ 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 @@ -189,14 +189,9 @@ 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) and renderer.charset: - ret = ret.encode(renderer.charset) + # Coerce text to bytes if required. + if isinstance(ret, str): + ret = ret.encode(renderer.charset) return ret, content_type From 77a8015a32572ac7e96a18b2e2162a7ca8b14584 Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Wed, 8 Jan 2025 20:54:11 +0400 Subject: [PATCH 4/4] Move ret str check back to where it was --- rest_framework/test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index a919f989d2..c273724b99 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -189,9 +189,9 @@ def _encode_data(self, data, format=None, content_type=None): content_type, renderer.charset ) - # Coerce text to bytes if required. - if isinstance(ret, str): - ret = ret.encode(renderer.charset) + # Coerce text to bytes if required. + if isinstance(ret, str): + ret = ret.encode(renderer.charset) return ret, content_type