Rest_framework Serializer 序列化
序列化與反序列化中不得不說的感情糾葛
Yo!Yo! 為什么說到感情呢?因為在restframe_work中,序列化與反序列化中有太多令人感到糾結,易亂,不好分清界限的地方。就像感情一樣。
博文圖片掛了臨時解決辦法
三角戀之 save/update/create
對於普通序列化類,作用的是普通python 對象,一般不需要反序列化得到一個對象,我們只需要得到一個序列化后的數據字典,然后自己再來根據python普通對象的類進行實例化出對象。所以一般序列化類是用不到save和update還有create方法的。
然而, 對於特殊的序列化類,ModelSerializer/ListSerializer都會提供save/update/create三種方法,目的是直接通過使用反序列化得到的validated_data進行創建一個新對象或者更新一個已有對象。
-
根據實例化序列化類時,是否提供instance參數,來判定在save調用時,是進行update還是create,一般instance參數是默認None,所以默認調用save就是調用create方法,而create方法是需要serializer類實現的,從而使用序列化數據創建一個想要的對象來。
-
如果提供了instance, 那么save會調用的時update方法,進行更新操作。這里的更新操作只會當前對象的,且支持全量更新和部分更新。因為時依據validated_data中有的屬性進行更新。如果在序列化時提供了partail=True,那么在validated_data就是部分數據,就是部分更新。
-
save源代碼,從源代碼可以看出,無論序列化類目的是序列化還是反序列化,只要提供了instance,都會影響到save操作:
四角戀之 序列化參數instance/data/many/partial
以下描述來自BaseSerializer源碼注釋
In particular,
if a `data=` argument is passed then:
.is_valid() - Available.
.initial_data - Available.
.validated_data - Only available after calling `is_valid()`
.errors - Only available after calling `is_valid()`
.data - Only available after calling `is_valid()`
If a `data=` argument is not passed then:
.is_valid() - Not available.
.initial_data - Not available.
.validated_data - Not available.
.errors - Not available.
.data - Available.
instance/data/many/partial 影響序列化對象行為的四個關鍵參數。
- 如果沒有data參數,只有instance,那么就不存在反序列化校驗一說,只有序列化對象instance。
- 如果有data,沒有instance,那么需要進行校驗data,然后將data進行反序列化,得到validated_data,此時再通過序列化對象獲取data,這個data和初始化提供的data可不一樣,這個序列化validated_data后的data,比起初始化data,可能減少了無效的字段(序列化沒有定義的字段)。
- 如果又提供了instance 又提供了data, 那么只要有data或者部分data,那么data都要進行驗證才能進行下面的save等操作,如果不經過is_valid過程,那么后面的獲取序列化數據或者反序列化數據都會無效。
- many參數將直接影響序列化類的類型,如果是many=False,那么直接使用當前序列化類。如果many=True,將實例化一個ListSerializer類來序列化或者反序列化類。(這也是看源碼是漏掉的地方,一直奇怪普通Serialiszer類怎么沒看到對多對象序列化的特殊處理。查看BaseSerializer.__new__方法)或者class Meta:中定義了list_serializer_class指定的多對象序列化類。
- 終於弄懂了,partial用於部分更新,為啥子要伴隨instance,因為要指明給save用,在save操作時給那個instance部分更新。邏輯這回走到下面源碼中的get_initial()獲取要進行更新instance的字段數據。
@property
def data(self):
if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
msg = (
'When a serializer is passed a `data` keyword argument you '
'must call `.is_valid()` before attempting to access the '
'serialized `.data` representation.\n'
'You should either call `.is_valid()` first, '
'or access `.initial_data` instead.'
)
raise AssertionError(msg)
if not hasattr(self, '_data'):
if self.instance is not None and not getattr(self, '_errors', None):
self._data = self.to_representation(self.instance)
elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
self._data = self.to_representation(self.validated_data)
else:
self._data = self.get_initial()
return self._data
小結: instance影響save行為;data是影響后續的所有行為,有data必須進行校驗動作(.is_valid)。
三角戀之 初始化參數data和序列化對象的data屬性,validated_data
前兩者也是比較迷惑人的。都叫data,前者是Serializer(data={'a':1,'b':2}) ,后者是序列化后的數據。后者是前者依靠序列化定義的字段過濾校驗后的。
而validated_data這是反序列化的目的對象的數據。
兩個many=True
- BaseSerializer(many=True) 和 RelatedField(many=True)
- 一個是將對象列表進行序列化;一個是將關聯字段對了的queryset進行序列化。都會序列化返回一個列表。
- 前者會變為一個ListSerializer類;后者將是一個ManyRelatedField類。
- 兩者的child屬性都是源類。
- 兩者其實非常相似。
多對象的序列化與反序列化
首先要知道,由於序列化主要對應的是查詢動作,如list,retrieve兩個。而destrory動作不涉及序列化類。update和partial_update,create動作則涉及反序列化。明確這些后,看下面:
所有的序列化類都繼承自BaseSerializer類,這個類重寫了__new__ 方法,會根據實例化時的參數many的布爾值。默認是False,則使用當前類作為單對象序列化的類。如果是True,那么__new__會返回一個ListSerializer類對象作為序列化對象,與此同時這個對象的一個child屬性將會存放當前這個類的一個實例為后續提供多對象中單個對象的處理功能。
源碼:
多對象序列化的不同之處
- 在序列化時,提供的對象列表或者數據列表,many=True要顯示設置
- 序列化得到的serializer.data 是一個嵌套字典的列表
- 反序列化的validated_data 是一個嵌套字典的列表
- 使用自定義的序列化類
class CustomSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = CustomListSerializer # 使用自定義的序列化類
- 自定義多對象的update行為,官網例子:
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
# Maps for id->instance and id->data item.
book_mapping = {book.id: book for book in instance}
data_mapping = {item['id']: item for item in validated_data}
# Perform creations and updates.
ret = []
for book_id, data in data_mapping.items():
book = book_mapping.get(book_id, None)
if book is None:
ret.append(self.child.create(data))
else:
ret.append(self.child.update(book, data))
# Perform deletions.
for book_id, book in book_mapping.items():
if book_id not in data_mapping:
book.delete()
return ret
class BookSerializer(serializers.Serializer):
# We need to identify elements in the list using their primary key,
# so use a writable field here, rather than the default which would be read-only.
id = serializers.IntegerField()
...
class Meta:
list_serializer_class = BookListSerializer
多對象的反序列化只支持create操作,不支持更新update和部分更新partial_update,因為更新操作需要提供說要更新的目的對象,而要獲得更新操作的目的對象,(一般是一個model對象)所以需要pk,然而我們的pk都是放在url中進行存放,通過lookup_url_kwarg來獲取pk,不肯能將多個對象pk放入url中,所以更新操作是不支持批量反序列化。只有通過上面提到的自定義更新。
嵌套子序列化
如果對象的屬性還是一個對象,這時候的對象序列化就要用到子序列化了。
- 即使是BaseSerializer也是繼承之Field類的,所以Serializer類也可以作為一另一個序列化類的字段的序列化對象。
序列化源碼解析
部分源碼解析
很多時候不知道怎么讓serializer對象和 instance 匹配序列化上的,只有通過看源碼了。里面封裝了來達到序列化和反序列化的方法。如 以下源碼截圖,就是一步一步得到的.
也可以通過源碼看出instance參數和data參數的關系
- 獲取序列化數據
- 根據instance參數決定序列化對象是instance還是valid_data(序列化檢驗不會報錯才進入流程)
- 這里走序列化instance路線
- 獲取可讀的field對象,不同類型的field類對象可讀性已實際而定,需查看每個類的源碼或者官方文檔說明。
- 所有定義個fields的來源,和存放fields的一個BindingDict
7. 定義的所有fields中包含的屬性名與對應field對象映射的由來
8. 來源於元類
9. 所有field類的父類,定義初始化時都是默認可讀可寫
-
回到序列化函數to_representation,看到序列化instances時,依靠循環所有序列化類定義的字段,在帶入instance
-
如果實例化字段對象時,帶入了source參數,那么將通過source定義的方式拿取數據,instance中的數據。
-
從實例獲取對應的source的值,這里算法是一個屬性一個屬性的循環獲取,對於函數直接調用獲取值。
-
source_attrs實在bind時發生的
-
最后,從source或者額外的method
最后就到了每個種Field類自己的序列化函數to_presentation()了,最后返回序列化結果 -
對於ModelSerializer 繼承了Serializer, 但是其fields定義的字段的字典{‘fieldname': field_object} ,是通過重寫了get_fields()方法。重寫的方法邏輯如下截圖:
各種類型數據對象對應的Field對象
FIeld對象抽象出來的共同特征
或者說是,他們所展現出來的多態性,只不過各自的內部邏輯實現根據各自的特征不同而已。
- get_attribute(self, instance) 獲取instance符合當前field代表的字段類型的屬性值。如可以來源於field指定的source,或者通過方法從instance中獲取,又或者是model對象的一個關聯字段獲取pk值等等。
- to_representation(attribute) 將字段獲取到的需要序列化的value進行序列化。可以查看上面源碼分析過程理解。
- to_internal_value() 這個是反序列化,可以另行查看源碼。
只要大體了解,那么我們既可以對各種需要序列化的對象進行大致分類。雖然這些對象都千差萬別,但是最終都有會根據其不同目的,實現了滿足對應需求的序列化方式。這種設計模式值得思考和借鑒,能夠將那么多的數據類型並且這么細化的分類實現,分類解耦,應對各種序列化需求,能夠清晰做出選擇,幾乎不用重造輪子。
不同的獲取數據方式,不同的序列化方式,可讀寫的選擇,等等,為了滿足需求,有太多的已實現的字段類可供選擇。只要我們清楚以下幾種類型他們實例化或者使用上的區別就可以了。
這里只看分類,具體參見 官檔:
- https://www.django-rest-framework.org/api-guide/serializers/
- https://www.django-rest-framework.org/api-guide/fields/
- https://www.django-rest-framework.org/api-guide/relations/
第一類普同字段
int/string等。
第二類choice類,數字標號有映射的值
ChoiceField/MultipleChoiceField
第三類文件對象
FileField/ImageField
第四類復合數據類,列表/字典/,需要再指定里面元素的值的類型
ListField/DictField/HStoreField/JSONField
第五類方法類型
SerializerMethodField
第六類針對model關系字段的類型字段,而且將不同的序列化方式細分有linked,Slug.而關系類型上GenericForeignKey都能滿足
StringRelatedField/PrimaryKeyRelatedField/HyperlinkedRelatedField
第七類其他
ReadOnlyField/HiddenField/ModelField
ModelSerializer
提供了Model字段與Serializer字段的映射,也提供對關系字段的映射,包括稍微復雜的GenericForeignKey關系。
- Meta class 中必須有model指定
- Meta class 中fields必須指定
- Meta class 中必須包含添加的序列化字段,如果和model同名,那么添加的加覆蓋model同名字段
- 自添加override model的字段,要注意其讀寫設置,不然會影響modelserializer的save行為,因為如果是只讀,那么在validated_data中將沒有這個字段,在save時就可能缺少字段,從而save失敗。這是在自添加字段需要注意的。
當前 partial_update 部分更新時,ModelSerializer的處理
對於部分更新,在實例化ModelSerializer時,時需要提供partial=True的,不然校驗會失敗,拿不到校驗后的數據來進行部分更新。可以查看ModelSerializer的校驗源碼,對部分校驗的支持。
跳過邏輯分析:
- 首先校驗數據時會進入到以下方法:
- 進入到運行校驗,由於有數據,所以進入to_internal_value校驗data中的數據
- 可以看到to_internal_data開時校驗每一個字段,通過field.get_value(data) 拿取字段對應數據,如果沒有則返回空。
- 然后進入校驗的驗證是否為空,如果時空,但是又是partial=True,那么拋出SkipField
- 回到了to_internal_value,接受到異常后,捕獲異常,如果時SkipField會繼續循環,最終得到ret校驗后的有效數據集:
- 然后回到run_validation,然后運行run_validators進行全局校驗,不過單字段的value我們已經得到了。
可以看到,對於partial_update,ModelSerializersave()的處理邏輯。
總結
- 序列化是可嵌套的
- 序列化初始化的參數對后續序列化對象影響是非常大的。
- 序列化字段分readable和writable,即對應可serialize和deserialized的。
- 有關model序列化,除了普通字段,還有關聯字段,關聯字段只會處理其對應的數據是多個還是單個都是用PrimaryKeyRelatedField。記住,字段可設置數據來源source等來定義單個字段的序列化出來的行為,畢竟序列化和反序列化接口都是要帶入對應對象參與序列化的。
- 序列化大致流程:data->serializer.to_representation->field.get_attribute(instance)->field.to_representation
- 三方法:get_attribute/to_representation/to_internal_value 對序列化和序列化結果的影響。
- HyperlinkedRelatedField 主要反解view url 的正確反解。注意事項見《Rest_framework 回顧記憶集》
實例代碼
class CourseModelSerializer(serializers.ModelSerializer):
level = serializers.CharField(source='get_level_display') # 利用source參數 在choice字段上
recommend_courses = serializers.SerializerMethodField() # manytomany 自定義序列化
chapters = serializers.SerializerMethodField()
image = serializers.SerializerMethodField()
class Meta:
model = Course
fields = ['id', 'title', 'image', 'level', 'recommend_courses', 'chapters']
def get_recommend_courses(self, obj):
qs = obj.re_course.all()
return [{'id': i.course.id, 're_course_title': i.course.title} for i in qs]
def get_chapters(self, obj):
if isinstance(self, ListSerializer):
return ''
qs = obj.chapters_set.all()
return [{'id': j.id, 'chapter_name': j.title} for j in qs]
def get_image(self, obj):
rel_path = obj.image
rely_on_path = settings.STATIC_URL
return rely_on_path + rel_path
# 主課程分類
class CourseCategoryModelSerializer(serializers.ModelSerializer):
subcategory_url = serializers.HyperlinkedRelatedField(source='coursesubcategory_set',
view_name='luffyapi:coursesubcategory-detail',
read_only=True, # 設置為只讀
many=True) # 對於多對象
class Meta:
model = CourseCategory
fields = ['id', 'name', 'subcategory_url']
# 子課程分類
class CourseSubCategoryModelSerializer(serializers.ModelSerializer):
"""
子課程分類 序列化
"""
category_url = serializers.HyperlinkedRelatedField(source='category', view_name='luffyapi:coursecategory-detail',
lookup_field='pk',
lookup_url_kwarg='pk', read_only=True)
courses_url = serializers.HyperlinkedIdentityField(
view_name='luffyapi:coursesubcategory-get-courses',
read_only=True)
class Meta:
model = CourseSubCategory
fields = ['id', 'name', 'category', 'category_url', 'courses_url']