python测试开发django-rest-framework-91.反序列化(ModelSerializer)之ChoiceField选项字段校验

前言

当我们需要校验选项字段的时候,需用到 ChoiceField 来校验

选项

在 model 模型里面有个字段是选项字段, goods_status 可以有2种状态,0是下架,1是出售中,默认

class Goods(models.Model):
    """商品表"""

    goods_status = models.IntegerField(choices=(
                                                (0, '下架'),
                                                (1, '出售中')
                                               ),
                                       default=1,
                                       verbose_name="0下架 1出售中")

当我们查询的时候,goods_status 显示的是0 和 1

我们想让它显示 下架 和出售中,这样看起来更友好

序列化

在序列化类里面使用get_<字段名>_display的方法,该方法获得choice字段对应的数据 下架和出售中.
这里涉及到一个很有用的实例方法: get_<Field name>_display 对于模型中含有choices参数的字段, <Field name> 是字段的名字, get_FOO_display() 返回选项的可读字符串

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品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)
    
    # chioce字段 get_<字段名>_display 显示名称
    goods_status = serializers.CharField(source='get_goods_status_display',
                                         required=False)
    # 必传字段
    goods_code = serializers.CharField(required=True,
                                       max_length=15,
                                       min_length=8,
                                       validators=[validators.UniqueValidator(queryset=Goods.objects.all(),
                                                                              message="goods_code 已存在")]
                                       )


    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段

序列化输出的时候,就可以显示出售中

当使用了source='get_goods_status_display' 后,这里goods_status字段就默认被设置为只读字段了,如果 post 要提交 create() 或者修改这个字段,就会报错

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:python36libsite-packages
est_frameworkserializers.py", line 932, in create
    instance = ModelClass._default_manager.create(**validated_data)
  File "E:python36libsite-packagesdjangodbmodelsmanager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "E:python36libsite-packagesdjangodbmodelsquery.py", line 415, in create
    obj = self.model(**kwargs)
  File "E:python36libsite-packagesdjangodbmodelsase.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

也可以单独写一个读取choice字段的方法, get_<字段名称>自定义输出内容

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品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)


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


    def get_goods_status(self,obj):
        """get_<字段名称> 重写goods_status"""
        return obj.get_goods_status_display()


    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段

这样写在提交的时候带上goods_status不会报错了,但不会存到数据库中(相当于忽略这个字段的校验了),达不到我们的期望结果。

to_representation 使用

接下来我们希望提交数据的时候,还是用原来的数字0和1提交,读出来的时候显示对应的名称
重写 ModelSerializer 类里面的 to_representation 方法,自定义序列化数据的返回,此时需去掉上面的

# chioce字段 get_<字段名>_display 显示名称
    goods_status = serializers.CharField(source='get_goods_status_display',
                                         required=False)

重写 ModelSerializer 类里面的 to_representation 方法

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品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)
    
    # 必传字段
    goods_code = serializers.CharField(required=True,
                                       max_length=15,
                                       min_length=8,
                                       validators=[validators.UniqueValidator(queryset=Goods.objects.all(),
                                                                              message="goods_code 已存在")]
                                       )
    def to_representation(self, instance):
        """to_representation自定义序列化数据的返回"""
        data = super().to_representation(instance)
        data.update(goods_status=instance.get_goods_status_display())
        return data

    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段

此时传状态对应的数字,返回查询的结果就是显示名称

ChoiceField 选项字段

ChoiceField 专门用来处理有choices选项的问题,处理起来更高级一点,比如数据库里面有多种状态,但是状态2不希望用户去操作,只让用户添加0和1两种状态

    goods_status = models.IntegerField(choices=(
                                                (0, '下架'),
                                                (1, '出售中'),
                                                (2, '黑名单')
                                               ),
                                       default=1,
                                       verbose_name="0下架 1出售中")

于是可以用到ChoiceField, 必须传choices 参数选项

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品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_<字段名>_display

    goods_status = serializers.ChoiceField(choices=(
                                                (0, '下架'),
                                                (1, '出售中')
                                               ),
                                           required=False)

    def to_representation(self, instance):
        """to_representation自定义序列化数据的返回"""
        data = super().to_representation(instance)
        data.update(goods_status=instance.get_goods_status_display())
        return data

    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段

实现效果跟上面的一样,功能多了一个限制,只能传数字0和1两种状态,返回的时候显示状态名称

重写 ChoiceField

如果我们在添加的时候,既可以添加0和1这2个状态,也可以提交"下架" 和 "出售中" 这2种名称,查询的时候显示名称。
对提交的数据,反序列化处理,需重写 ChoiceField 方法

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


class ChoiceField(serializers.ChoiceField):
    """重写ChoiceField"""

    def to_representation(self, obj):
        """返回状态名称"""
        if obj == '' and self.allow_blank:
            return obj
        return self._choices[obj]

    def to_internal_value(self, data):
        """支持choice的key 或value名称的写入"""
        for i in self._choices:
            # 这样无论用户POST上来但是CHOICES的 Key 还是Value 都能被接受
            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):
    """序列化商品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)

    # 直接用上面重写的ChoiceField
    goods_status = ChoiceField(choices=(
                                        (0, '下架'),
                                        (1, '出售中')
                                       ),
                                required=False)
    # 必传字段
    goods_code = serializers.CharField(required=True,
                                       max_length=15,
                                       min_length=8,
                                       validators=[validators.UniqueValidator(queryset=Goods.objects.all(),
                                                                              message="goods_code 已存在")]
                                       )
    goods_stock = serializers.IntegerField(required=True,
                                           min_value=1,
                                           max_value=10000)

    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段


传状态名称可以支持

传数字也支持

如果只想接收用户传状态名称,可以重写 ChoiceField 的 to_internal_value 方法

    def to_internal_value(self, data):
        """支持choice的value名称的写入"""

        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)

关于choicefield 相关的用法可以参考https://stackoverflow.com/questions/28945327/django-rest-framework-with-choicefield

原文地址:https://www.cnblogs.com/yoyoketang/p/14347488.html