Django Rest Framework - Writing Serializers that Differ From Your Internal Data Model

No matter how hard we try to get our data model correct (and even if we’ve done so!) sometimes by the time we need to use it, either for a public-facing API or an internal one, we need things to look a little different. Django Rest Framework(DRF) has some useful tools for allowing us to build serializers based on our models that differ somewhat from how we’ve defined things internally.

For our example, we’ll say we have a data model that looks, in part, like this:

class Item(models.Model):
    name = models.CharField(max_length=255)
    ...

class Offer(models.Model):
    item = models.ForeignKey(Item)
    ...

class Purchase(models.Model):
    offer = models.ForeignKey(Offer)
    ...

If we want to allow users to access Items via the API, we may add an ItemSerializer that leverages DRF’s ModelSerializer functionality, and looks like this, to start:

class ItemSerializer(serializers.ModelSerializer):
     class Meta:
         model = Item
         fields = ('name',)

Below are just two of the most common ways I’ve seen folks override their external data models to differ from internal ones. Other things you’re trying to accomplish that you’re not sure how to do? Feel free to reach out and I’ll add to this post! ✉️

Not all of the information we present is stored in the database, or should be. Sometimes we need to calculate or generate a piece of information at the time the data is requested. One example of this would be generating a login_token for a User when that record is accessed. For our example, we’ll include the number of times an item was purchased by adding a purchase_count to our ItemSerializer. The SerializerMethodField is perfect for this.

A SerializerMethodField is a read-only field that allows you to define a method that is evaluated when the resource is accessed, and the return data used as the value for this field. The method should be given the name get_<field_name> to be referenced automatically. Once we’ve added this field, our serializer will look like this:

from rest_framework.fields import SerializerMethodField

class ItemSerializer(serializers.ModelSerializer):
    purchase_count = SerializerMethodField()

    class Meta:
         model = Item
         fields = ('name', 'purchase_count')

    def get_purchase_count(item):
        return Purchase.objects.filter(
            offer__item=item, 
            state="success").count()

Our Item class has a field called name. But what if we decide that we want to call this display_name on our external-facing data model? We can do that by defining a new field on our serializer and giving it a source that matches the internal data model, and updating the value in our fields list in the Meta class. After making that change, our serializer would look like this:

from rest_framework.fields import SerializerMethodField

class ItemSerializer(serializers.ModelSerializer):
    purchase_count = SerializerMethodField()
    display_name = fields.CharField(source='name')

    class Meta:
         model = Item
         fields = ('display_name', 'purchase_count`)

    def get_purchase_count(item):
        return Purchase.objects.filter(
            offer__item=item, 
            state="success").count()