Python test development django-rest-framework-91. Deserialization (ModelSerializer) ChoiceField option field verification…

Foreword

When we need to verify the option field, we need to use ChoiceField to verify

options

There is a field in the model model that is an option field. goods_status can have two statuses, 0 means off the shelf, 1 means on sale, the default

class Goods(models.Model):
    """Product list"""

    goods_status = models.IntegerField(choices=(
                                                (0, 'Removed'),
                                                (1, 'For sale')
                                               ),
                                       default=1,
                                       verbose_name="0 off the shelves 1 on sale")

When we query, goods_status displays 0 and 1

We want it to show Discontinued and For Sale so it looks more friendly

Serialization

Use the get__display method in the serialization class. This method obtains the data corresponding to the choice field. It is being removed and sold.
This involves a very useful instance method: get__display For fields in the model that contain the choices parameter, is the name of the field, get_FOO_display() returns a human-readable string of the option

# Author-Shanghai Youyou QQ communication group: 717225969
# blog address https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """Serialized product models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)
    
    #choce field get_<field name>_display display name
    goods_status = serializers.CharField(source='get_goods_status_display',
                                         required=False)
    #Required fields
    goods_code = serializers.CharField(required=True,
                                       max_length=15,
                                       min_length=8,
                                       validators=[validators.UniqueValidator(queryset=Goods.objects.all(),
                                                                              message="goods_code already exists")]
                                       )


    class Meta:
        model = Goods
        fields = '__all__' # Return all fields

When serializing the output, it can display On sale

When source=’get_goods_status_display’ is used, the goods_status field here is set to a read-only field by default. If the post wants to submit create() or modify this field, an error will be reported.

TypeError at /api/v1/goods/
Got a `TypeError` when calling `Goods.objects.create()`.
This may be because you have a writable field on the serializer class that is not a valid argument to `Goods.objects.create()`.
You may need to make the field read-only, or override the GoodsSerializer.create() method to handle this correctly.
Original exception was:
 Traceback (most recent call last):
  File "E:\python36\lib\site-packages\rest_framework\serializers.py", line 932, in create
    instance = ModelClass._default_manager.create(**validated_data)
  File "E:\python36\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "E:\python36\lib\site-packages\django\db\models\query.py", line 415, in create
    obj = self.model(**kwargs)
  File "E:\python36\lib\site-packages\django\db\models\base.py", line 495, in __init__
    raise TypeError("'%s' is an invalid keyword argument for this function" % kwarg)
TypeError: 'get_goods_status_display' is an invalid keyword argument for this function

You can also write a separate method to read the choice field, get_ to customize the output content

# Author-Shanghai Youyou QQ communication group: 717225969
# blog address https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """Serialized product models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)


    # Set SerializerMethodField
    goods_status = serializers.SerializerMethodField(read_only=False, write_only=False)


    def get_goods_status(self,obj):
        """get_<field name> override goods_status"""
        return obj.get_goods_status_display()


    class Meta:
        model = Goods
        fields = '__all__' # Return all fields

In this way, if you bring goods_status when submitting, you will not get an error, but it will not be saved in the database (which is equivalent to ignoring the verification of this field), which cannot achieve our expected results.

to_representation use

Next, we hope that when submitting data, we still use the original numbers 0 and 1 to submit, and the corresponding names will be displayed when reading them out.
Rewrite the to_representation method in the ModelSerializer class to customize the return of serialized data. At this time, you need to remove the above

# chicoce field get_<field name>_display display name
    goods_status = serializers.CharField(source='get_goods_status_display',
                                         required=False)

Override the to_representation method in the ModelSerializer class

# Author-Shanghai Youyou QQ communication group: 717225969
# blog address https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """Serialized product models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)
    
    #Required fields
    goods_code = serializers.CharField(required=True,
                                       max_length=15,
                                       min_length=8,
                                       validators=[validators.UniqueValidator(queryset=Goods.objects.all(),
                                                                              message="goods_code already exists")]
                                       )
    def to_representation(self, instance):
        """return of to_representation custom serialized data"""
        data = super().to_representation(instance)
        data.update(goods_status=instance.get_goods_status_display())
        return data

    class Meta:
        model = Goods
        fields = '__all__' # Return all fields

At this time, the number corresponding to the status is passed, and the returned query result is the display name

ChoiceField option field

ChoiceField is specially used to deal with problems with choices options. It is more advanced to handle. For example, there are multiple states in the database, but state 2 does not want users to operate, and only allows users to add two states: 0 and 1.

goods_status = models.IntegerField(choices=(
                                                (0, 'Removed'),
                                                (1, 'for sale'),
                                                (2, 'Blacklist')
                                               ),
                                       default=1,
                                       verbose_name="0 off the shelves 1 on sale")

So ChoiceField can be used, and the choices parameter options must be passed.

# Author-Shanghai Youyou QQ communication group: 717225969
# blog address https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """Serialized product models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)
    # get_<field name>_display

    goods_status = serializers.ChoiceField(choices=(
                                                (0, 'Removed'),
                                                (1, 'For sale')
                                               ),
                                           required=False)

    def to_representation(self, instance):
        """return of to_representation custom serialized data"""
        data = super().to_representation(instance)
        data.update(goods_status=instance.get_goods_status_display())
        return data

    class Meta:
        model = Goods
        fields = '__all__' # Return all fields

The implementation effect is the same as above, but there is an additional limitation in the function. Only the numbers 0 and 1 can be passed in two states, and the state name is displayed when returning.

Rewrite ChoiceField

If we are adding, we can add two statuses of 0 and 1, or we can submit two names of “off the shelf” and “on sale”, and the names will be displayed when querying.
To deserialize the submitted data, you need to override the ChoiceField method.

# Author-Shanghai Youyou QQ communication group: 717225969
# blog address https://www.cnblogs.com/yoyoketang/


class ChoiceField(serializers.ChoiceField):
    """Override ChoiceField"""

    def to_representation(self, obj):
        """Return status name"""
        if obj == '' and self.allow_blank:
            return obj
        return self._choices[obj]

    def to_internal_value(self, data):
        """Supports writing the key or value name of choice"""
        for i in self._choices:
            # In this way, no matter the user POSTs up, the Key or Value of CHOICES can be accepted.
            if i == data or self._choices[i] == data:
                return i
        raise serializers.ValidationError("Acceptable values are {0}.".format(list(self._choices.values())))


class GoodsSerializer(serializers.ModelSerializer):
    """Serialized product models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', required=False)

    # Directly use the ChoiceField rewritten above
    goods_status = ChoiceField(choices=(
                                        (0, 'Removed'),
                                        (1, 'For sale')
                                       ),
                                required=False)
    #Required fields
    goods_code = serializers.CharField(required=True,
                                       max_length=15,
                                       min_length=8,
                                       validators=[validators.UniqueValidator(queryset=Goods.objects.all(),
                                                                              message="goods_code already exists")]
                                       )
    goods_stock = serializers.IntegerField(required=True,
                                           min_value=1,
                                           max_value=10000)

    class Meta:
        model = Goods
        fields = '__all__' # Return all fields

Passing the status name can support

Passing numbers also supports

If you only want to receive the status name from the user, you can override the to_internal_value method of ChoiceField

def to_internal_value(self, data):
        """Supports writing the value name of choice"""

        if data == '' and self.allow_blank:
            return ''
        
        for key, val in self._choices.items():
            if val == data:
                return key
        self.fail('invalid_choice', input=data)

For related usage of choicefield, please refer to https://stackoverflow.com/questions/28945327/django-rest-framework-with-choicefield