天天看點

restframework序列化解析詳解(番外)restframework(2):序列化解析字段與選項:序列化總結:

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有兩種應用模式,一種是前後端不分離,一種是前後端分離,目前後端分離的時候,後端隻需要向前端傳輸資料即可,不需要進行其他的操作,一般如果是中大型公司,都是前後端分離,這也是目前的市場規則需要,具體的可以看下圖:

restframework序列化解析詳解(番外)restframework(2):序列化解析字段與選項:序列化總結:

現階段主流的資料格式為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就會報錯。

restframework序列化解析詳解(番外)restframework(2):序列化解析字段與選項:序列化總結:

序列化解析

命名規則:

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,具體在下面說,下圖為測試結果。

restframework序列化解析詳解(番外)restframework(2):序列化解析字段與選項:序列化總結:

然後我們還可以用同樣的方式查book表,因為url已經設定了,是以我們隻要将視圖中的AuthorModelSerializers下的Author改成Book,然後再把後面調用部分改成該序列化器:

restframework序列化解析詳解(番外)restframework(2):序列化解析字段與選項:序列化總結:

兩種方式的精确查找

在開始我們的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

另外如果覺得這樣不行,還有其它的驗證方式可以使用:

restframework序列化解析詳解(番外)restframework(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

總結:

  1. 指明字段時需要包含read_only=True或者queryset參數:
  2. 包含read_only=True參數時,該字段将不能用作反序列化使用
  3. 包含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中文譯本》

繼續閱讀