1. 程式人生 > >DRF解析組件以及序列化組件

DRF解析組件以及序列化組件

插入數據 繼承 foreign 部分 price t對象 就會 註意 技術分享

一.知識點回顧:

1.三元運算:

三元運算能夠簡化我們的代碼,請看如下代碼:

# 定義兩個變量
a = 1
b = 2
# 判斷a的真假值,如果為True,則將判斷表達式的前面的值賦給c,否則將判斷表達式後面的值賦給c
c = a if a else b

print(c) # 1  # 因為a的真假值判斷為True,所以c為1
# 定義兩個變量
a = 0
b = 2
# 判斷a的真假值,如果為True,則將判斷表達式的前面的值賦給c,否則將判斷表達式後面的值賦給c
c = a if a else b

print(c) # 2  # 因為a的真假值判斷為False,所以c為2

2.列表推導式:

列表推導式的返回結果仍然是一個列表,假設有列表li = [1, 2, 3, 4], 請同學們思考,如果將li列表裏面的每一個元素乘以2得到一個新的列表,請看下面的代碼:

li = [1, 2, 3, 4]
# 方式一:使用普通for循環
new_list = list()
for item in li:
    new_list.append(item * 2)

# 方式二:使用列表推導式
li = [ x * 2 for x in li]

可見,列表推導式能夠大大簡化代碼,再看下面的代碼:

class JsonParser(object):
    
pass class FormParser(object): pass class Parser(object): parser_classes = [JsonParser, FormParser] def _get_parse(self): parser_list = [parser() for parser in self.parser_classes] return parser_list parser = Parser() parser_list = parser._get_parse()
print(parser_list) # [<__main__.JsonParser object at 0x103f07048>, <__main__.FormParser object at 0x103f072e8>]

可以看到,parser_list是parser_classes中每個類的實例化對象列表。以上就是關於列表推導式的回顧。

3.getattr

在學習面向對象時,我們知道可以通過對象加點號獲取該對象的屬性,也可以通過對象的dict訪問屬性,請看下面的代碼:

class Father(object):
    country = "china"

class Person(Father):
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("pizza", 18)
print(p.__dict__)       # {‘name‘: ‘wahaha‘, ‘age‘: 18}
print(Person.__dict__)  # {‘__module__‘: ‘__main__‘, ‘__init__‘: <function Person.__init__ at 0x103f132f0>, ‘__doc__‘: None}
print(p.name)           # wahaha
print(p.age)            # 18
print(p.country)        # china 如果對象不存在這個屬性,則會到其父類中查找這個屬性
print(p.hobby)          # 如果在父類中也找不到這個屬性,則會報錯:AttributeError: ‘Person‘ object has no attribute ‘hobby‘

對象的屬性查找首先會在該對象的一個名為dict的字典中查找這個屬性,如果找不到,則會到其父類中查找這個屬性,如果在父類中都也找不到對應的屬性,這會拋出異常AttributeError,我們可以通過在類中定義一個getattr來重定向未查找到屬性後的行為,請看下面的代碼:

class Father(object):
    country = "china"

class Person(Father):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __getattr__(self, value):
        raise ValueError("屬性%s不存在" % value)

p = Person("pizza", 18)
print(p.hobby)  # ValueError: 屬性hobby不存在

可以看到,我們能夠重新定義異常,也可以做其他任何事情,這就是getattr,一句話總結,通過對象查找屬性,如果找不到屬性,且該對象有getattr方法,那麽getattr方法會被執行,至於執行什麽邏輯,我們可以自定義。

4.Django settings文件查找順序

在使用Django的時候,應該是經常會用到它的settings文件,通過在settings文件裏面定義變量,我們可以在程序的任何地方使用這個變量,方便好用,比如,假設我在settings裏面定義了一個變量NAME=”wahaha”, 雖然可以在項目的任何地方使用:

>>> from drf_server import settings
>>> print(settings.NAME) #wahaha

但是,這種方式並不是被推薦和建議的,因為除了項目本身的settings文件之外,Django程序本身也有許多配置變量,都存儲在django/conf/global_setting.py模塊裏面,包括緩存、數據庫、秘鑰等,如果我們只是from drf_server import settings導入了項目本身的配置信息,當需要用到Django默認的配置信息的時候,還需要再次導入,from django.conf import settings,所以建議的使用方式是:

>>> from django.conf import settings
>>> print(settings.NAME)

使用上面的方式,我們除了可以使用自定義的配置信息(NAME)外,還可以使用global_settings中的配置信息,不需要重復導入,Django查找變量的順序是先從用戶的settings裏面查找,然後在global_settings中查找,如果用戶的settings中找到了,則不會繼續查找global_settings中的配置信息,假設我在用戶的settings裏面定義了NAME=”wahaha”, 在global_settings中定義了NAME=”ruwawa”,請看下面的打印結果:

>>> from django.conf import settings
>>> print(settings.NAME) #wahaha

可見,這種方式更加靈活高效,建議使用。

5.Django原生serializer

我們可以自定義符合REST規範的接口,請看下面的代碼:

models.py:

from django.db import models

# Create your models here.


class Courses(models.Model):
    title = models.CharField(max_length=32)
    description = models.CharField(max_length=32)

views.py:

class CoursesView(View):
    def get(self, request):
        courses = list()

        for item in Courses.objects.all():
            course = {
                "title": item.title,
                "description": item.description
            }

            courses.append(course)

        return HttpResponse(json.dumps(courses, ensure_ascii=False))

通過上面的方式,我們定義出了符合規範的返回數據,加上符合規範的url,我們可以說,手動方式進行REST開發也是完全沒有問題的,但是,企業最註重的是開發效率,而不是程序員實現需求的方式,理論上來說,我們可以通過任何方式,但是我們應該盡可能的采用高效的、靈活的、強大的工具來幫助我們完成重復的事情,所以我們需要學習DRF,它提供了很多的功能,在講DRF的序列化之前,我們來了解另一個知識,那就是,Django框架原生的序列化功能,即Django原生serializer,它的使用方式如下:

views.py

from django.core.serializers import serialize

class StudentView(APIView):

    def get(self, request):
       
        origin_students = Student.objects.all()
        serialized_students = serialize("json", origin_students)

        return HttpResponse(serialized_students)

使用方式非常簡單,導入模塊之後,將需要的格式和queryset傳給serialize進行序列化,然後返回序列化後的數據。

如果你的項目僅僅只是需要序列化一部分數據,不需要用到諸如認證、權限等等其他功能,可以使用Django原生serializer,否則建議使用DRF。

二.解析器組件

1.解析器組件的使用

首先,來看看解析器組件的使用,稍後我們一起剖析其源碼:

from django.http import JsonResponse

from rest_framework.views import APIView
from rest_framework.parsers import JSONParser, FormParser
# Create your views here.


class LoginView(APIView):
    parser_classes = [FormParser]

    def get(self, request):
        return render(request, parserver/login.html)

    def post(self, request):
        # request是被drf封裝的新對象,基於django的request
        # request.data是一個property,用於對數據進行校驗
        # request.data最後會找到self.parser_classes中的解析器
        # 來實現對數據進行解析
        
        print(request.data)  # {‘username‘: ‘alex‘, ‘password‘: 123}

        return JsonResponse({"status_code": 200, "code": "OK"})

使用方式非常簡單,分為如下兩步:

  • from rest_framework.views import APIView
  • 繼承APIView
  • 直接使用request.data就可以獲取Json數據

如果你只需要解析Json數據,不允許任何其他類型的數據請求,可以這樣做:

  • from rest_framework.parsers import JsonParser
  • 給視圖類定義一個parser_classes變量,值為列表類型[JsonParser]
  • 如果parser_classes = [], 那就不處理任何數據類型的請求了

因Django原生無法處理application/json協議的請求,我們肯定需要在request對象上做文章,因為只有有了用戶請求,我們的解析才有意義,沒有請求,就沒有解析,更沒有處理請求的邏輯,所以,我們需要弄明白,在整個流程中,request對象是什麽時候才出現的,是在綁定url和處理視圖之間的映射關系的時候嗎?我們來看看源碼:

@classonlymethod
def as_view(cls, **initkwargs):
    """Main entry point for a request-response process."""
    for key in initkwargs:
        if key in cls.http_method_names:
            raise TypeError("You tried to pass in the %s method name as a "
                            "keyword argument to %s(). Don‘t do that."
                            % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

def view(request, *args, **kwargs):
    self = cls(**initkwargs)
    if hasattr(self, get) and not hasattr(self, head):
        self.head = self.get
        self.request = request
        self.args = args
        self.kwargs = kwargs
        return self.dispatch(request, *args, **kwargs)
    view.view_class = cls
    view.view_initkwargs = initkwargs

    # take name and docstring from class
    update_wrapper(view, cls, updated=())

    # and possible attributes set by decorators
    # like csrf_exempt from dispatch
    update_wrapper(view, cls.dispatch, assigned=())
    return view

在執行view函數的時候,是請求到來,根據url查找映射表,找到視圖函數,然後執行view函數並傳入request對象,所以,可以在這個視圖函數裏面加入處理application/json的功能:

@classonlymethod
def as_view(cls, **initkwargs):
    """Main entry point for a request-response process."""
    for key in initkwargs:
        if key in cls.http_method_names:
            raise TypeError("You tried to pass in the %s method name as a "
                            "keyword argument to %s(). Don‘t do that."
                            % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

def view(request, *args, **kwargs):
    if request.content_type == "application/json":
        import json
        return HttpResponse(json.dumps({"error": "Unsupport content type!"}))

    self = cls(**initkwargs)
    if hasattr(self, get) and not hasattr(self, head):
        self.head = self.get
        self.request = request
        self.args = args
        self.kwargs = kwargs
        return self.dispatch(request, *args, **kwargs)
    view.view_class = cls
    view.view_initkwargs = initkwargs

    # take name and docstring from class
    update_wrapper(view, cls, updated=())

    # and possible attributes set by decorators
    # like csrf_exempt from dispatch
    update_wrapper(view, cls.dispatch, assigned=())
    return view

事實上,你可以在這裏,也可以在這之後的任何地方進行功能的添加。

那麽,DRF是如何做,我們在使用的時候只是繼承了APIView,然後直接使用request.data,所以,我鬥膽猜測,功能肯定是在APIView中定義的,接下來,我們一起來分析一下DRF解析器源碼,看看DRF在什麽地方加入了這個功能,上篇博客,我通過面向對象的方式,給類的某個方法新增了功能,調用重寫的方法,就實現了功能擴展,但是上面除了request.data,我沒有調用任何新的方法,所以,問題就在這個request.data上,它絕不僅僅是一個普通的對象屬性。

2.解析器組件的源碼解析

技術分享圖片

上圖詳細描述了整個過程,最重要的就是重新定義的request對象,和parser_classes變量,也就是我們在上面使用的類變量。好了,通過分析源碼,驗證了我們的猜測。

三.序列化組件

1.序列化組件的使用

models.py

from django.db import models

# Create your models here.


class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

    def __str__(self):
        return self.name


class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    def __str__(self):
        return self.name


class Book(models.Model):
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to="Publish", to_field="nid", on_delete=models.CASCADE)
    authors = models.ManyToManyField(to="Author")

    def __str__(self):
        return self.title

2.通過序列化組件進行GET接口設計

首先,設計url,本次我們只設計GET和POST兩種接口:

from django.urls import re_path

from serializers import views

urlpatterns = [
    re_path(rbooks/$, views.BookView.as_view())
]

我們新建一個名為app_serializers.py的模塊,將所有的序列化的使用集中在這個模塊裏面,對程序進行結偶:

from rest_framework import serializers

from .models import Book

class BookSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=128)
    publish_date = serializers.DateTimeField()
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField(max_length=32)
    authors = serializers.CharField(max_length=32)

接著,使用序列化組件,開始寫視圖類:

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

# 當前app中的模塊
from .models import Book
from .app_serializer import BookSerializer

# Create your views here.

class BookView(APIView):
    def get(self, request):
        origin_books = Book.objects.all()
        serialized_books = BookSerializer(origin_books, many=True)

        return Response(serialized_books.data)

我們已經通過序列化組件定義了一個符合標準的接口,定義好model和url後,使用序列化組件的步驟如下:

  • 導入序列化組件:from rest_framework import serializers
  • 定義序列化類,繼承serializers.Serializer(建議單獨創建一個專用的模塊用來存放所有的序列化類):class BookSerializer(serializers.Serializer):pass
  • 定義需要返回的字段(字段類型可以與model中的類型不一致,參數也可以調整),字段名稱必須與model中的一致
  • 在GET接口邏輯中,獲取QuerySet
  • 開始序列化:將QuerySet作業第一個參數傳給序列化類,many默認為False,如果返回的數據是一個列表嵌套字典的多個對象集合,需要改為many=True
  • 返回:將序列化對象的data屬性返回即可

上面的接口邏輯中,我們使用了Response對象,它是DRF重新封裝的響應對象。該對象在返回響應數據時會判斷客戶端類型(瀏覽器或POSTMAN),如果是瀏覽器,它會以web頁面的形式返回,如果是POSTMAN這類工具,就直接返回Json類型的數據。

此外,序列化類中的字段名也可以與model中的不一致,但是需要使用source參數來告訴組件原始的字段名,如下:

class BookSerializer(serializers.Serializer):
    BookTitle = serializers.CharField(max_length=128, source="title")
    publishDate = serializers.DateTimeField()
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    # source也可以用於ForeignKey字段
    publish = serializers.CharField(max_length=32, source="publish.name")
    authors = serializers.CharField(max_length=32)

下面是通過POSTMAN請求該接口後的返回數據,大家可以看到,除ManyToManyField字段不是我們想要的外,其他的都沒有任何問題:

[
    {
        "title": "Python入門",
        "publishDate": null,
        "price": "119.00",
        "publish": "浙江大學出版社",
        "authors": "serializers.Author.None"
    },
    {
        "title": "Python進階",
        "publishDate": null,
        "price": "128.00",
        "publish": "清華大學出版社",
        "authors": "serializers.Author.None"
    }
]

那麽,多對多字段如何處理呢?如果將source參數定義為”authors.all”,那麽取出來的結果將是一個QuerySet,對於前端來說,這樣的數據並不是特別友好,我們可以使用如下方式:

class BookSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=32)
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publishDate = serializers.DateField()
    publish = serializers.CharField()
    publish_name = serializers.CharField(max_length=32, read_only=True, source=publish.name)
    publish_email = serializers.CharField(max_length=32, read_only=True, source=publish.email)
    # authors = serializers.CharField(max_length=32, source=‘authors.all‘)
    authors_list = serializers.SerializerMethodField()

    def get_authors_list(self, authors_obj):
        authors = list()
        for author in authors_obj.authors.all():
            authors.append(author.name)

        return authors

請註意,get_必須與字段名稱一致,否則會報錯。

3.通過序列化組件進行POST接口設計

接下來,我們設計POST接口,根據接口規範,我們不需要新增url,只需要在視圖類中定義一個POST方法即可,序列化類不需要修改,如下:

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

# 當前app中的模塊
from .models import Book
from .app_serializer import BookSerializer

# Create your views here.


class BookView(APIView):
    def get(self, request):
        origin_books = Book.objects.all()
        serialized_books = BookSerializer(origin_books, many=True)

        return Response(serialized_books.data)

    def post(self, request):
        verified_data = BookSerializer(data=request.data)

        if verified_data.is_valid():
            book = verified_data.save()
            # 可寫字段通過序列化添加成功之後需要手動添加只讀字段
            authors = Author.objects.filter(nid__in=request.data[authors])
            book.authors.add(*authors)

            return Response(verified_data.data)
        else:
            return Response(verified_data.errors)

POST接口的實現方式,如下:

  • url定義:需要為post新增url,因為根據規範,url定位資源,http請求方式定義用戶行為
  • 定義post方法:在視圖類中定義post方法
  • 開始序列化:通過我們上面定義的序列化類,創建一個序列化對象,傳入參數data=request.data(application/json)數據
  • 校驗數據:通過實例對象的is_valid()方法,對請求數據的合法性進行校驗
  • 保存數據:調用save()方法,將數據插入數據庫
  • 插入數據到多對多關系表:如果有多對多字段,手動插入數據到多對多關系表
  • 返回:將插入的對象返回

請註意,因為多對多關系字段是我們自定義的,而且必須這樣定義,返回的數據才有意義,而用戶插入數據的時候,serializers.Serializer沒有實現create,我們必須手動插入數據:

# 第二步, 創建一個序列化類,字段類型不一定要跟models的字段一致
class BookSerializer(serializers.Serializer):
    # nid = serializers.CharField(max_length=32)
    title = serializers.CharField(max_length=128)
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField()
    # 外鍵字段, 顯示__str__方法的返回值
    publish_name = serializers.CharField(max_length=32, read_only=True, source=publish.name)
    publish_city = serializers.CharField(max_length=32, read_only=True, source=publish.city)
    # authors = serializers.CharField(max_length=32) # book_obj.authors.all()

    # 多對多字段需要自己手動獲取數據,SerializerMethodField()
    authors_list = serializers.SerializerMethodField()

    def get_authors_list(self, book_obj):
        author_list = list()

        for author in book_obj.authors.all():
            author_list.append(author.name)

        return author_list

    def create(self, validated_data):
        # {‘title‘: ‘Python666‘, ‘price‘: Decimal(‘66.00‘), ‘publish‘: ‘2‘}
        validated_data[publish_id] = validated_data.pop(publish)
        book = Book.objects.create(**validated_data)

        return book

    def update(self, instance, validated_data):
        # 更新數據會調用該方法
        instance.title = validated_data.get(title, instance.title)
        instance.publishDate = validated_data.get(publishDate, instance.publishDate)
        instance.price = validated_data.get(price, instance.price)
        instance.publish_id = validated_data.get(publish, instance.publish.nid)

        instance.save()

        return instance

這樣就會非常復雜化程序,如果我希望序列化類自動插入數據呢?

問題一:如何讓序列化類自動插入數據?

問題二:如果字段很多,那麽顯然,寫序列化類也會變成一種負擔,有沒有更加簡單的方式?

答案是肯定的,我們可以這樣做:

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book

        fields = (title,
                  price,
                  publish,
                  authors,
                  author_list,
                  publish_name,
                  publish_city
                  )
        extra_kwargs = {
            publish: {write_only: True},
            authors: {write_only: True}
        }

    publish_name = serializers.CharField(max_length=32, read_only=True, source=publish.name)
    publish_city = serializers.CharField(max_length=32, read_only=True, source=publish.city)

    author_list = serializers.SerializerMethodField()

    def get_author_list(self, book_obj):
        # 拿到queryset開始循環 [{}, {}, {}, {}]
        authors = list()

        for author in book_obj.authors.all():
            authors.append(author.name)

        return authors

步驟如下:

  • 繼承ModelSerializer:不再繼承Serializer
  • 添加extra_kwargs類變量:extra_kwargs = {‘publish’: {‘write_only’: True}}

使用ModelSerializer完美的解決了上面兩個問題

DRF解析組件以及序列化組件