Django序列化元件Serializers詳解

程式碼一字狂發表於2020-07-26

 

  本文主要系統性的講解django rest framwork 序列化元件的使用,基本看完可以解決工作中序列化90%的問題,寫作參考官方文件https://www.django-rest-framework.org/api-guide/serializers/#modelserializer,分成如下九個部分:

  01、為什麼要用序列化元件

  02、序列化元件的基本使用

  03、序列化元件常用欄位

  04、序列化元件is_valid、validated_data

  05、序列化元件校驗欄位 

  06、序列化元件.create() and .update()

  07、序列化元件ModelSerializer

  08、序列化元件構造複雜的結構

  09、序列化元件修改返回值to_representation、to_internal_value

 

01、為什麼要用序列化元件

  我們知道前後端常用json資料結構互動, 在後端我們常想把一個物件返回給前端,但是json序列化是不能序列化物件(不過可以新增序列化引數encoder序列化原理和序列化元件差不多需要自己定義序列化類和返回的結構),所以就有了我們的序列化元件,可以自定義特定結構把物件序列化返回給前端,同時可以對前端傳入的引數進行資料校驗等功能。

 

02、序列化元件的基本使用

models

from django.db import models

# Create your models here.


class Book(models.Model):
    id = models.IntegerField(primary_key=True)
    title = models.CharField(max_length=255)
    desc = models.CharField(max_length=255)
    is_deleted = models.IntegerField(choices=[(1, "刪除"), (0, "未刪除")])
    author = models.CharField(max_length=255)

 

serializer

from rest_framework.serializers import Serializer
from rest_framework import serializers


class BookSerializer(Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField()
    desc = serializers.CharField()
    is_deleted = serializers.ChoiceField(choices=[(1, "刪除"), (0, "未刪除")], source="get_is_deleted_display")
    author = serializers.CharField()

 

views

from app01.models import Book
from app01.serializer import BookSerializer
from django.http import HttpResponse, JsonResponse

# Create your views here.


def get_books(request):
    books = Book.objects.all()
    se = BookSerializer(books, many=True)
    return JsonResponse(se.data, safe=False)

 

  結果返回:

[{"id": 1, "title": "活著", "desc": "講述一代人的人生", "is_deleted": "未刪除", "author": "餘華"}]

 

 

  在寫法上model和serializer的寫法非常相近,但內在邏輯model是與資料庫表的關係對映,serializer是對物件的序列化和反序列化。

 

03、序列化元件常用欄位

常用欄位型別

欄位欄位構造方式
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=)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

選項引數:

名稱作用
max_length 最大長度
min_lenght 最小長度
allow_blank 是否允許為空
trim_whitespace 是否截斷空白字元
max_value 最大值
min_value 最小值

 

 

 

 

 

 

 

 

 

通用引數

引數名稱說明
read_only 表明該欄位僅用於序列化輸出,預設False
write_only 表明該欄位僅用於反序列化輸入,預設False
required 表明該欄位在反序列化時必須輸入,預設True
default 反序列化時使用的預設值
allow_null 表明該欄位是否允許傳入None,預設False
validators 該欄位使用的驗證器
error_messages 包含錯誤編號與錯誤資訊的字典
label 用於HTML展示API頁面時,顯示的欄位名稱
help_text 用於HTML展示API頁面時,顯示的欄位幫助提示資訊


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

  這一段引用自https://www.jianshu.com/p/26529651aa27

 

  在這裡額外講一個引數source,在官方文件中是這樣解釋的:

將用於填充欄位的屬性的名稱。可以是僅接受self引數的方法,例如URLField(source='get_absolute_url'),也可以使用點分符號遍歷屬性,例如EmailField(source='user.email')。當使用點分符號序列化欄位時,default如果在屬性遍歷期間任何物件不存在或為空,則可能需要提供一個值。

該值source='*'具有特殊含義,用於指示應將整個物件傳遞給該欄位。這對於建立巢狀表示或對需要訪問完整物件才能確定輸出表示的欄位很有用。

預設為欄位名稱。

  其中比較常用的用法:

  1、source="get_field_name_display"如上所展示,在choice欄位中可以展示選項對應的解釋,這中用法代表的是官方解釋說的可以是僅接受self引數的方法,也就是source中可以填serializer物件可以呼叫的方法(方法中需要傳入self),內在邏輯是這個欄位會展示此方法的返回的結果。

  2、source='user.email'這種用法常在ModelSerializer的子類中,其中user為User物件,此中寫法是指展示user的email屬性,常用於我們想把外來鍵物件user的屬性和本物件的屬性展示在同一層級,而不是下一級。

 

04、序列化元件is_valid、validated_data

  當我們定義好序列化器時,怎麼校驗傳入的欄位呢?那就是is_valid方法,可以根據定義序列化器的校驗規則判斷傳入欄位是否合法。

serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}

 

  然後serializer.validated_data就可以獲取校驗過後的資料字典。

 

05、序列化元件校驗欄位 

  序列化元件校驗欄位的方式常有三種:

  1、首先是在欄位的validators屬性, 其中傳入一個校驗方法列表如:validators=(my_validator, )其中my_validator中定義校驗規則。

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
    score = IntegerField(validators=[multiple_of_ten])

 

  2、最常用的是定義一個validate_field_name(self, value)的函式(其中field_name指的是欄位名),函式內是具體的邏輯。

from rest_framework import serializers

class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()

    def validate_title(self, value):
        """
        Check that the blog post is about Django.
        """
        if 'django' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Django")
        return value

 

  3、最後一種是定義一個validate(self, data)其中data是所有欄位的鍵值對,所以這個校驗方法是物件級別的校驗。

from rest_framework import serializers

class EventSerializer(serializers.Serializer):
    description = serializers.CharField(max_length=100)
    start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, data):
        """
        Check that start is before finish.
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

 

  當然或許當你看到這裡會問why?how? 只能說原始碼是最好的答案。

 

06、序列化元件.create() and .update()

  在我們定義的序列化類中, 可以新增create和update方法,當我們有需求是根據反序列化後的資料在資料庫表中建立記錄或者更新某條資料,這時我們就可以在create方法和update方法中定義對應的邏輯。

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()
   def create(self, validated_data):
        return Comment.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.content = validated_data.get('content', instance.content)
        instance.created = validated_data.get('created', instance.created)
        instance.save()
        return instance

 

  接下來呼叫serializer.save()命令便可建立一條紀錄或者更新一條記錄,其中判斷save時什麼時候是建立什麼時候是更新呢?關鍵在於serializer的例項化。

# .save() will create a new instance.
serializer = CommentSerializer(data=data)
serializer.save()
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

serializer.save()

 

  其中當Serializer類例項化沒有傳入model物件時會呼叫create方法建立一條記錄, 如果Serializer類例項化時傳入了model物件就會呼叫update方法更新一條記錄。

 

  有時除了反序列化的欄位我們還需要其他欄位怎麼辦呢?我們可以在save中傳入引數名和值,可以在validated_data中根據引數名取到對應的值。

serializer.save(owner=request.user)

 

  這樣我們就可以在validated_data.get("owner")就可以取到user物件了。

 

07、序列化元件ModelSerializer

  ModelSerializer和表單的ModelForm元件很相似,都極大簡化了我們的開發,可以在內部類Meta中定義對應的model,ModelSerializer就會自動生成model欄位對應的Field不用我們定義。

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']

 

   其中fields中定義序列化的欄位,如果是全部欄位就寫__all__, 上述例子我們沒有定義具體的欄位,ModelSerializer幫我們自動生成了 'id', 'account_name', 'users', 'created'的Field。

 

08、序列化元件構造複雜的結構

  以下是個存在一對多和多對多欄位的序列化器

serializer

from res_framework import serializers

#這個類用於被例項化,多對多欄位這麼寫
class AuthorSerializer(serializers.Serializer):
    id = serializers.Charfield()
    name = serializers.Charfield()
    age = serializers.Charfield()

#傳給views.py的主類
class BookSerializer(serializers.Serializer):
    name = serializers.Charfield()
    #source 可以指定欄位 ,  id是要序列化的表名。
    id  = serializers.CharField(source='nid') 
    #,source後欄位用.的方式可以跨表查詢。
    publish = serializer.CharField(source='publish.email')
    ''' 
    如果在models.py的book類中定義一個test方法。
    def test(self): 
        return str(self.price)+self.name
   '''
   # 返回的結果就會有xx欄位,souce不但可以指定表模型欄位,還可以指定模型表方法,並且賦值給xx變數
   xx = serializers.Charfield(source='test')
   
   
   #外來鍵的實現方法:
   #一對多欄位
   #如果要通過外來鍵欄位返回出版社的所有資訊,包括id,name,email...
   #obj是當前迴圈序列化到的資料物件
    publish = serializers.SerializerMethodField()
    def get_publish(self,obj):
        return {’id‘:obj.publish.pk,'name':obj.publish.name}
   
   #多對多欄位
   #所有作者的詳情,也展示出來
    authors = serializers.SerializermethodFiled()
    def get_authors(self,obj):
        author_list  = obj.authors.all()
        author_ser = AuthorSerializer(author_list,many=True)
        return author_ser.data

views

from rest_framework.views import APIView
from rest_framework.response import Response
from app01 import models

class BookView(APIview):
    def get(self,request,*args,**kwargs):
            #獲取所有圖書資料
        response = {'status':100,'msg':'獲取成功'}
        book_list = models.Book.objects.all()
        #例項化BookSerializer類,把要序列化的資料book_list傳入
        #如果要序列化querySet物件,一定要加many = True
        book_ser = BookSerializer(book_list,many=True)
        #把序列化後的資料book_ser.data 拿出來放到response字典中返回給客戶端
        response['data'] = book_ser.data 
        return Response(response)

 

 

如果使用ModelSerializer:

serializer

from app01 import models
class PublishSerializer(serializers.ModelSerializer): 
    class Meta: #固定寫法
    # 指定要序列化Book表
    model = models.Book
    #指定要序列化的欄位
    fields = ['nid','name']
    #序列化所有欄位
    fileds ='__all__‘ 
    #要排除的欄位(不能與fileds連用)
    # exclude = ['name','price'] 
    #深度判定
    depth = 1 
    #如果要不按照父類的來,想要自己定義顯示的欄位的話,自己定義一個,覆蓋掉父類的欄位屬性。
    publish = serializers.SerializerMethodField()  #一對多欄位
    def get_publish(self,obj):
        return {’id‘:obj.publish.pk,'name':obj.publish.name}

  此段摘自https://blog.csdn.net/qq_36019490/article/details/90339490

  這裡額外解釋一下depth這個引數:在物件外來鍵另一物件,另一物件又外來鍵另一物件以此類推,depth的作用就是可以決定序列化時的外來鍵深度。

  複雜序列化器的要點在於,serializers.SerializerMethodField()和get_field_name的使用獲取自己想得到的欄位值,還有source的使用,上面有講。

 

09、序列化元件修改返回值to_representation、to_internal_value

  to_representation(self, instance):如果序列化器定義了此方法,可以改變序列化物件data的值,也就是serializer.data的值,你可以根據自己的業務場景去重新構造返回值。

def to_representation(self, instance):
    """Convert `username` to lowercase."""
    ret = super().to_representation(instance)
    ret['username'] = ret['username'].lower()
    return ret

 

 

  to_internal_value(self, data): data為未經校驗的資料欄位, 此方法可以實現校驗和修改反序列化後的值,然後返回。如果不想修改反序列化後的值只是做校驗的話,完全可以使用validate方法替代。

def to_internal_value(self, value):
    if value == None:
        return 0
    return value

 

  總而言之,這兩個方法一個是用於重新構造validated_data並返回,一個用於重新構造serializer.data的值並返回。

 

相關文章