DRF之序列化元件

Lea4ning發表於2024-04-22

【二】序列化元件

【1】序列化與反序列化的概念

  • 序列化(Serialization):將物件轉換為可傳輸或可儲存的格式的過程。在序列化過程中,物件的屬性和資料被轉換為一個位元組流或字串,以便在網路上傳輸或儲存到檔案中。常見的序列化格式包括 JSON、XML、Protocol Buffers 等。序列化後的資料可以在不同的系統、程式語言或應用程式之間進行交換和共享。

  • 反序列化(Deserialization):將序列化後的資料恢復為原始物件的過程。在反序列化過程中,從序列化格式(例如 JSON 字串)中解析出物件的屬性和資料,並重新構建原始物件。反序列化的過程與序列化過程相反,它將序列化後的資料轉換回原始物件,以便進行進一步的處理、操作或顯示。

  • 簡單來說:

    • 序列化:將物件轉換為可傳輸的格式

    • 反序列化:從 JSON 字串、XML 文件、位元組流等中解析出物件的屬性和資料。

【2】序列化類(Serializer)

【2.1】定義序列化類

# 定義序列化類需要繼承drf.Serializer
from rest_framework import serializers

'''簡單示例'''
class MySerializer(serializers.Serializer):
    欄位名 = serializers.欄位型別(欄位引數)
    
'''例項'''
class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)

    def create(self, validated_data):
        """
        根據提供的驗證過的資料建立並返回一個新的`Snippet`例項。
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        根據提供的驗證過的資料更新和返回一個已經存在的`Snippet`例項。
        """
        instance.title = validated_data.get('title', instance.title)
        return instance
  • 序列化器類的第一部分定義了序列化/反序列化的欄位。
  • create()update()方法定義了在呼叫serializer.save()時如何建立和修改完整的例項。

【2.2】常見欄位及引數

【2.2.1】常用欄位型別
欄位 欄位構造方式
BooleanField BooleanField()
NullBooleanField NullBooleanField()
CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正則欄位,驗證正則模式 [a-zA-Z0-9-]+
URLField URLField(max_length=200, min_length=None, allow_blank=False)
UUIDField UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose'"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex'"5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
IntegerField IntegerField(max_value=None, min_value=None)
FloatField FloatField(max_value=None, min_value=None)
DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位數 decimal_palces: 小數點位置
DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
DurationField DurationField()
ChoiceField ChoiceField(choices) choices與Django的用法相同
MultipleChoiceField MultipleChoiceField(choices)
FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)
【2.2.2】選項引數
引數名稱 作用
max_length 最大長度
min_lenght 最小長度
allow_blank 是否允許為空
trim_whitespace 是否截斷空白字元
max_value 最小值
min_value 最大值
【2.2.3】通用引數
引數名稱 說明
read_only 表明該欄位僅用於序列化輸出,預設False
write_only 表明該欄位僅用於反序列化輸入,預設False
required 表明該欄位在反序列化時必須輸入,預設True
default 反序列化時使用的預設值
allow_null 表明該欄位是否允許傳入None,預設False
validators 該欄位使用的驗證器
error_messages 包含錯誤編號與錯誤資訊的字典
label 用於HTML展示API頁面時,顯示的欄位名稱
help_text 用於HTML展示API頁面時,顯示的欄位幫助提示資訊

【3】序列化類的基本操作

  • 1 - Serialization - Django REST framework中文站點 (q1mi.github.io)

【3.1】序列化

# 匯入構建好的序列化類
from xx import Serializer
# 建立序列化類物件
ser = Serializer(instance=obj物件,data=request.data,...)
# 獲取序列化後的資料  # 以json格式返回
ser.data
【3.1.1】序列化物件的常用引數
  1. instance:要序列化的模型例項。如果需要對現有物件進行序列化,則傳遞該例項。
  2. data:要反序列化的資料。如果需要從資料中建立物件,則傳遞該資料。通常用於建立或更新物件。
  3. context:上下文資料,可以在序列化器的各個方法中使用。通常用於在序列化器之間傳遞額外的資訊。
  4. many:指定是否序列化多個物件。預設為 False。如果設定為 True,則可以序列化多個物件的查詢集。
  5. partial:指定是否部分更新物件。預設為 False。如果設定為 True,則可以部分更新物件而不需要提供所有欄位的值。

【3.2】反序列化校驗

  • 序列化器在反序列化時通常會執行一系列驗證操作,以確保輸入的資料符合預期的格式和約束。
  • 這些驗證功能可以幫助確保使用者提供的資料是有效的,並且可以在儲存到資料庫之前進行預處理。
【3.2.1】校驗級別
【3.2.1.1】欄位級別的驗證
class BookSerializer(serializers.Serializer):
    name = serializers.CharField(min_length=5, max_length=32, error_messages={
        'min_length': '最少不得少於5個字元',
        'max_length': '最多不得多於32個字元'
    })
    price = serializers.IntegerField(min_value=10, max_value=999, error_messages={
        'min_value': '最少不得少於10元',
        'max_value': '最多不得多於999元'
    })
    publish = serializers.CharField(min_length=3, max_length=255, error_messages={
        'min_length': '最少不得少於3個字元',
        'max_length': '最多不得多於255個字元'
    })
【3.2.1.2】自定製校驗器validators
  • 與forms元件一樣,可以指定validators引數自定製驗證器
def func(value):
    '''驗證資料格式'''
    if xxx:
        # 指定條件
        # 丟擲異常
        raise ValidationError("錯誤資訊")
    return value

class MySerializer(serializers.Serializer):
    # 可以指定多個驗證器
    name = serializers.CharField(max_length=32,validators=[func,func2...])
【3.2.1.3】鉤子函式
  • 區域性鉤子:validate_欄位(self,欄位)
  • 全域性鉤子:validate(self,attrs)
  • 校驗失敗 丟擲指定異常ValidationError
    • from rest_framework.exceptions import ValidationError
# 區域性鉤子
def validate_task_name(self, name: str):
    if 'sb' in name:
        # 丟擲指定異常
        raise ValidationError('不得包含侮辱性詞彙')
    # 注意需要返回該欄位
    return name

# 全域性鉤子
def validate(self, attrs):
    if attrs.get('name') == attrs.get('publish'):
        raise ValidationError('書名不得與出版社名一致')
    # 需要返回欄位
    return attrs
【3.2.2】校驗完成的資料 校驗失敗的錯誤資訊
  • ser.is_valid():返回布林值,True表示所有資料驗證透過,False表示有錯誤資料

    • 透過傳遞raise_exception=True引數開啟,REST framework接收到此異常,會向前端返回HTTP 400 Bad Request響應
  • ser.data:包含了序列化後欄位及其對應數值的字典,可以直接用於生成 JSON 或其他格式的響應資料

  • ser.errors:校驗失敗的錯誤提示資訊,由error_message引數指定或使用預設

# views.py
'''不需要攜帶引數的檢視類'''
class BookViewCR(APIView):
    def get(self, request):
        book_all = Book.objects.all()
        # 將需要序列化的資料給 【instance】引數  # 如果是多條 需要傳遞 【many】引數
        ser = BookSerializer(instance=book_all, many=True)
        # 返回 一個陣列【[{},{}]】  # 指定了many引數將視為多條資料 並按照restful規範返回陣列格式
        return Response({'code': 100,'msg': 'get','results': ser.data})

    def post(self, request):
        data = request.data
        # 新增資料  # 直接將資料傳入data引數中
        ser = BookSerializer(data=data)
        if ser.is_valid():
            # 校驗資料
            # 校驗透過  # 執行儲存方法
            ser.save()
            return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
        else:
            # 校驗失敗
            # 返回錯誤資訊
            return Response({'code': 101, 'msg': ser.errors})
      
'''需要攜帶 pk 引數 的檢視類'''
class BookViewRUD(APIView):
    # 查詢指定pk資料
    def get(self, request, tid):
        # 獲取物件
        book = Book.objects.filter(pk=tid).first()
        if not book:
            return Response({'code': 101, 'msg': '當前書籍不存在'})
        # 將查詢到的物件傳入instance
        ser = BookSerializer(instance=book)
        # 返回序列化後的資料
        return Response({'code': 100, 'msg': f'查詢指定【{tid}】成功', 'result': ser.data})
    
    # 修改指定資料
    def put(self, request, tid):
         # 獲取物件
        book = Book.objects.filter(pk=tid).first()
        if not book:
            return Response({'code': 101, 'msg': '當前書籍不存在'})
        # 將物件傳入instance  # 將需要更新的資料傳入data
        ser = BookSerializer(instance=book, data=request.data)
        if ser.is_valid():
            # 進行資料校驗
            # 資料校驗透過呼叫save方法儲存
            ser.save()
            return Response({'code': 100, 'msg': f'修改指定【{tid}】成功', 'result': ser.data})
        else:
            # 校驗失敗返回錯誤資訊
            return Response({'code': 101, 'msg': ser.errors, })

【3.2】反序列化儲存 create update

  • 序列化器判斷是新建物件還是更新物件的標準就是,是否傳遞了instance引數,資料將由data引數接受
class xxx(serializers.Serializer):
    # ... 
    def create(self, validated_data):
        '''可以對校驗過的資料進行再次處理'''
        # 新增資料
        book = Book.objects.create(**validated_data)
        '''
        title = validated_data.pop('title')
        title += '爆款'
        # 為標題加上爆款二字
        Book.objects.create(**validated_data,title=title)
        '''
        return book

    def update(self, instance, validated_data):
        # 由於instance是一個物件,沒辦法直接使用 【queryset.update(**kwargs)】方法
        # 所以需要使用反射的方法為物件更新屬性
        for key in validated_data.keys():
            setattr(instance, key, validated_data.get(key))
        instance.save()
        # 將物件返回
        return instance
【3.2.1】save(**kwargs)
  • 對序列化器進行save()儲存時,可以額外傳遞資料,這些資料可以在create()update()中的validated_data引數獲取到
# views.py
class MyView(APIView):
    def post(self,request):
        ser = Ser(data=reuqest.data)
        ser.is_valid(raise_exception=True)
        ser.save(other='xxx')
        
###############################################
# serializer.py
class Ser(serializer.Serializer):
    ...
    def create(self,validated_data):
        other = validated_data.pop('other')
        ...

【4】source引數

  • source 引數用於指定要序列化的欄位的源。通常,它用於在序列化器中訪問模型例項的屬性或方法,並將其值包含在序列化後的資料中。
  • 當你需要訪問模型例項的屬性或方法,並將其值序列化到響應中時,可以使用 source 引數來指定該屬性或方法的名稱。這個引數告訴序列化器從哪裡獲取資料以進行序列化。

【4.1】source引數跨表查詢

# models.py
class Books(models.Model):
    name = models.CharField(max_length=64)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    # 如果是外來鍵欄位  # 不使用source引數指定將會返回整個publish模型例項
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)

###############################################

# serializer.py
class Ser(serializers.Serializer):
    ...
    '''可以指定返回外來鍵欄位物件所對應的屬性值'''
    publish_name = serializers.CharField(source='publish.name')
    publish_addr = serializers.CharField(source='publish.addr')
    
    '''更改欄位序列化時顯示的名稱'''
    book_name = serializers.CharField(source='title')  # book_name的值就是模型表中的title欄位

【4.2】source引數指定執行模型表中的方法

'''指定執行模型表中的方法'''
# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)

    def name2desc(self):
        '''執行一些程式碼'''
        return self.name + self.desc
    
############################################
# ser.py
class TaskSer(serializers.Serializer):
    # 透過source引數指定模型表中方法  # 傳對應的函式名即可
    name_desc = serializers.CharField(source='name2desc')

【4.3】source引數指定顯示模型表中欄位名稱

# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)

############################################
# ser.py
class TaskSer(serializers.Serializer):
    # task_name將作為序列化返回給前端的json資料中展示name欄位的鍵
    task_name = serializers.CharField(source='name')

【4.4】總結

# ser.py

from rest_framework import serializers

class TaskSer(serializers.Serializer):
    # 模型表中有的欄位將會一一對應
    name = serializers.CharField()
    # 可以使用source引數自定義顯示給前端的json資料中的鍵
    task_name = serializers.CharField(source='name')
    # 模型表中的方法也可以使用同名的欄位名進行呼叫
    name2desc = serializers.CharField()
    # 也可以使用source引數執行模型表中的方法
    name_desc = serializers.CharField(source='name2desc')
    # 直接使用外來鍵欄位將回返回當前模型表物件對應的外來鍵物件
    user = serializers.CharField()
    # 可以使用source引數進行跨表查詢
    user_name = serializers.CharField(source='user.username')
    
    
###############################################
# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)
    user = models.ForeignKey(to='User', on_delete=models.CASCADE, default=1)

    def name2desc(self):
        '''執行一些程式碼'''
        return self.name + self.desc


class User(models.Model):
    username = models.CharField(max_length=32, default='aaa')

image-20240422153057758

【5】使用 SerializerMethodField 定製欄位

# 基本語法
欄位 = serializers.SerializerMethodField()
    def get_欄位(self,obj):
        '''此處的obj就是當前正在序列化的模型表物件'''
        return ...
class TaskSer(serializers.Serializer):
    # 模型表中有的欄位將會一一對應
    name = serializers.CharField()
    desc = serializers.CharField()
    # 可以使用source引數進行跨表查詢
    user_name = serializers.CharField(source='user.username')
    xxx = serializers.SerializerMethodField()

    def get_xxx(self, obj):
        '''
        此處的self為序列化類的物件,應當返回模型物件中的資料
        :param obj: 當前模型表物件
        :return: 想要展示的值
        '''
        return obj.desc + '這是描述喲'

image-20240422155215255

【6】使用 子序列化 定製欄位

  • 使用子序列化以實現序列化類的複用
class UserSer(serializers.Serializer):
    '''user表的序列化類'''
    username = serializers.CharField()


class TaskSer(serializers.Serializer):
    # 模型表中有的欄位將會一一對應
    name = serializers.CharField()
    desc = serializers.CharField()
    # 使用子序列化以實現序列化類的複用
    # 如果欄位名與模型表中的外來鍵欄位一致  # 當呼叫子序列化時,將會自動將該外來鍵物件傳入子序列化類中
    user = UserSer()
    # 可以使用source引數指定該欄位對應的外來鍵欄位  # 那麼子序列化類將會自動識別外來鍵物件
    user_obj = UserSer(source='user')

image-20240422160909664

  • 如果既不使用與外來鍵欄位同名,且不使用source引數指定,將會報錯

【7】ListField和DictField

  • ListField 用於序列化和反序列化列表型別的資料。
    • 當你序列化資料時,ListField 會將列表中的每個元素序列化為相應的格式,例如 JSON 陣列
    • 當你反序列化時,ListField欄位將可以接受陣列型別的資料
  • DictField 用於序列化和反序列化字典型別的資料。
    • 當你序列化資料時,DictField 會將字典中的鍵值對序列化為相應的格式,例如 JSON 物件。
    • 當你反序列化時,DictField欄位將可以接受字典型別的資料

【7.1】序列化使用場景

  • 在模型表中,定義某些方法,返回列表或字典格式
  • 【注】對於多對多欄位,不能直接使用ListField來進行序列化,可以使用
    • SerializerMethodField定義方法
    • 在模型表中定義方法返回列表
    • 使用子序列化並傳遞many=True
# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)
    user = models.ForeignKey(to='User', on_delete=models.CASCADE, default=1)

    def num_list(self):
        return [1, 2, 3]

    def info_dic(self):
        return {'id': 1, 'username': self.user.username}
##################################################

# ser.py
class TaskSer(serializers.Serializer):
    # 模型表中有的欄位將會一一對應
    name = serializers.CharField()
    desc = serializers.CharField()
    # 所有的模型表中的欄位型別都可以使用CharField強轉  # 但是此時該欄位就只是字串了
    l_str = serializers.CharField(source='num_list')
    # 使用source引數指定
    l = serializers.ListField(source='num_list')
    # 使用同名的欄位
    num_list = serializers.ListField()

    info_dic = serializers.DictField()
    dic = serializers.DictField(source='info_dic')

image-20240422162856445

  • 多對多外來鍵欄位,序列化演示
class TaskSer(serializers.Serializer):
    # 模型表中有的欄位將會一一對應
    name = serializers.CharField()
    desc = serializers.CharField()

    # # 多對多外來鍵欄位
    # user = serializers.ListField()  # 將會報錯
    # 使用 SerializerMethodField
    user_list = serializers.SerializerMethodField()

    def get_user_list(self, obj):
        return [user.username for user in obj.user.all()]

    # 使用子序列化
    user_list2 = UserSer(source='user', many=True)

【7.2】反序列化使用場景

  • 常用於多對多欄位
# models.py
class Task(models.Model):
    name = models.CharField(max_length=32)
    desc = models.CharField(max_length=32)
    user = models.ManyToManyField(to='User')


class User(models.Model):
    username = models.CharField(max_length=32, default='aaa')
    
#################################################

# ser.py
class TaskSer(serializers.Serializer):
    # 模型表中有的欄位將會一一對應
    name = serializers.CharField()
    desc = serializers.CharField()

    # 使用ListField接收列表資料  # write_only表示該欄位只進行反序列化  # 只用來接收前端的資料並儲存
    user = serializers.ListField(write_only=True)
    # user = serializers.CharField(write_only=True)  # "Not a valid string."  # 將會報錯

    # 使用DictField接收字典形式資料
    task_info = serializers.DictField(write_only=True)
    
    
    
    def create(self, validated_data):
        # 使用post建立物件時,必須重寫create方法
        # 否則會報錯
        print(validated_data)
        # {'name': 'task3', 'desc': 'task3-desc', 'user': [1, 2], 'task_info': {'time': 'xxxx', 'style': 'aaaa'}}
        return validated_data  # 正常需要返回建立號的物件  # 此處將會返回序列化類序列化的資料
    
#################################################    
# views.py
class TaskView(APIView):
    def get(self, request):
        ser = TaskSer(Task.objects.all(), many=True)
        return Response(ser.data)

    def post(self, request):
        ser = TaskSer(data=request.data)
        # 反序列化校驗
        ser.is_valid(raise_exception=True)
        # 呼叫create方法
        ser.save()
        return Response(ser.data if ser.is_valid() else ser.errors)

image-20240422165811807

【8】read_onlywrite_only

  • 當我們希望某些欄位只執行序列化,也就是返回給前端時,可以使用read_only
  • 當我們希望某些欄位只執行反序列化,也就是要求前端傳值交由我們儲存,可以使用write_only
  • 預設不填代表著,該欄位既做序列化又做反序列化
class TaskSer(serializers.Serializer):
    # 模型表中有的欄位將會一一對應
    name = serializers.CharField()
    desc = serializers.CharField()

    # 只做序列化,將使用者資訊展示給前端
    user = UserSer(many=True, read_only=True)

    # 只做反序列化,需要使用者傳值
    user_l = serializers.ListField(write_only=True)
    task_info = serializers.DictField(write_only=True)

image-20240422170618201

image-20240422170624778

【補充】其他知識

【1】欄位引數validators

  • 在欄位中新增validators選項引數,可以補充驗證行為,如
def about_django(value):
    if 'django' not in value.lower():
        raise serializers.ValidationError("圖書不是關於Django的")

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    title = serializers.CharField(label='名稱', max_length=20, validators=[about_django])
    pub_date = serializers.DateField(label='釋出日期', required=False)
    read = serializers.IntegerField(label='閱讀量', required=False)
    comment = serializers.IntegerField(label='評論量', required=False)
    image = serializers.ImageField(label='圖片', required=False)

【2】反序列化校驗的三層校驗

  1. 執行欄位內部的校驗
    • max_length / min_length 等
    • validators :是一個列表,可以傳多個校驗函式,將會依次執行列表中的所有校驗函式
  2. 執行區域性鉤子:validate_欄位
  3. 執行全域性鉤子:validate

【3】無法直接使用source引數反向查詢

  • 直接使用 source 引數是無法實現複雜的反向查詢的。source 引數通常用於簡單的欄位訪問,例如直接從模型例項的屬性中獲取資料。
  • 如果需要進行復雜的反向查詢,例如從關聯模型中獲取相關物件列表,需要使用 SerializerMethodField 並定義一個方法來實現

【9】模型類序列化類(ModelSerializer)

  • DRF為我們提供了ModelSerializer模型類序列化器來幫助我們快速建立一個Serializer類。
  • ModelSerializer與常規的Serializer相同,但提供了:
    • 基於模型類自動生成一系列欄位
    • 基於模型類自動為Serializer生成validators,比如unique_together
    • 包含預設的create()和update()的實現
from rest_framework.serializers import ModelSerializer

class XXX(ModelSerializer):
     # 不在模型表中的欄位
    欄位 = serializers.CharField()
    class Meta:
        # 如果是模型表中的欄位  # 可以在Meta中的fields引數中指定即可
        model=對應的表
        # 模型表中的欄位將於序列化類的欄位型別一一對應
        fields = 需要使用的欄位
        #### 【注】此處的fields需要注意,不僅僅要填寫在模型表中的欄位,還需要填寫額外的欄位 ####
        extra_kwargs = {
            '欄位':{'額外新增的引數'}
        }
   
  • 【注】fields是所有使用到的欄位
from .models import Task


class TaskSerV2(serializers.ModelSerializer):
    # 只做反序列化,需要使用者傳值
    user_l = serializers.ListField(write_only=True)
    task_info = serializers.DictField(write_only=True)
    aaa = serializers.CharField(source='name')

    class Meta:
        model = Task
        fields = '__all__'  # __all__ 表示模型表中的所有欄位
        # fields = ['id','name','aaa','desc']  # 相當於
        extra_kwargs = {
            'name': {'max_length': 10}
        }
        
    '''如果所需欄位就是表中需要的,就不需要重寫create方法了'''
    def create(self, validated_data):
        # 由於Task表中並不需要下面兩個欄位,所以去除掉
        validated_data.pop('user_l')
        validated_data.pop('task_info')
        # 呼叫ModelSerializer中的create方法
        task = super().create(validated_data)
        return task

相關文章