Django Rest Framework with ChoiceField

前端 未结 8 1909
轮回少年
轮回少年 2020-12-02 06:01

I have a few fields in my user model that are choice fields and am trying to figure out how to best implement that into Django Rest Framework.

Below is some simplifi

相关标签:
8条回答
  • 2020-12-02 06:12

    I prefer the answer by @nicolaspanel to keep the field writeable. If you use this definition instead of his ChoiceField, you take advantage of any/all of the infrastructure in the built-in ChoiceField while mapping the choices from str => int:

    class MappedChoiceField(serializers.ChoiceField):
    
        @serializers.ChoiceField.choices.setter
        def choices(self, choices):
            self.grouped_choices = fields.to_choices_dict(choices)
            self._choices = fields.flatten_choices_dict(self.grouped_choices)
            # in py2 use `iteritems` or `six.iteritems`
            self.choice_strings_to_values = {v: k for k, v in self._choices.items()}
    

    The @property override is "ugly" but my goal is always to change as little of the core as possible (to maximize forward compatibility).

    P.S. if you want to allow_blank, there's a bug in DRF. The simplest workaround is to add the following to MappedChoiceField:

    def validate_empty_values(self, data):
        if data == '':
            if self.allow_blank:
                return (True, None)
        # for py2 make the super() explicit
        return super().validate_empty_values(data)
    

    P.P.S. If you have a bunch of choice fields that all need to be mapped this, way take advantage of the feature noted by @lechup and add the following to your ModelSerializer (not its Meta):

    serializer_choice_field = MappedChoiceField
    
    0 讨论(0)
  • 2020-12-02 06:13

    Since DRF 3.1 there is new API called customizing field mapping. I used it to change default ChoiceField mapping to ChoiceDisplayField:

    import six
    from rest_framework.fields import ChoiceField
    
    
    class ChoiceDisplayField(ChoiceField):
        def __init__(self, *args, **kwargs):
            super(ChoiceDisplayField, self).__init__(*args, **kwargs)
            self.choice_strings_to_display = {
                six.text_type(key): value for key, value in self.choices.items()
            }
    
        def to_representation(self, value):
            if value is None:
                return value
            return {
                'value': self.choice_strings_to_values.get(six.text_type(value), value),
                'display': self.choice_strings_to_display.get(six.text_type(value), value),
            }
    
    class DefaultModelSerializer(serializers.ModelSerializer):
        serializer_choice_field = ChoiceDisplayField
    

    If You use DefaultModelSerializer:

    class UserSerializer(DefaultModelSerializer):    
        class Meta:
            model = User
            fields = ('id', 'gender')
    

    You will get something like:

    ...
    
    "id": 1,
    "gender": {
        "display": "Male",
        "value": "M"
    },
    ...
    
    0 讨论(0)
  • 2020-12-02 06:16

    The following solution works with any field with choices, with no need to specify in the serializer a custom method for each:

    from rest_framework import serializers
    
    class ChoicesSerializerField(serializers.SerializerMethodField):
        """
        A read-only field that return the representation of a model field with choices.
        """
    
        def to_representation(self, value):
            # sample: 'get_XXXX_display'
            method_name = 'get_{field_name}_display'.format(field_name=self.field_name)
            # retrieve instance method
            method = getattr(value, method_name)
            # finally use instance method to return result of get_XXXX_display()
            return method()
    

    Example:

    given:

    class Person(models.Model):
        ...
        GENDER_CHOICES = (
            ('M', 'Male'),
            ('F', 'Female'),
        )
        gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
    

    use:

    class PersonSerializer(serializers.ModelSerializer):
        ...
        gender = ChoicesSerializerField()
    

    to receive:

    {
        ...
        'gender': 'Male'
    }
    

    instead of:

    {
        ...
        'gender': 'M'
    }
    
    0 讨论(0)
  • 2020-12-02 06:17

    Probalbly you need something like this somewhere in your util.py and import in whichever serializers ChoiceFields are involved.

    class ChoicesField(serializers.Field):
        """Custom ChoiceField serializer field."""
    
        def __init__(self, choices, **kwargs):
            """init."""
            self._choices = OrderedDict(choices)
            super(ChoicesField, self).__init__(**kwargs)
    
        def to_representation(self, obj):
            """Used while retrieving value for the field."""
            return self._choices[obj]
    
        def to_internal_value(self, data):
            """Used while storing value for the field."""
            for i in self._choices:
                if self._choices[i] == data:
                    return i
            raise serializers.ValidationError("Acceptable values are {0}.".format(list(self._choices.values())))
    
    0 讨论(0)
  • 2020-12-02 06:18

    I found soup boy's approach to be the best. Though I'd suggest to inherit from serializers.ChoiceField rather than serializers.Field. This way you only need to override to_representation method and the rest works like a regular ChoiceField.

    class DisplayChoiceField(serializers.ChoiceField):
    
        def __init__(self, *args, **kwargs):
            choices = kwargs.get('choices')
            self._choices = OrderedDict(choices)
            super(DisplayChoiceField, self).__init__(*args, **kwargs)
    
        def to_representation(self, obj):
            """Used while retrieving value for the field."""
            return self._choices[obj]
    
    0 讨论(0)
  • 2020-12-02 06:32

    Django provides the Model.get_FOO_display method to get the "human-readable" value of a field:

    class UserSerializer(serializers.ModelSerializer):
        gender = serializers.SerializerMethodField()
    
        class Meta:
            model = User
    
        def get_gender(self,obj):
            return obj.get_gender_display()
    

    for the latest DRF (3.6.3) - easiest method is:

    gender = serializers.CharField(source='get_gender_display')
    
    0 讨论(0)
提交回复
热议问题