Skip to content

Commit ff1778e

Browse files
committed
Fixes BrowsableAPIRenderer for usage with ListSerializer.
Renders list of items in raw_data_form and does not renders form in template while using with `ListSerializer` (`many=True`).
1 parent cdc956a commit ff1778e

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
@@ -511,6 +511,9 @@ def get_rendered_html_form(self, data, view, method, request):
511511
return self.render_form_for_serializer(serializer)
512512

513513
def render_form_for_serializer(self, serializer):
514+
if isinstance(serializer, serializers.ListSerializer):
515+
return None
516+
514517
if hasattr(serializer, 'initial_data'):
515518
serializer.is_valid()
516519

@@ -560,10 +563,13 @@ def get_raw_data_form(self, data, view, method, request):
560563
context['indent'] = 4
561564

562565
# strip HiddenField from output
566+
is_list_serializer = isinstance(serializer, serializers.ListSerializer)
567+
serializer = serializer.child if is_list_serializer else serializer
563568
data = serializer.data.copy()
564569
for name, field in serializer.fields.items():
565570
if isinstance(field, serializers.HiddenField):
566571
data.pop(name, None)
572+
data = [data] if is_list_serializer else data
567573
content = renderer.render(data, accepted, context)
568574
# Renders returns bytes, but CharField expects a str.
569575
content = content.decode()

tests/test_renderers.py

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

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

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

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

0 commit comments

Comments
 (0)