Is there a way to get an enumeration type text choice field to accept an incoming post request with the human readable value?

Viewed 408

It was my understanding (maybe incorrect) that using enumeration type text choices for CharFields is useful because your database stores less characters on the database but you can still work with a human readable values. However it seems counter-productive when you are posting data from the frontend to the backend because the frontend has to post the value that will be stored in the database, not the human readable one. For example:

class MyModel(models.Model):
    class Animals(models.TextChoices):
        ALLIGATOR = 'a', 'Alligator'
        BEAR = 'b', 'Bear'
        COYOTE = 'c', 'Coyote'

    animal = models.CharField(max_length=20, choices=Animals.choices)

If i post {animal: 'Coyote'} to the endpoint, it will say 'animal is not a valid choice'. I have to post {animal: 'c'} instead. Is there a way I can get the MyModelSerializer to accept human readable values instead of having to setup frontend select element options to have differing labels and values?

1 Answers

You can override the to_internal_value method of ChoiceField.

from rest_framework.fields import ChoiceField


class AllowHumanReadableNameAsValueChoiceField(ChoiceField):

    def to_internal_value(self, data):
        value = next((v for k, v in self.choices.items()), None)
        if value is not None:
            return value
        return super().to_internal_value(data)

Usage:

class MyModelSerializer(ModelSerializer):
    class Meta:
        model = MyModel
        fields = '__all__'

    serializer_choice_field = AllowHumanReadableNameAsValueChoiceField

Why choice fields are useful

It was my understanding (maybe incorrect) that using enumeration type text choices for CharFields is useful because your database stores less characters on the database

That's usually not the main motivation to use choices.

Rather, from https://docs.djangoproject.com/en/3.2/ref/models/fields/#choices:

If choices are given, they’re enforced by model validation and the default form widget will be a select box with these choices instead of the standard text field.

For each model field that has choices set, Django will add a method to retrieve the human-readable name for the field’s current value.

The label can be a lazy translatable string.

An example

You might have had the species name in the database:

SPLENDID_POISON_FROG = 'Oophaga speciosa', 'Splendid poison frog'

After this species was reclassified as extinct in 2020, you might have wanted to change the human-readable name to indicate that status without having to change the database values:

SPLENDID_POISON_FROG = 'Oophaga speciosa', 'Splendid poison frog (Extinct)'
Related