Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 362
bdoubleu Avatar asked Nov 25 '25 03:11

bdoubleu


1 Answers

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)
like image 154
bdoubleu Avatar answered Nov 28 '25 02:11

bdoubleu