restframework(2):序列化解析
2018年10月27日 14:50:27 submarineas 閱讀數 783
版權聲明:本文為部落客原創文章,轉載請注明出處。 https://blog.csdn.net/submarineas/article/details/83350925
django rest framework serializers小結 https://blog.csdn.net/l_vip/article/details/79156113
https://blog.csdn.net/qq_41500222/article/details/87895643 源碼概述
restframework(2):序列化解析
- 字段與選項:
- 常用字段類型:
- 通用參數
- 序列化
- 序列化意義
- 序列化執行個體
- 序列化解析
- 第一種表示方法——Serializers:
- 第二種表示方法——ModelSerializers:
- 兩種方式的精确查找
- 兩種方式的不同——多表查詢
- 反序列化使用
- 補充說明
- 關聯對象嵌套序列化
- 總結:
字段與選項:
常用字段類型:
字段 | 字段構造方式 |
---|---|
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) |
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) |
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的用法相同 |
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=) |
相對而言,常用字段類型是比較常見的,在我們的ORM(Object Relational Mapping,對象關系映射,簡稱ORM)模式裡,隻要是連接配接資料庫那麼就一定需要定義我們的模型參數。
通用參數
參數名稱 | 說明 |
---|---|
max_length | 最大長度 |
min_lenght | 最小長度 |
read_only | 表明該字段僅用于序列化輸出,預設False |
write_only | 表明該字段僅用于反序列化輸入,預設False |
required | 表明該字段在反序列化時必須輸入,預設True |
default | 反序列化時使用的預設值 |
allow_null | 表明該字段是否允許傳入None,預設False |
validators | 該字段使用的驗證器 |
error_messages | 包含錯誤編号與錯誤資訊的字典 |
label | 用于HTML展示API頁面時,顯示的字段名稱 |
help_text | 用于HTML展示API頁面時,顯示的字段幫助提示資訊 |
這裡比較常用的字段是前面六個,其中max_length和min_length一般配合着charfield使用,可以給它設定上下限。而read_only和write_only是兩個相反的概念,前者是不接收用戶端的資料,隻向用戶端輸出資料,後者是隻接收用戶端的資料,不向用戶端輸出資料,這就可以類比于我們登入注冊時的密碼框,我們隻需要向它寫入而并不需要它像我們輸出,并且該字段是經過hash加密的,尋常情況難以解密。
序列化
序列化意義
web有兩種應用模式,一種是前後端不分離,一種是前後端分離,目前後端分離的時候,後端隻需要向前端傳輸資料即可,不需要進行其他的操作,一般如果是中大型公司,都是前後端分離,這也是目前的市場規則需要,具體的可以看下圖:
現階段主流的資料格式為json格式,是以在restframework在前後端傳輸資料時,也主要是json資料,過程中就要需要把其他資料轉換成json資料,比如資料庫查詢所有資料時,是queryset對象,那就要把這對象處理成json資料傳回前端。
下面我們來看一個執行個體:
序列化執行個體
models.py 檔案:
from django.db import models
# Create your models here.
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.IntegerField()
pub_date = models.DateField()
publish = models.ForeignKey("Publish")
authors = models.ManyToManyField("Author")
def __str__(self):
return self.title
class Publish(models.Model):
name = models.CharField(max_length=32)
email = models.EmailField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
def __str__(self):
return self.name
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
建構完models,我們通過資料庫的初始化和遷移并連接配接我們的資料庫,下面是我們序列化的幾種方式。
views.py 中:
from django.shortcuts import render,HttpResponse
# Create your views here.
from django.views import View
from rest_framework.response import Response
from .models import *
from app01.serilizer import *
from rest_framework.views import APIView
class PublishView(APIView):
def get(self,request):
# 序列化
# 方式1:
publish_list=list(Publish.objects.all().values("name","email"))
return HttpResponse(json.dumps(publishers), content_type='application/json')
# 方式2:
from django.forms.models import model_to_dict
publish_list=Publish.objects.all()
temp=[]
for obj in publish_list:
temp.append(model_to_dict(obj))
return HttpResponse(json.dumps(temp), content_type='application/json')
# 方式3:
from django.core import serializers
ret=serializers.serialize("json",publish_list)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
1. 對查詢的資料類型進行基礎資料類型的強轉,比如list(queryset對象.values()),因為query set對象不能直接被序列化
2. 單個資料對象 model_to_dict(obj),是Django中的一個方法:傳回一個字典,key是obj 這個對象的字段名,value是字段對應的值。這種是最快的一種序列化的方式。
3. django提供的serialize方法,data=serializers.serialize(“json”,book_list)
下面我們可以用postman進行測試,發現正常。
注意:如果上述三種方法用queryset去序列化,然後傳回的将不是一個json格式的資料,那麼我們的postman就會報錯。
序列化解析
命名規則:
books表的增删改查:
路由設定 | 請求方式 | 說明 |
---|---|---|
/books/ | get | 傳回目前所有資料 |
/books/ | post | 傳回送出資料 |
/book/(\d+) | get | 傳回目前檢視的單條資料 |
/book/(\d+) | put | 傳回更新資料 |
/book/(\d+) | delete | 傳回空 |
是以我們的url配置如下:
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^authors/', views.AuthorView.as_view()),
url(r'^author/(?P<pk>\d+)/', views.AuthorDetailView.as_view()),
url(r'^book/(?P<pk>\d+)/', views.BooksDetailView.as_view()),
url(r'^books/', views.BooksView.as_view()),
]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
第一種表示方法——Serializers:
view中:
from rest_framework.views import APIView
from .models import *
# Create your views here.
from rest_framework import serializers
from rest_framework.response import Response
# 建構序列化器
class AuthorSerializers(serializers.Serializer):
name = serializers.CharField(max_length=32)
age = serializers.IntegerField()
# 擷取序列化資料
class AuthorView(APIView):
def get(self,request):
obj = Author.objects.all()
author = AuthorSerializers(obj,many=True)
return Response(author.data)
def post(self,request):
ps = AuthorSerializers(data=request.data)
if ps.is_valid():
print(ps.validated_data)
ps.save() # create方法
return Response(ps.data)
else:
return Response(ps.errors)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
上述方法是通過繼承serializer類來完成序列化,這裡需要注意,因為我們的obj是個queryset對象,那麼序列化字段就需要加many=True,如果是model對象,那麼就不需要加many:
def get(self,request):
obj = Author.objects.all().first()
author = AuthorSerializers(obj)
return Response(author.data)
- 1
- 2
- 3
- 4
第二種表示方法——ModelSerializers:
class AuthorModelSerializers(serializers.ModelSerializer):
class Meta:
model=Author
fields="__all__" # 全部
# exclude = ('price',) # 除了price這項以外
# fields=('pub_date','title') # 隻有pub、title兩項
# 作者類
class AuthorView(APIView):
def get(self,request):
obj = Author.objects.all()
author = AuthorSerializers(obj,many=True)
return Response(author.data)
def post(self,request):
ps = AuthorSerializers(data=request.data)
if ps.is_valid():
print(ps.validated_data)
ps.save() # create方法
return Response(ps.data)
else:
return Response(ps.errors)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
上述兩種方式顯示的結果都是一緻的,我們可以通過postman進行測試,選擇請求方式為get,得到了相同的結果。post請求方式就不再示範了,是要求我們在浏覽器頁面或者postman中寫一個json格式資料送出,然後會在資料庫中建立并儲存。和上面一樣,因為我們寫的是queryset對象,是以序列化需要加many來限制,關于many,具體在下面說,下圖為測試結果。
然後我們還可以用同樣的方式查book表,因為url已經設定了,是以我們隻要将視圖中的AuthorModelSerializers下的Author改成Book,然後再把後面調用部分改成該序列化器:
兩種方式的精确查找
在開始我們的url配置中,我們在正則中還設定了兩個帶參數的表達式,這裡同樣可以通過它們的pk(主鍵)值找到響應的字段。并且序列化器部分也不需要改變,隻需要定義的是擷取資料部分:
class BookDetailView(APIView):
def get(self,request,pk):
obj = Author.objects.filter(pk=pk).first()
author_id = AuthorModelSerializers(obj)
return Response(author_id.data)
def put(self, request, pk):
author = Author.objects.filter(pk=pk).first()
ps = AuthorModelSerializers(author, data=request.data)
if ps.is_valid():
ps.save()
return Response(ps.data)
else:
return Response(ps.errors)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
兩種方式的不同——多表查詢
對于單表查詢來說,我們認為上述兩種方式的表達方式基本一緻,Serializers是需要我們一個個取手寫字段的,而ModelSerializers是可以幫我們簡化一些代碼,并且fields也能設定哪些字段。但在多表中,就會不适用了,兩種方式産生分歧:
1. 一對多,通過source="Book.name"指定字段
2. 多對多,通過get_字段名鈎子函數來定義要擷取的内容
# 一對多
# publish = serializers.CharField() #不加source時,預設給的是Publish模型定義__str__傳回的字段
publish = serializers.CharField(source="publish.name")
# 多對多
# authors = serializers.CharField(source="authors.all") #擷取是一個queryset對象 字元串
authors = serializers.SerializerMethodField() #通過鈎子函數自定制需要的資訊
def get_authors(self, obj):
temp = []
for author in obj.authors.all():
temp.append({'name':author.name, 'email':author.age})
return temp
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
如果我們的序列化器繼承的是Serializers,那麼我們隻需要重新寫入字段即可,因為Serializers在源碼裡繼承的是BaseSerializer,自己并沒有create字段,而Base類中,create隻是傳回異常。但如果是ModelSerializers,則必須要重寫create方法,在源碼中它已經重寫了create方法,讓它真正具有了功能,對整個資料表。是以create如果不重寫,那麼它會報錯。
serializers.SerializerMethodField()是固定寫法,而下面的函數名必須是get_字段名,函數的參數obj就是每一個book對象,這樣我們通過這個類,在使用postman進行get請求時就能得到和上面一樣的資料。
{
"id": 1,
"authors": [
1
],
"title": "像少年啦飛馳",
"price": 32,
"pub_date": "2008-10-08",
"publish": 1
},
。。。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
反序列化使用
1. 驗證
使用序列化器進行反序列化時,需要對資料進行驗證後,才能擷取驗證成功的資料或儲存成模型類對象。
在擷取反序列化的資料前,必須調用is_valid()方法進行驗證,驗證成功傳回True,否則傳回False。驗證失敗,可以通過序列化器對象的errors屬性擷取錯誤資訊,傳回字典,包含了字段和字段的錯誤。如果是非字段錯誤,可以通過修改REST framework配置中的NON_FIELD_ERRORS_KEY來控制錯誤字典中的鍵名。驗證成功,可以通過序列化器對象的validated_data屬性擷取資料。
在定義序列化器時,指明每個字段的序列化類型和選項參數,本身就是一種驗證行為。如我們前面定義過的BookSerializer,這裡我們在model的book類中再擴寫幾個字段,然後遷移。
class BookSerializer(serializers.Serializer):
"""圖書資料序列化器"""
title = serializers.CharField(label='名稱', max_length=20)
repub_date = serializers.DateField(label='再版日期', required=False)
read = serializers.IntegerField(label='閱讀量', required=False)
comment = serializers.IntegerField(label='評論量', required=False)
image = serializers.ImageField(label='圖檔', required=False)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
我們可以看官方解釋翻譯:
通常,如果在反序列化期間未提供字段,則會引發錯誤。如果在反序列化期間不需要此字段,則設定為false。将此設定為False還允許在序列化執行個體時從輸出中省略對象屬性或字典鍵。如果密鑰不存在,它将不會包含在輸出表示中。預設為True。
是以通過構造序列化器對象,并将要反序列化的資料傳遞給data構造參數,進而進行驗證,是以可以在指令行中這樣寫:
>>>data = {'repub_date': '12'}
>>>serializer = BookInfoSerializer(data=data)
>>>serializer.is_valid() # False
>>>serializer.errors # {}
>>>serializer.validated_data # OrderedDict([('title', 'python')])
- 1
- 2
- 3
- 4
- 5
錯誤驗證,我們列印的結果為:
'bpub_date': [ErrorDetail(string='Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].', code='invalid')]}
- 1
is_valid()方法還可以在驗證失敗時抛出異常serializers.ValidationError,可以通過傳遞raise_exception=True參數開啟,REST framework接收到此異常,會向前端傳回HTTP 400 Bad Request響應。
# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)
- 1
- 2
另外如果覺得這樣不行,還有其它的驗證方式可以使用:
這個在這裡就不再做過多描述了,等以後看源碼的時候可以一起來解釋含義與用法。
補充說明
關聯對象嵌套序列化
如果我們想定義外鍵的字段的序列化,可以有以下方式:
1、PrimaryKeyRelatedField
此字段将被序列化為關聯對象的主鍵。官方解釋為可以用于使用其主鍵表示關系的目标。是以我們可以設定其外鍵形式。
https://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
參數 | 官方解釋 |
---|---|
queryset | 驗證字段輸入時用于模型執行個體查找的查詢集。關系必須顯式設定或設定查詢集read_only=True。 |
many | 如果應用于多對多關系,則應将此參數設定為True。 |
allow_null | 如果設定為True,則該字段将接受None可為空的關系的值或空字元串。預設為False。 |
pk_field | 設定為字段以控制主鍵值的序列化/反序列化。例如,pk_field |
總結:
- 指明字段時需要包含read_only=True或者queryset參數:
- 包含read_only=True參數時,該字段将不能用作反序列化使用
- 包含queryset參數時,将被用作反序列化時參數校驗使用
是以可以這樣表示:
book = serializers.PrimaryKeyRelatedField(label='圖書', read_only=True)
- 1
或者說是:
book = serializers.PrimaryKeyRelatedField(label='圖書', queryset=Book.objects.all())
- 1
2、StringRelatedField
此字段将被序列化為關聯對象的字元串表示方式(即__str__方法的傳回值)
book = serializers.StringRelatedField(label=‘圖書’)
3、 many參數
在序列化器對象中,如果關聯的對象資料不是隻有一個,而是包含多個資料,此時關聯字段類型的指明仍可使用上述幾種方式,隻是在聲明關聯字段時,多補充一個many=True參數即可。
此處僅拿PrimaryKeyRelatedField類型來舉例,其他相同。
在BookSerializer中添加關聯字段:
class BookSerializer(serializers.Serializer):
"""圖書資料序列化器"""
title = serializers.CharField(label='名稱', max_length=20)
repub_date = serializers.DateField(label='再版日期', required=False)
read = serializers.IntegerField(label='閱讀量', required=False)
comment = serializers.IntegerField(label='評論量', required=False)
image = serializers.ImageField(label='圖檔', required=False)
page = serializers.PrimaryKeyRelatedField(read_only=True, many=True) # 新增
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
使用效果:
這裡我們将序列化器和擷取序列化資料部分分成兩個檔案,一個為serializers.py檔案,一個還是原來的views.py檔案,這樣對以後檢查代碼更友善。
>>>from .serializers import BookSerializer
>>>from .models import Book
>>>book = Book.objects.get(id=2)
>>>serializer = BookSerializer(book)
>>>serializer.data
- 1
- 2
- 3
- 4
- 5
總結:
當我們在使用restframework的序列化帶來便利的同時,也需要知道怎樣選擇一個對本項目最合适的序列化方式,以及最優的解決方案,我在寫這篇部落格的時候在stackoverflow上看到了一個這樣的問題,假如我們想要關聯字段的序列化呈現一種嵌套輸出的形式,比如說是{“xx”:“123”,{“yy”:“333”,“zz”}}這種,many的話在前面試驗後是輸出多資料清單,但現在需要嵌套一個json資料,就可以PrimaryKeyRelatedField和source聯合起來使用,具體的網址和步驟我忘了。
這篇部落格曆時兩天,查閱了很多資料,發現還有很多東西沒有囊括到,比如說超連結API:Hyperlinked,當用這個的時候,我們的views擷取資料部分就要加上context={},源碼中規定要把資料傳遞過去,還有很多小坑沒有去實作過,希望以後有時間的話再從頭做一遍。
參考:
[1]. https://www.django-rest-framework.org/api-guide
[2]. https://blog.windrunner.me/python/web/django-rest-framework.html
[3]. http://www.cnblogs.com/xinsiwei18/p/9742391.html
[4]. http://www.cnblogs.com/lyq-biu/p/9769421.html
[5]. https://www.cnblogs.com/fqh202/p/9608110.html
[6]. 《The Django Book 2.0中文譯本》