• 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

  • 相关阅读:
    《你必须知道的495个C语言问题》读书笔记之第15-20章:浮点数、风格、杂项
    《你必须知道的495个C语言问题》读书笔记之第8-10章:字符串、布尔类型和预处理器
    《你必须知道的495个C语言问题》读书笔记之第4-7章:指针
    《你必须知道的495个C语言问题》读书笔记之第3章:表达式
    《你必须知道的495个C语言问题》读书笔记之第1-2章:声明和初始化
    bzoj4361 isn(树状数组优化dp+容斥)
    bzoj4665 小w的喜糖(dp+容斥)
    P4859 已经没有什么好害怕的了(dp+二项式反演)
    bzoj4710 [Jsoi2011]分特产(容斥)
    bzoj2839 集合计数(容斥)
  • 原文地址:https://www.cnblogs.com/yoyoketang/p/14347488.html
Copyright © 2020-2023  润新知