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!