While going through some code, I came across a nice little trick I wanted to share about switching serializers based on the request method. My specific example is that I have multiple models that use a different serializer for the PUT (update) method.

Let's take a look at the code.


# drinks/models.py
class StoreDrink(models.Model):
	size = models.PositiveIntegerField(choices=SIZE_CHOICES)
	type = models.CharField(max_length=30)
	store = models.ForeignKey('stores.Store')
	price = models.DecimalField(max_digits=4, decimal_places=2)
	active = models.BooleanField(default=True)

# drinks/serializer.py
class StoreDrinkSerializer(ModelSerializer):
    class Meta:
        model = StoreDrink

class StoreDrinkUpdateSerializer(ModelSerializer):
    class Meta:
        model = StoreDrink
        read_only_fields = ['store', 'type', 'size']

Here you can see that we have a StoreDrink model with two different serializers. Now we'll go ahead and change the serializer via the get_serializer_class method which comes with all APIViews.


class StoreDrinkViewSet(ModelViewSet):
    queryset = StoreDrink.objects.all()
    serializer_class = StoreDrinkSerializer

    def get_serializer_class(self):
        "return a different serializer if performing an update"
        serializer_class = self.serializer_class

        if self.request.method == 'PUT' and self.update_serializer_class:
            serializer_class = StoreDrinkUpdateSerializer

        return serializer_class

Ok, this might be cool if you haven't done something like this before. But I have some news - we're not to the actual cool part yet. What happens now when we have something similar with another endpoint? What happens when you have something similar with 10 other endpoints?


# orders/models.py
class Order(models.Model):
	store = models.ForeignKey('stores.Store')
	order_datetime = models.DateTimeField(auto_now_add=True)
	customer_drink = models.ForeignKey(CustomerDrink)
	status = models.CharField(max_length=1, choices=ORDER_CHOICES, default="O")
	tip = models.DecimalField(max_digits=4, decimal_places=2, default=Decimal('1.00'),
		validators=[MinValueValidator(Decimal('0.00'))])

# orders/serializers.py
class OrderSerializer(ModelSerializer):

    class Meta:
        model = Order
        read_only_fields = ['order_datetime', 'active']

class OrderUpdateSerializer(OrderSerializer):
    "should only be able to update status"

    class Meta:
        model = Order
        read_only_fields = ['order_datetime', 'tip', 'customer_drink', 'store']

# orders/views.py
class OrderViewSet(UpdateSerializerMixin, ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer

    def get_serializer_class(self):
        "return a different serializer if performing an update"
        serializer_class = self.serializer_class

        if self.request.method == 'PUT' and self.update_serializer_class:
            serializer_class = OrderUpdateSerializer

        return serializer_class

As you can see, we're overriding the get_serializer_class method in almost the exact same way. The only thing that is different is the serializer class that we want to use for PUT methods. Let's dry this up a bit with a mixin that is applied to both views, and any others with similar functionality.


# core/mixins.py
class UpdateSerializerMixin(object):
    "specify a different serializer to use for PUT requests"

    def get_serializer_class(self):
        "return a different serializer if performing an update"
        serializer_class = self.serializer_class

        if self.request.method == 'PUT' and self.update_serializer_class:
            serializer_class = self.update_serializer_class

        return serializer_class

# orders/views.py
class OrderViewSet(UpdateSerializerMixin, ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer
    update_serializer_class = OrderUpdateSerializer

# drinks/views.py
class StoreDrinkViewSet(UpdateSerializerMixin, NoDestroyViewSet):
    queryset = StoreDrink.objects.all()
    serializer_class = StoreDrinkSerializer
    update_serializer_class = StoreDrinkUpdateSerializer

Beautiful, amiright?

We can now apply this mixin to any of our views that change the serializer class for a PUT request. We could possibly even expand upon that and not tie it to just a PUT request. I haven't had the need to do this yet, but hopefully soon!