Skip to content

Commit

Permalink
fix nested serializer detection & smarter metadata extraction #319
Browse files Browse the repository at this point in the history
  • Loading branch information
tfranzel committed Mar 2, 2021
1 parent 8c48c49 commit d8c416a
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 14 deletions.
31 changes: 18 additions & 13 deletions drf_spectacular/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,8 +437,11 @@ def _map_model_field(self, model_field, direction):
)
return build_basic_type(OpenApiTypes.STR)

def _map_serializer_field(self, field, direction):
meta = self._get_serializer_field_meta(field)
def _map_serializer_field(self, field, direction, collect_meta=True):
if collect_meta:
meta = self._get_serializer_field_meta(field)
else:
meta = {}

if has_override(field, 'field'):
override = get_override(field, 'field')
Expand All @@ -449,7 +452,7 @@ def _map_serializer_field(self, field, direction):
elif isinstance(override, dict):
schema = override
else:
schema = self._map_serializer_field(force_instance(override), direction)
schema = self._map_serializer_field(force_instance(override), direction, False)

field_component_name = get_override(field, 'field_component_name')
if field_component_name:
Expand All @@ -469,23 +472,23 @@ def _map_serializer_field(self, field, direction):
schema = serializer_field_extension.map_serializer_field(self, direction)
return append_meta(schema, meta)

# nested serializer
if isinstance(field, serializers.Serializer):
component = self.resolve_serializer(field, direction)
return append_meta(component.ref, meta) if component else None

# nested serializer with many=True gets automatically replaced with ListSerializer
if isinstance(field, serializers.ListSerializer):
if is_list_serializer(field):
if is_serializer(field.child):
component = self.resolve_serializer(field.child, direction)
return append_meta(build_array_type(component.ref), meta) if component else None
else:
schema = self._map_serializer_field(field.child, direction)
schema = self._map_serializer_field(field.child, direction, collect_meta)
return append_meta(build_array_type(schema), meta)

# nested serializer
if is_serializer(field):
component = self.resolve_serializer(field, direction)
return append_meta(component.ref, meta) if component else None

# Related fields.
if isinstance(field, serializers.ManyRelatedField):
schema = self._map_serializer_field(field.child_relation, direction)
schema = self._map_serializer_field(field.child_relation, direction, collect_meta)
# remove hand-over initkwargs applying only to outer scope
schema.pop('description', None)
schema.pop('readOnly', None)
Expand Down Expand Up @@ -542,7 +545,7 @@ def _map_serializer_field(self, field, direction):
component = self.resolve_serializer(field.child, direction)
return append_meta(build_array_type(component.ref), meta) if component else None
else:
schema = self._map_serializer_field(field.child, direction)
schema = self._map_serializer_field(field.child, direction, collect_meta)
return append_meta(build_array_type(schema), meta)

# DateField and DateTimeField type is string
Expand Down Expand Up @@ -625,7 +628,9 @@ def _map_serializer_field(self, field, direction):
if anyisinstance(field, [serializers.DictField, serializers.HStoreField]):
content = build_basic_type(OpenApiTypes.OBJECT)
if not isinstance(field.child, _UnvalidatedField):
content['additionalProperties'] = self._map_serializer_field(field.child, direction)
content['additionalProperties'] = self._map_serializer_field(
field.child, direction, collect_meta
)
return append_meta(content, meta)

if isinstance(field, serializers.CharField):
Expand Down
29 changes: 28 additions & 1 deletion tests/test_polymorphic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import pytest
from django.db import models
from rest_framework import serializers, viewsets
from rest_framework.decorators import api_view
from rest_framework.response import Response

from drf_spectacular.openapi import AutoSchema
from drf_spectacular.utils import OpenApiParameter, PolymorphicProxySerializer, extend_schema
from drf_spectacular.utils import (
OpenApiParameter, PolymorphicProxySerializer, extend_schema, extend_schema_field,
)
from tests import assert_schema, generate_schema


Expand Down Expand Up @@ -90,3 +93,27 @@ def test_polymorphic(no_warnings, viewset):
generate_schema('persons', viewset),
'tests/test_polymorphic.yml'
)


def test_polymorphic_serializer_as_field_via_extend_schema_field(no_warnings):
@extend_schema_field(
PolymorphicProxySerializer(
component_name='MetaPerson',
serializers=[LegalPersonSerializer, NaturalPersonSerializer],
resource_type_field_name='type',
)
)
class XField(serializers.DictField):
pass

class XSerializer(serializers.Serializer):
field = XField()

@extend_schema(request=XSerializer, responses=XSerializer)
@api_view(['GET'])
def view_func(request, format=None):
pass # pragma: no cover

schema = generate_schema('x', view_function=view_func)
assert 'MetaPerson' in schema['components']['schemas']
assert 'MetaPerson' in schema['components']['schemas']['X']['properties']['field']['$ref']

0 comments on commit d8c416a

Please sign in to comment.