Search code examples
pythondjangoswaggerdrf-spectacular

how to specify response for specific request methods for API action in django


I have an API action in django which accepts GET and POST requests where each of them will have a distinct response schema. If a request is of method "GET" we will return a list, if it is "POST" we will return only the created entity...

from drf_spectacular.utils import extend_schema

class SessionsViewSet(viewsets.ModelViewSet):
    
    ...
    
    @extend_schema(
        parameters=[query_params["extra_query_param"]],
        responses={
            "GET": serializers.ExampleSerializer(many=True),
            "POST": serializers.ExampleSerializer(many=False),
        },
    )
    @action(
        detail=True,
        url_path="example",
        methods=["GET", "POST"],
        filter_backends=[],
    )
    def example(self, request, pk, *args, **kwargs):
        match request.method:
            case "GET":
                queryset = MyModel.objects.filter(session_pk_id=pk)
                page = self.paginate_queryset(queryset)
                serializer = get_serializer(page, many=True)
                return self.get_paginated_response(serializer.data)
            case "POST":
                serializer = get_serializer(data=request.data, many=False
                )
                if serializer.is_valid():
                    serializer.save()
                    return Response(
                        serializer.data,
                        status=status.HTTP_201_CREATED,
                    )
                else:
                    return Response(
                        serializer.errors, status=status.HTTP_400_BAD_REQUEST
                    )
            case _:
                return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

The problem is that now I am not sure how to define this rule so that the swagger auto-schema will detect and present as such.

How can I explicitly state that a response schema or serializer belongs to a specific request method?


Solution

  • In Django Rest framework generic views and viewsets you have to specify the serializer in one of two ways: https://www.django-rest-framework.org/api-guide/generic-views/#get_serializer_classself

    1. Define the serializer_class attribute
    2. Override the get_serializer_class method

    Overriding the get_serializer_class method is useful when the serializer depends on the request, just like your case. Drf_spectacular introspection relies on this functionality. In your example you are defining the serializer within your custom action so drf_spectacular has no way of knowing which serializer is expected for each request type.

    So, you simply need to move your logic to get_serializer_class and drf_spectacular will be able to recognize it properly:

    def get_serializer_class(self):
        match self.request.method:
            case "POST":
                return FirstSerializerClass
            case "GET":
                return SecondSerializerClass
    
        raise ApiException(status=status.HTTP_405_METHOD_NOT_ALLOWED, reason="Method {} not allowed.".format(self.request.method))
    

    The above code assumes default error handling where ApiExceptions are automatically converted to HTTP responses.

    UPDATE: Don't forget to use self.get_serializer_class() or the shortcut method self.get_serializer() within your action to keep the code DRY.