bdoubleu
bdoubleu

Reputation: 6127

DRF options request on viewset with multiple serializers

I'm using a mixin on my viewset so that multiple serializers can be used accross different viewset actions and any custom actions.

I have an extra action called invoice which is just a normal update but using a different serializer. I need to perform an OPTIONS request at the endpoint to get options for a <select> element. The problem is that when I perform the request it's picking up the serializer from the default update - OrderSerializer instead of InvoiceSerializer. How can I pick up the options from the correct serializer?

class MultipleSerializerMixin:
    """
    Mixin that allows for multiple serializers based on the view's
    `serializer_action_classes` attribute.

    ex.
        serializer_action_classes = {
            'list': ReadOnlyListSerializer,
            'retrieve': ReadOnlyDetailSerializer,
        }
    """
    def get_serializer_class(self):
        try:
            return self.serializer_action_classes[self.action]
        except (KeyError, AttributeError):
            return super().get_serializer_class()


class OrderAPIViewSet(MultipleSerializerMixin,
                      viewsets.ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = serializers.OrderSerializer
    serializer_action_classes = {
        'invoice': serializers.InvoiceSerializer,
    }

    @action(detail=True, methods=['put'], url_name='invoice')
    def invoice(self, request, *args, **kwargs):
        """
        Invoice the order and order lines.
        """
        return self.update(request, *args, **kwargs)

Update:

So after inspecting the determine_actions method in metadata.SimpleMetadata it would seem that when performing an OPTIONS request view.action is metadata instead of invoice which explains why the serializer is defaulting to view.serializer_class.

Upvotes: 3

Views: 2663

Answers (2)

mehdidrissi
mehdidrissi

Reputation: 9

Override get_serializer_class method is enough and OPTIONS request will detect which serializer to use :

def get_serializer_class(self):
   if self.request.method == 'GET':
      return ReadOnlyShopSerializer
   return ShopSerializer

Upvotes: 0

bdoubleu
bdoubleu

Reputation: 6127

One workaround is to create an extra action as a schema endpoint that could be accessed via a GET request that manually sets the action to invoice.

@action(detail=True, methods=['get', 'put'])
def invoice_schema(self, request, *args, **kwargs):
    self.action = 'invoice'
    data = self.metadata_class().determine_metadata(request, self)
    return Response(data, status=status.HTTP_200_OK)

A more DRY solution if you have multiple actions that use different serializers would be to override the view's options method and set the action from the query parameters. This could be added to MultipleSerializerMixin to make it the default behaviour for all views that use this mixin.

def options(self, request, *args, **kwargs):
    self.action = request.query_params.get('action')
    return super().options(request, *args, **kwargs)

Upvotes: 2

Related Questions