前言
當我們需要校驗選項字段的時候,需用到 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