【DRF序列化】
目錄
前後端分離後,其互動一般都選擇使用JSON資料格式,JSON是一個輕量級的資料互動格式.
因此,後端傳送給前端(或前端傳送給後端)的資料都要轉成JSON格式,這就得需要我們把從資料庫內取到的資料進行序列化.
本文將詳細講述Django專案中如何使用第三方庫rest_framework
進行序列化.
在命令列中輸入:pip install djangorestframework
@
***
首先,我們準備三張資料表:
from django.db import models __all__ = ['Book', 'Publisher', 'Author'] class Book(models.Model): """書籍表""" title = models.CharField(max_length=62) CHOICES = ((1, '前端'), (2, '後端'), (3, '運維')) category = models.IntegerField(choices=CHOICES) pub_date = models.DateField() # 出版日期 publisher = models.ForeignKey(to='Publisher') # 外來鍵出版社表 authors = models.ManyToManyField(to='Author') # 多對多作者表 class Publisher(models.Model): """出版社表""" title = models.CharField(max_length=64) class Author(models.Model): """作者表""" name = models.CharField(max_length=64)
插入資料:
# 在Python指令碼中呼叫Django環境: import os, datetime if __name__ == '__main__': # 請將'blog091.settings'更改為對應的專案名稱及配置檔案,更改後直接執行即可 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog091.settings') import django django.setup() from blog import models # 添加出版社 pub01 = models.Publisher.objects.create(title="上帝出版社") pub02 = models.Publisher.objects.create(title="沙河出版社") pub03 = models.Publisher.objects.create(title="西二旗出版社") # 新增作者 au01 = models.Author.objects.create(name="迷之標籤大仙") au02 = models.Author.objects.create(name="迷之重啟大仙") au03 = models.Author.objects.create(name="迷之演算法大仙") # 新增書籍 pub_date = {'year': 2099, 'month': 12, 'day': 31} book01 = models.Book.objects.create(title="【論寫標籤的姿勢】", category=1, pub_date=datetime.date(**pub_date), publisher=pub01) book01.authors.add(au01) book02 = models.Book.objects.create(title="【論重啟服務的姿勢】", category=2, pub_date=datetime.date(**pub_date), publisher=pub02) book02.authors.add(au02) book03 = models.Book.objects.create(title="【論寫演算法的姿勢】", category=3, pub_date=datetime.date(**pub_date), publisher=pub03) book03.authors.add(au03)
基本的序列化操作
既然我們要使用DRF的序列化,那麼我們就得遵循人家框架的一些標準.
- 在Django中,我們的CBV繼承類是View;而在DRF中,我們的繼承類時APIView.
- 在Django中,我們返回資料使用HTTPResponse、JsonResponse、render;而在DRF中,我們使用Response.
第一步 註冊app
第二步 新建一個py檔案,在檔案內宣告序列化類
from rest_framework import serializers class BookSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) # 我們先註釋掉一個欄位 # 然後你讀一讀下面第二行黃色的字,再看一看第四步驟中的圖,你就明白了. # pub_date = serializers.DateField() category = serializers.CharField(source='get_category_display')
==這裡面寫的欄位必須是在models檔案中存在的.==
==並且,在models檔案中有,而在這裡沒有的欄位會被過濾掉.==
第三步 使用CBV,序列化物件
```python
from blog import models繼承類
from rest_framework.views import APIView
返回資料的方法
from rest_framework.response import Response
匯入上一步驟定義的序列化類
from .serializers import BookSerializer
class Book(APIView):
def get(self, request):# 首先,我們獲取書籍列表 book_list = models.Book.objects.all() # 然後,將書籍列表傳入序列化類 ret = BookSerializer(book_list, many=True) # book_list:要過濾的物件列表(過濾的是欄位,而不是物件) # many=True:表示取多個(原始碼中會迴圈取) # 最後返回資料 # 資料在其data中,所以要返回ret.data return Response(ret.data)
第四部 啟動專案,訪問站點
怎麼樣,四不四很66啊.
***
外來鍵/多對多關係的序列化
在宣告序列化類的檔案中新增如下程式碼:
from rest_framework import serializers class PublisherSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=64) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class BookSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) pub_date = serializers.DateField() category = serializers.CharField(source='get_category_display') # 出版社(外來鍵關係) 指定上面寫的出版社類即可 publisher = PublisherSerializer() # 內部通過外來鍵關係的id找到publisher_object # 再通過PublisherSerializer(publisher_object)得到資料 # 作者(多對多關係) 指定上面寫的作者類即可 authors = AuthorSerializer(many=True) # many=True:表示取多個(原始碼中會迴圈取)
然後,我們開啟瀏覽器,狂點重新整理後,即可看到:
***
反序列化的操作
當前端給我們傳送資料的時候(post請求),我們要進行一些校驗然後儲存到資料庫.
DRF中的Serializer給我們提供了一些校驗和儲存資料的方法,首先我們寫出反序列化用到的一些欄位,有些欄位要跟序列化區分開來,基本步驟如下.
步驟一 宣告反序列化類
·
==序列化和反序列化的欄位不統一,因此我們需要分別定義序列化和反序列化的欄位==from rest_framework import serializers from blog import models class PublisherSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=64) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class BookSerializer(serializers.Serializer): # 序列化和反序列化的欄位不統一,因此必須分別定義序列化和反序列化的欄位 """ 我們先了解如下三個引數: required=False -> 反序列化時不校驗此欄位 read_only=True -> 反序列化時不校驗此欄位 write_only=True -> 序列化時不校驗此欄位 """ # ==== 指定序列化時校驗,而反序列化時不校驗的欄位 ==== id = serializers.IntegerField(required=False) category = serializers.CharField(source='get_category_display', read_only=True) publisher = PublisherSerializer(read_only=True) authors = AuthorSerializer(many=True, read_only=True) # ==== 始終校驗的欄位 ==== title = serializers.CharField(max_length=64) pub_date = serializers.DateField() # ==== 指定反序列化時校驗,而序列化時不校驗的欄位 ==== post_category = serializers.IntegerField(write_only=True) publisher_id = serializers.IntegerField(write_only=True) author_list = serializers.ListField(write_only=True) def create(self, validated_data): """ 用於增加資料的方法 :param validated_data: 校驗通過的資料(就是步驟二中傳過來的book_obj) :return: validated_data """ # 開始ORM操作: book_obj = models.Book.objects.create( title=validated_data['title'], pub_date=validated_data['pub_date'], category=validated_data['post_category'], publisher_id=validated_data['publisher_id'] ) book_obj.authors.add(*validated_data['author_list']) return validated_data
步驟二 使用CBV 反序列化物件
from blog import models # 繼承類 from rest_framework.views import APIView # 返回資料的方法 from rest_framework.response import Response # 匯入上一步驟定義的序列化類 from .serializers import BookSerializer class Book(APIView): def get(self, request): book_list = models.Book.objects.all() ret = BookSerializer(book_list, many=True) return Response(ret.data) # 重點在這裡: def post(self, request): """post請求的基本思路:確定資料型別以及資料結構,對前端傳過來的資料進行校驗""" book_obj = request.data # 提取post請求發過來的資料 ser_obj = BookSerializer(data=book_obj) # data=book_obj:指定反序列化資料 if ser_obj.is_valid(): # 開始校驗 ser_obj.save() # 儲存資料 return Response(ser_obj.validated_data) # 校驗成功,返回校驗資訊 return Response(ser_obj.errors) # 校驗失敗,返回錯誤資訊
步驟三 啟動專案 訪問頁面並提交JSON資料
成功後,會展示出提交的資料,如下:
***
單條資料查詢及更新
步驟一 準備url
from blog import views urlpatterns = [ url(r'^book/(?P<book_id>\d+)$', views.BookEdit.as_view()), ]
步驟二 宣告(反)序列化類
from rest_framework import serializers from blog import models class PublisherSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=64) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class BookSerializer(serializers.Serializer): # 序列化和反序列化的欄位不統一,因此必須分別定義序列化和反序列化的欄位 """ 我們先了解如下三個引數: required=False -> 反序列化時不校驗此欄位 read_only=True -> 反序列化時不校驗此欄位 write_only=True -> 序列化時不校驗此欄位 """ # ==== 指定序列化時校驗,而反序列化時不校驗的欄位 ==== id = serializers.IntegerField(required=False) category = serializers.CharField(source='get_category_display', read_only=True) publisher = PublisherSerializer(read_only=True) authors = AuthorSerializer(many=True, read_only=True) # ==== 始終校驗的欄位 ==== title = serializers.CharField(max_length=64) pub_date = serializers.DateField() # ==== 指定反序列化時校驗,而序列化時不校驗的欄位 ==== post_category = serializers.IntegerField(write_only=True) publisher_id = serializers.IntegerField(write_only=True) author_list = serializers.ListField(write_only=True) def update(self, instance, validated_data): """ 用於更新資料的方法 :param instance: 要更新的資料就是步驟三中傳過來的book_obj) :param validated_data: 校驗通過的資料 :return: instance """ # 開始ORM操作: instance.title = validated_data.get('title', instance.title) instance.pub_date = validated_data.get('pub_date', instance.pub_date) instance.category = validated_data.get('category', instance.category) instance.publisher_id = validated_data.get('publisher_id', instance.publisher_id) if validated_data.get('author_list'): instance.authors.set(validated_data['author_list']) instance.save() return instance
步驟三 使用CBV (反)序列化物件
class BookEdit(APIView): def get(self, request, book_id): # 1. 獲取指定id的資料 book_obj = models.Book.objects.filter(id=book_id).first() # 2. 過濾欄位(序列化) ser_obj = BookSerializer(book_obj) # 3. 返回資料,資料在data中 return Response(ser_obj.data) # put請求 def put(self, request, book_id): book_obj = models.Book.objects.filter(id=book_id).first() # 先獲取要更新的資料 ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True) # instance=book_obj:要更新的資料 # data=request.data:新的資料 # partial=True:部分校驗 if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.validated_data) # 校驗成功,返回校驗資訊 return Response(ser_obj.errors) # 校驗失敗,返回錯誤資訊
步驟四 啟動專案 查詢指定id的資料
這就是單條資料查詢.
步驟五 更新指定id的資料
提交成功後,將展示出更新的資料,如下:
***
資料的校驗
DRF為我們提供了鉤子函式,可用於校驗單個或多個欄位.
單個欄位的校驗
在(反)序列化類中新增鉤子函式.
def validate_title(self, value):
"""
validate_欄位名, 對單個欄位進行校驗
:param value: 對應提交的title的值
:return:
"""
if "之" not in value:
raise serializers.ValidationError("書名必須含有 之")
此時,新增/更新書籍時,如果書名中沒有"之"字,則將丟擲錯誤資訊:
多個欄位的校驗
在(反)序列化類中新增鉤子函式.
def validate(self, attrs):
"""
可對對所有欄位進行校驗
:param attrs: 提交的所有資料
:return: 校驗通過時返回attrs
"""
if attrs['post_category'] == 3 and attrs['pub_date'] == '2099-12-31':
return attrs
else:
raise serializers.ValidationError("型別必須指定 3,且出版日期必須為 2099-12-31")
此時,新增/更新書籍時,如果不符合校驗規則,則將丟擲錯誤資訊.
自定義校驗器
# 首先,定義一個校驗器函式
def my_validate(value):
"""自定義的校驗器函式"""
if "敏感資訊" in value.lower():
raise serializers.ValidationError("內容包含敏感詞彙!")
class BookSerializer(serializers.Serializer):
# 然後,在校驗器中呼叫此函式
title = serializers.CharField(max_length=64, validators=[my_validate,])
# validators=[my_validate,]:指定校驗函式列表
現在,我們已經很清楚Serializer的用法了,然而我們發現,所有的序列化都跟我們的模型緊密相關...
對,沒錯,DRF也給我們提供了跟模型緊密相關的序列化器——ModelSerializer.
***
終極用法 ModelSerializer
==根據模型自動生成欄位.==
==預設就實現了新增與更新的方法.==
以下示例將實現上述的所有功能:
from rest_framework import serializers
from blog import models
class PublisherSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=64)
class AuthorSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField(max_length=32)
def my_validate(value):
"""自定義的校驗器函式"""
if "敏感資訊" in value.lower():
raise serializers.ValidationError("內容包含敏感詞彙!")
class BookSerializer(serializers.ModelSerializer):
""" 重寫欄位 + def get_自定義欄位名(self, obj): """
# 重寫的欄位如果與原欄位同名,則會覆蓋掉原欄位
# 外來鍵關聯的物件有很多欄位我們是用不到的, 都傳給前端會有資料冗餘, 就需要我們自己去定製序列化外來鍵物件的哪些欄位
publisher_info = serializers.SerializerMethodField(read_only=True)
def get_publisher_info(self, obj):
# obj是要序列化的每個物件
return {'id': obj.publisher.id, 'title': obj.publisher.title}
authors_info = serializers.SerializerMethodField(read_only=True)
def get_authors_info(self, obj):
return [{'id': author.id, 'name': author.name} for author in obj.authors.all()]
# 再比如我們的選擇欄位,預設顯示的是key, 而我們要展示給使用者的是value, 因此,我們重寫選擇欄位來自定製:
category_dis = serializers.SerializerMethodField(read_only=True)
def get_category_dis(self, obj):
return obj.get_category_display()
class Meta:
""" 指定資料表 """
model = models.Book
""" 獲取欄位 """
fields = "__all__" # 所有欄位
# fields = ['id', 'title', '...'] # 包含指定欄位
# exclude=["id", '...'] # 排除指定欄位
""" depth """
# 代表找巢狀關係的第幾層,指定找外來鍵關係向下找幾層
# depth = 1
# 會將所有的外來鍵關係變成只讀read_only=True
# 這個引數幾乎不用,最好不要錯過4層
""" 只讀欄位 """
# 即反序列化時不校驗的欄位
read_only_fields = ['id', ]
""" extra_kwargs """
# 按照下面的格式, 可為所有欄位設定引數
extra_kwargs = {
"title": {"validators": [my_validate, ]},
'publisher': {'write_only': True},
'authors': {'write_only': True}
}