Displaying Human-Readable ArrayField Choices in Django REST Framework
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.
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:
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.
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.
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
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"
]
}
🐡️