1. 程式人生 > >【DRF序列化】

【DRF序列化】

目錄

前後端分離後,其互動一般都選擇使用JSON資料格式,JSON是一個輕量級的資料互動格式.

因此,後端傳送給前端(或前端傳送給後端)的資料都要轉成JSON格式,這就得需要我們把從資料庫內取到的資料進行序列化.

本文將詳細講述Django專案中如何使用第三方庫rest_framework進行序列化.

在命令列中輸入:pip install djangorestframework

,方可下載rest_framework.

@
***

首先,我們準備三張資料表:

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}
        }