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?
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
serializer_class
attributeget_serializer_class
methodOverriding 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.