【二】序列化元件
【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】序列化物件的常用引數
- instance:要序列化的模型例項。如果需要對現有物件進行序列化,則傳遞該例項。
- data:要反序列化的資料。如果需要從資料中建立物件,則傳遞該資料。通常用於建立或更新物件。
- context:上下文資料,可以在序列化器的各個方法中使用。通常用於在序列化器之間傳遞額外的資訊。
- many:指定是否序列化多個物件。預設為 False。如果設定為 True,則可以序列化多個物件的查詢集。
- 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')
【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 + '這是描述喲'
【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')
- 如果既不使用與外來鍵欄位同名,且不使用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')
- 多對多外來鍵欄位,序列化演示
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)
【8】read_only
和write_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)
【補充】其他知識
【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】反序列化校驗的三層校驗
- 執行欄位內部的校驗
- max_length / min_length 等
- validators :是一個列表,可以傳多個校驗函式,將會依次執行列表中的所有校驗函式
- 執行區域性鉤子:
validate_欄位
- 執行全域性鉤子:
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