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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM