Sudhanshu Mishra
Sudhanshu Mishra

Reputation: 2092

How to send the associated value of a choice field

I have a model called Ticket with a choice field status:

STATUS_CHOICES = (
    ('1', 'Open'),
    ('2', 'Resolved'),
    ('3', 'Won\'t fix')
)
status = models.CharField(
    "Status",
    max_length=1,
    choices=STATUS_CHOICES,
    blank=False,
    default='1'
)

I am using Django rest framework for the API. The default model serializer sends the first value of choice i.e. 1 instead of Open. How can I make it to send the second text value in response.

Here's a part of my Serializer:

class TicketSerializer(serializers.ModelSerializer):
    status = serializers.ChoiceField(choices=Ticket.STATUS_CHOICES)        

    class Meta:
        model = Ticket
        fields = ('status', )

Upvotes: 0

Views: 1574

Answers (2)

Sudhanshu Mishra
Sudhanshu Mishra

Reputation: 2092

Following approach solves this problem. We can create a custom field for Choices.

class ChoicesField(serializers.Field):
    def __init__(self, choices, **kwargs):
        self._choices = choices
        super(ChoicesField, self).__init__(**kwargs)

    def to_representation(self, obj):
        return self._choices[int(obj) - 1]  # obj is the first value

    def to_internal_value(self, data):
        return getattr(self._choices, data)

This is similar to the solution suggested by Tahir but in a cleaner way.

Upvotes: 1

Muhammad Tahir
Muhammad Tahir

Reputation: 5184

API's are generally used to fetch data, not data's UI representation. In ('1', 'Choice 1'), '1' is the value to be stored and used everywhere, 'Choice 1' is its UI representation that we need to explicitly use when we need it (in the UI i.e. templates).

DjangoAdmin is a complete Django app so it uses the UI representation explicitly. If you use Django Forms you need to use it explicitly too. It is not automatically used.

In template if you do {{ my_form_choice_field.value }} it will use '1' not 'Choice 1'. You need to explicitly use {{ my_form_choice_field.get_my_form_choice_field_display }} (in the UI layer) to get 'Choice 1'.

So, as I started with API's are generally used to fetch data, not it's UI representation. That is why (JUST LIKE DJANGO) DjangoRestFramework also uses only the original value and not its UI representation.

Now to answer your question, if you really want to return the UI representation. Then you need to override BaseSerializer's to_representation method to replace value with its UI representation and to override to_internal_value to replace UI representation back to value when saving.

You can see an example (from the docs) here.

Quoting the docs example code linked above here.

class HighScoreSerializer(serializers.BaseSerializer):
    def to_internal_value(self, data):
        score = data.get('score')
        player_name = data.get('player_name')

        # Perform the data validation.
        if not score:
            raise ValidationError({
                'score': 'This field is required.'
            })
        if not player_name:
            raise ValidationError({
                'player_name': 'This field is required.'
            })
        if len(player_name) > 10:
            raise ValidationError({
                'player_name': 'May not be more than 10 characters.'
            })

        # Return the validated values. This will be available as
        # the `.validated_data` property.
        return {
            'score': int(score),
            'player_name': player_name
        }

    def to_representation(self, obj):
        return {
            'score': obj.score,
            'player_name': obj.player_name
        }

    def create(self, validated_data):
        return HighScore.objects.create(**validated_data)

Upvotes: 4

Related Questions