Displaying Human-Readable ArrayField Choices in Django REST Framework

Abenezer Belachew

Abenezer Belachew · January 20, 2024

3 min read

I'll try to keep this article short and to the point. I recently had to deal with a problem where I had a Django model with an ArrayField containing a CharField that had a bunch of text choices. I wanted to display the choices in a human-readable format in Django REST Framework. This is how I did it. I'll be using some made up models for this example.

app/models.py
from django.db import models
from django.contrib.postgres.fields import ArrayField
from django.utils.translation import gettext_lazy as _

class Pizza(models.Model):
    class PizzaType(models.TextChoices):
        MARGHERITA = 'M', _('Margherita')
        PEPPERONI = 'P', _('Pepperoni')
        HAWAIIAN = 'H', _('Hawaiian')
        MEAT_LOVERS = 'ML', _('Meat Lovers')
        VEGGIE = 'V', _('Veggie')

    name = models.CharField(max_length=100)
    toppings = ArrayField(
        models.CharField(max_length=2, choices=PizzaType.choices),
        default=list,
        blank=True,
    )

Serialized in the following way:

serializers.py
from rest_framework import serializers
from .models import Pizza

class PizzaSerializer(serializers.ModelSerializer):
    class Meta:
        model = Pizza
        fields = ['name', 'toppings']

Now, if we were to create a Pizza object with the following toppings: ['M', 'P', 'H'], we would get the following response:

{
    "name": "My Pizza",
    "toppings": [
        "M",
        "P",
        "H"
    ]
}

But I don't want to display the toppings in this format on the client side. I want to display them in the human-readable format.

There are a few ways to do this. I'll cover two of them here.

1. Override using the get_<field_name> method

The first way is to explicitly define the array field with the choices argument in the serializer and then modify it using the get_<field_name> method.

serializers.py
from rest_framework import serializers
from .models import Pizza

class PizzaSerializer(serializers.ModelSerializer):
    toppings = serializers.SerializerMethodField()

    class Meta:
        model = Pizza
        fields = ['name', 'toppings']

    def get_toppings(self, obj):
        return [Pizza.PizzaType(topping).label for topping in obj.toppings]

Now, the response looks like this:

{
    "name": "My Pizza",
    "toppings": [
        "Margherita",
        "Pepperoni",
        "Hawaiian"
    ]
}

2. Override using the to_representation method

The second way is to override the to_representation method of the serializer.

serializers.py
from rest_framework import serializers
from .models import Pizza

class PizzaSerializer(serializers.ModelSerializer):
    class Meta:
        model = Pizza
        fields = ['name', 'toppings']

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        representation['toppings'] = [Pizza.PizzaType(topping).label for topping in representation['toppings']]
        return representation

or

serializers.py
from rest_framework import serializers
from .models import Pizza

class PizzaSerializer(serializers.ModelSerializer):
    toppings = serializers.ListField(child=serializers.ChoiceField(choices=Pizza.PizzaType.choices), allow_empty=True, read_only=True)

    class Meta:
        model = Pizza
        fields = ['name', 'toppings']

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        representation['toppings'] = [Pizza.PizzaType(topping).label for topping in representation['toppings']]
        return representation

Now, the response looks like this:

{
    "name": "My Pizza",
    "toppings": [
        "Margherita",
        "Pepperoni",
        "Hawaiian"
    ]
}

🐡️