diff --git a/drf_spectacular/contrib/rest_polymorphic.py b/drf_spectacular/contrib/rest_polymorphic.py index 1e725c52..fe0c64d6 100644 --- a/drf_spectacular/contrib/rest_polymorphic.py +++ b/drf_spectacular/contrib/rest_polymorphic.py @@ -42,8 +42,13 @@ def map_serializer(self, auto_schema, direction): f'this might lead to code generation issues.' ) + one_of_list = [] + for _, ref in sub_components: + if ref not in one_of_list: + one_of_list.append(ref) + return { - 'oneOf': [ref for _, ref in sub_components], + 'oneOf': one_of_list, 'discriminator': { 'propertyName': serializer.resource_type_field_name, 'mapping': {resource_type: ref['$ref'] for resource_type, ref in sub_components}, diff --git a/drf_spectacular/serializers.py b/drf_spectacular/serializers.py index 8fafcb6d..5721abe7 100644 --- a/drf_spectacular/serializers.py +++ b/drf_spectacular/serializers.py @@ -20,8 +20,12 @@ def map_serializer(self, auto_schema, direction): if not self._has_discriminator(): return {'oneOf': [schema for _, schema in sub_components]} else: + one_of_list = [] + for _, schema in sub_components: + if schema not in one_of_list: + one_of_list.append(schema) return { - 'oneOf': [schema for _, schema in sub_components], + 'oneOf': one_of_list, 'discriminator': { 'propertyName': self.target.resource_type_field_name, 'mapping': {resource_type: schema['$ref'] for resource_type, schema in sub_components} diff --git a/tests/test_polymorphic.py b/tests/test_polymorphic.py index 27b2cf9d..6f8bb349 100644 --- a/tests/test_polymorphic.py +++ b/tests/test_polymorphic.py @@ -1,3 +1,4 @@ +from random import choice from unittest import mock import pytest @@ -324,6 +325,45 @@ def view_func(request, format=None): } +def test_polymorphic_with_default_serializer(no_warnings): + class DefaultPersonSerializer(serializers.ModelSerializer): + type = serializers.SerializerMethodField() + + class Meta: + model = NaturalPerson2 + fields = ('id', 'type') + + def get_type(self, obj) -> str: + return choice(['basic', 'simple']) + + class XViewSet(viewsets.GenericViewSet): + @extend_schema( + responses=PolymorphicProxySerializer( + component_name='MetaPerson', + serializers={ + 'natural': NaturalPersonSerializer, + 'basic': DefaultPersonSerializer, + 'simple': DefaultPersonSerializer, + }, + resource_type_field_name='type', + ) + ) + def list(self, request, *args, **kwargs): + return Response({}) # pragma: no cover + + schema = generate_schema('x', XViewSet) + components = schema['components']['schemas'] + assert components['MetaPerson']['oneOf'] == [ + {'$ref': '#/components/schemas/NaturalPerson'}, + {'$ref': '#/components/schemas/DefaultPerson'} + ] + assert components['MetaPerson']['discriminator']['mapping'] == { + 'natural': '#/components/schemas/NaturalPerson', + 'basic': '#/components/schemas/DefaultPerson', + 'simple': '#/components/schemas/DefaultPerson' + } + + def test_polymorphic_forced_many_false(no_warnings): class XViewSet(viewsets.GenericViewSet): @extend_schema(