Skip to content

Commit 02d9bfc

Browse files
authored
Fixes BrowsableAPIRenderer for usage with ListSerializer. (#7530)
Renders list of items in raw_data_form and does not renders form in template while using with `ListSerializer` (`many=True`).
1 parent 376a5cb commit 02d9bfc

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

rest_framework/renderers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,9 @@ def get_rendered_html_form(self, data, view, method, request):
506506
return self.render_form_for_serializer(serializer)
507507

508508
def render_form_for_serializer(self, serializer):
509+
if isinstance(serializer, serializers.ListSerializer):
510+
return None
511+
509512
if hasattr(serializer, 'initial_data'):
510513
serializer.is_valid()
511514

@@ -555,10 +558,13 @@ def get_raw_data_form(self, data, view, method, request):
555558
context['indent'] = 4
556559

557560
# strip HiddenField from output
561+
is_list_serializer = isinstance(serializer, serializers.ListSerializer)
562+
serializer = serializer.child if is_list_serializer else serializer
558563
data = serializer.data.copy()
559564
for name, field in serializer.fields.items():
560565
if isinstance(field, serializers.HiddenField):
561566
data.pop(name, None)
567+
data = [data] if is_list_serializer else data
562568
content = renderer.render(data, accepted, context)
563569
# Renders returns bytes, but CharField expects a str.
564570
content = content.decode()

tests/test_renderers.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,13 +633,72 @@ def list_action(self, request):
633633
class AuthExampleViewSet(ExampleViewSet):
634634
permission_classes = [permissions.IsAuthenticated]
635635

636+
class SimpleSerializer(serializers.Serializer):
637+
name = serializers.CharField()
638+
636639
router = SimpleRouter()
637640
router.register('examples', ExampleViewSet, basename='example')
638641
router.register('auth-examples', AuthExampleViewSet, basename='auth-example')
639642
urlpatterns = [path('api/', include(router.urls))]
640643

641644
def setUp(self):
642645
self.renderer = BrowsableAPIRenderer()
646+
self.renderer.accepted_media_type = ''
647+
self.renderer.renderer_context = {}
648+
649+
def test_render_form_for_serializer(self):
650+
with self.subTest('Serializer'):
651+
serializer = BrowsableAPIRendererTests.SimpleSerializer(data={'name': 'Name'})
652+
form = self.renderer.render_form_for_serializer(serializer)
653+
assert isinstance(form, str), 'Must return form for serializer'
654+
655+
with self.subTest('ListSerializer'):
656+
list_serializer = BrowsableAPIRendererTests.SimpleSerializer(data=[{'name': 'Name'}], many=True)
657+
form = self.renderer.render_form_for_serializer(list_serializer)
658+
assert form is None, 'Must not return form for list serializer'
659+
660+
def test_get_raw_data_form(self):
661+
with self.subTest('Serializer'):
662+
class DummyGenericViewsetLike(APIView):
663+
def get_serializer(self, **kwargs):
664+
return BrowsableAPIRendererTests.SimpleSerializer(**kwargs)
665+
666+
def get(self, request):
667+
response = Response()
668+
response.view = self
669+
return response
670+
671+
post = get
672+
673+
view = DummyGenericViewsetLike.as_view()
674+
_request = APIRequestFactory().get('/')
675+
request = Request(_request)
676+
response = view(_request)
677+
view = response.view
678+
679+
raw_data_form = self.renderer.get_raw_data_form({'name': 'Name'}, view, 'POST', request)
680+
assert raw_data_form['_content'].initial == '{\n "name": ""\n}'
681+
682+
with self.subTest('ListSerializer'):
683+
class DummyGenericViewsetLike(APIView):
684+
def get_serializer(self, **kwargs):
685+
return BrowsableAPIRendererTests.SimpleSerializer(many=True, **kwargs) # returns ListSerializer
686+
687+
def get(self, request):
688+
response = Response()
689+
response.view = self
690+
return response
691+
692+
post = get
693+
694+
view = DummyGenericViewsetLike.as_view()
695+
_request = APIRequestFactory().get('/')
696+
request = Request(_request)
697+
response = view(_request)
698+
view = response.view
699+
700+
raw_data_form = self.renderer.get_raw_data_form([{'name': 'Name'}], view, 'POST', request)
701+
assert raw_data_form['_content'].initial == '[\n {\n "name": ""\n }\n]'
643702

644703
def test_get_description_returns_empty_string_for_401_and_403_statuses(self):
645704
assert self.renderer.get_description({}, status_code=401) == ''

0 commit comments

Comments
 (0)