Skip to content

Commit

Permalink
explicit override for non-list serializers on ViewSet list #49
Browse files Browse the repository at this point in the history
  • Loading branch information
tfranzel committed May 8, 2020
1 parent a92731b commit df22119
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
2 changes: 1 addition & 1 deletion drf_spectacular/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,7 @@ def _get_response_for_code(self, serializer):
schema = build_basic_type(OpenApiTypes.OBJECT)
schema['description'] = 'Unspecified response body'

if self._is_list_view(serializer):
if self._is_list_view(serializer) and not get_override(serializer, 'many') is False:
schema = build_array_type(schema)
paginator = self._get_paginator()
if paginator:
Expand Down
18 changes: 18 additions & 0 deletions drf_spectacular/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,21 @@ def decorator(f):
return f

return decorator


def extend_schema_serializer(many=None):
"""
Decorator for the "serializer" kind. Intended for overriding default serializer behaviour that
cannot be influenced through `.extend_schema`.
:param many: override how serializer is initialized. Mainly used to coerce the list view detection
heuristic to acknowledge a non-list serializer.
"""
def decorator(klass):
if not hasattr(klass, '_spectacular_annotation'):
klass._spectacular_annotation = {}
if many is not None:
klass._spectacular_annotation['many'] = many
return klass

return decorator
40 changes: 40 additions & 0 deletions tests/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,3 +493,43 @@ def get(self, request):
assert schema['components']['schemas']['X']['properties']['x']['readOnly'] is True
assert 'enum' in schema['components']['schemas']['XEnum']
assert schema['components']['schemas']['XEnum']['type'] == 'integer'


def test_viewset_list_with_envelope(no_warnings):
class XSerializer(serializers.Serializer):
x = serializers.IntegerField()

def enveloper(serializer_class, list):
@extend_schema_serializer(many=False)
class EnvelopeSerializer(serializers.Serializer):
status = serializers.BooleanField()
data = XSerializer(many=list)

class Meta:
ref_name = 'Enveloped{}{}'.format(
serializer_class.__name__.replace("Serializer", ""),
"List" if list else "",
)
return EnvelopeSerializer

class XViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
@extend_schema(responses=enveloper(XSerializer, True))
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs) # pragma: no cover

@extend_schema(
responses=enveloper(XSerializer, False),
parameters=[OpenApiParameter('id', int, OpenApiParameter.PATH)],
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs) # pragma: no cover

schema = generate_schema('x', viewset=XViewset)

operation_list = schema['paths']['/x/']['get']
assert operation_list['operationId'] == 'x_list'
assert get_response_schema(operation_list)['$ref'] == '#/components/schemas/EnvelopedXList'

operation_retrieve = schema['paths']['/x/{id}/']['get']
assert operation_retrieve['operationId'] == 'x_retrieve'
assert get_response_schema(operation_retrieve)['$ref'] == '#/components/schemas/EnvelopedX'

0 comments on commit df22119

Please sign in to comment.