Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to annotate a serializer field with different request/response schemas? #778

Closed
jerivas opened this issue Jul 27, 2022 · 2 comments
Closed

Comments

@jerivas
Copy link

jerivas commented Jul 27, 2022

I have this custom serializer field that is used in a number of serializers:

class NestedPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
    def __init__(self, serializer, **kwargs):
        """
        On read display a complete nested representation of the object(s)
        On write only require the PK (not an entire object) as value
        """
        self.serializer = serializer
        super().__init__(**kwargs)

    def to_representation(self, obj):
        return self.serializer(obj, context=self.context).to_representation(obj)

# Usage
class MySerializer:
    related_obj = NestedPrimaryKeyRelatedField(RelatedSerializer, allow_null=True, required=False)

The idea is that when the client GETs MySerializer they receive a nice nested representation of related_obj using RelatedSerializer, but when they POST/PUT/PATCH they only need to provide the PK (not an entire object) to set the value of related_obj.

The actual functionality works as expected, but the schema generated by Spectacular assumes the field is just a primary key for both read and write operations, while in reality on read the schema should be a full object based on RelatedSerializer.

I tried to create a custom extension but I'm struggling with the fine details:

class NestedPkExtension(OpenApiSerializerFieldExtension):
    # Ensure annotations use different read/write serializers when using NestedPrimaryKeyRelatedField
    target_class = NestedPrimaryKeyRelatedField

    def map_serializer_field(self, auto_schema, direction: Direction):
        # I know the direction plays a role here, but don't know exactly what
        if direction == "response":
            # Return an object schema
        else:
            # Return a primary key schema

Any help would be appreciated 🙏

@tfranzel
Copy link
Owner

hi @jerivas

Please be aware that asymmetric serializers requires you to use the setting 'COMPONENT_SPLIT_REQUEST': True. OpenAPI by itself cannot represent request/response differences. We therefore split them in two component.

I think the following should do whay you need. they key is to know the where to hook into auto_schema.

    class NestedPkExtension(OpenApiSerializerFieldExtension):
        # Ensure annotations use different read/write serializers when using NestedPrimaryKeyRelatedField
        target_class = NestedPrimaryKeyRelatedField

        def map_serializer_field(self, auto_schema, direction: Direction):
            if direction == "response":
                # target is NestedPrimaryKeyRelatedField instance.
                # build a component from the serializer and return a reference to that component
                component = auto_schema.resolve_serializer(self.target.serializer, direction)
                return component.ref if component else None
            else:
                # Return a primary key schema
                return build_basic_type(OpenApiTypes.UUID)  # or whatever key you use

@jerivas
Copy link
Author

jerivas commented Jul 27, 2022

Thank you so much! That did the trick 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants