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:\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
也可以单独写一个读取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