DRF之視圖組件

不斷的優化咱們寫的程序,是每一個程序員必備的技能和職業素養,也是幫助咱們成長的很是重要的手段。git

引入

經過上一節課的學習,咱們已經經過DRF的序列化組件,設計出了符合rest規範的GET、POST接口邏輯,咱們知道操做數據庫主要有增刪改查幾種方式,增長,查詢兩種接口邏輯咱們都實現完畢了,接下來,咱們繼續實現剩下的兩種,刪除和修改。程序員

今日概要

  • serializer進行put、delete及獲取單條數據的接口設計
  • 視圖組件的使用及源碼剖析

知識點複習回顧

  • 昨日回顧
  • RESTful api接口規範
  • 混入類、多繼承
  • 函數的參數

在開始以前,按照慣例,咱們複習前兩節課學習過的知識點、以及學習今天的新知識點所需掌握的知識點。github

知識點複習回顧一:RESTful api接口規範

第一節課的那張圖你們還有印象嗎?若是沒有印象,請同窗們思考一下,我給你們總結的REST的最重要的一句話,那就是:url用來惟必定位資源,http請求方式用來定位用戶行爲。數據庫

根據這句話,咱們設計了下面的RESTful api:django

view

上面就是關於REST規範的回顧,請牢記:url用來惟必定位資源,http請求方式用來定位用戶行爲。編程

知識點複習回顧二:混入類、多繼承

我有一個Animal類,它包含以下方法:api

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
class Animal(object):
def eat(self):
print("Eat")

def walk(self):
print("Walk")

def sleep(self):
print("Sleep")

def run(self):
print("Run")

def flying(self):
print("Flying")

def wangwang(self):
print("Wangwang")

def miao(self):
print()


class Dog(Animal):pass


class Cat(Animal):pass


class Bird(Animal):pass

能夠看到,Dog類繼承了Animal類,可是Dog並無飛行和喵的功能,因此,若是直接繼承Animal會有一些問題,請同窗們思考,如何解決這個問題呢?網絡

好了,其實咱們有多中方式能夠解決這個問題,好比:app

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
32
33
34
35
36
class Animal(object):
def eat(self):
print("Eat")

def walk(self):
print("Walk")

def sleep(self):
print("Sleep")

def run(self):
print("Run")


class Flying(object):
def flying(self):
print("Flying")


class WangWang(object):
def wangwang(self):
print("Wangwang")


class Miao(object):
def miao(self):
print()


class Dog(Animal, WangWang):pass


class Cat(Animal, Miao):pass


class Bird(Animal, Flying):pass

咱們將不一樣的功能封裝到了獨立的類中,而後採用一種Mixin(混合類)的方式,其實也就是多繼承,來解決這個問題,這在Python中是比較常見的解決方式,好比socketserver模塊就用到了這種方式,當咱們須要線程的時候,能夠繼承線程類,當咱們須要進程的時候,能夠繼承進程類。socket

知識點複習回顧三:函數的參數

接下來,咱們一塊兒回顧一下函數的參數,假設我有函數以下:

1
2
3
4
5
6
7
8
def func(a, b, c=1, *args, **kwargs):
print(a, b, c, args, kwargs)

func(1, 2, 3, 4, 5) # 1, 2, 3, (4, 5) 全部未被匹配到的非key=value型的參數都會被*args接收
func(b=1, a=1, c=2, 4, 5) # 報錯,位置參數不能在關鍵字參數以後
func(4, 5, c=3, {"name": "pizza"}) # 報錯,位置參數不能在關鍵字參數以後,字典不是關鍵字參數
func({"name": "pizza"}, 1, 2) # {'name': 'pizza'} 1 2 () {}
func(1, 2, 3, 4, 5, d=1, e=2) # 1 2 3 (4, 5) {'d': 1, 'e': 2} 未被匹配到的關鍵字參數被傳遞給kwargs

今日詳細

好了,知識點的複習和補充,我們就到這裏,接下來,正式開始今天的內容,首先開始設計剩下三個接口邏輯。

使用serializer進行put接口設計

根據規範,PUT接口用來定義用戶對數據修改的邏輯,也就是update,它的url是這樣的, 127.0.0.1/books/1/,請求方式是PUT,1表明的是具體的數據,使用戶動態傳遞的,因此咱們的url應該是這樣的:

1
re_path(r'books/(\d+)/$', views.BookFilterView.as_view()),

此時咱們應該從新定義一個視圖類,由於url不同了,因此在views.py中,需新增一個視圖類:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from rest_framework.views import APIView
from app_serializer import BookSerializer


class BookFilterView(APIView):

def put(self, request, nid):
book_obj = Book.objects.get(pk=nid)

serialized_data = BookSerializer(data=request.data, instance=book_obj)

if serialized_data.is_valid():
serialized_data.save()
else:
return Response(serialized_data.errors)

請注意,在序列化時,咱們除了傳入data參數外,還需告訴序列化組件,咱們須要更新哪條數據,也就是instance,另外,咱們使用的序列化類仍是以前那個:

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

使用POSTMAN工具發送一個PUT請求修改數據: PUT http://127.0.0.1:9001/serializers/books/1

請注意,此時會報錯:RuntimeError: You called this URL via PUT, but the URL doesn’t end in a slash and you have APPEND_SLASH set. Django can’t redirect to the slash URL while maintaining PUT data. Change your form to point to 127.0.0.1:9001/serializers/books/1/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.

由於,若是是GET請求,Django的全局APPEND_SLASH參數爲True,因此會在url後面加上/(若是沒有),可是若是是PUT或者DELETE請求,APPEND_SLASH不會添加 / 到url末尾。而咱們上面定義的url是明確以 / 結尾的,因此,咱們應該在url後面加上反斜線 / ,或者把url修改成不以斜線結尾。

加上以後,再次發送請求修改數據:PUT http://127.0.0.1:9001/serializers/books/1/,查看數據庫,發現,數據已經被修改了。

這就是PUT接口邏輯的設計,分爲以下幾個步驟:

  • url設計:re_path(r’books/(\d+)/$’, views.BookFilterView.as_view())
  • 視圖類:從新定義一個視圖類
  • put方法:在視圖類中定義一個put方法
  • 序列化:在序列化的過程當中,須要傳入當前修改的數據行,參數名爲instance
  • 序列化類:不須要修改
  • url路徑:請求時,發送的url必須與urls.py中定義的url徹底匹配
使用serializer進行delete接口設計

接下來,繼續設計delete接口,根據規範,delete接口的url爲:127.0.0.1/books/1/,請求方式是DELETE,與put是一致的,都是對用戶指定的某行數據進行操做,數字1是動態的,因此咱們的url不變:

1
re_path(r'books/(\d+)/$', views.BookFilterView.as_view()),

一樣的,視圖類和序列化類都不須要從新定義,只須要在視圖類中定義一個delete方法便可,以下代碼所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class BookFilterView(APIView):

def delete(self, request, nid):
book_obj = Book.objects.get(pk=nid).delete()

return Response("")

def put(self, request, nid):
book_obj = Book.objects.get(pk=nid)

serialized_data = BookSerializer(data=request.data, instance=book_obj)

if serialized_data.is_valid():
serialized_data.save()
return Response(serialized_data.data)
else:
return Response(serialized_data.errors)

用POSTMAN來試試DELETE請求:http://127.0.0.1:9001/serializers/books/53/,咱們將剛剛添加的數據刪除,操做成功,一樣的,請注意,請求url必須徹底匹配urls.py中定義的url。

使用serializer進行單條數據的接口設計

最後一個接口的設計,是對單條數據進行獲取,根據規範url爲:127.0.0.1/books/1/,請求方式爲GET,根據url和前面兩個接口的經驗,此次仍然使用以前的視圖類,由於根據REST規範,url惟必定位資源,127.0.0.1/books/1/和127.0.0.1/books/是不一樣的資源,因此,咱們不能使用以前那個獲取所有數據的視圖類。這裏確定不能重用以前的那個get方法,必須從新定義一個get方法。

urls.py不變,新增三個接口邏輯後的視圖類以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BookFilterView(APIView):
def get(self, request, nid):
book_obj = Book.objects.get(pk=nid)

serialized_data = BookSerializer(book_obj, many=False)

return Response(serialized_data.data)

def delete(self, request, nid):
book_obj = Book.objects.get(pk=nid).delete()

return Response("")

def put(self, request, nid):
book_obj = Book.objects.get(pk=nid)

serialized_data = BookSerializer(data=request.data, instance=book_obj)

if serialized_data.is_valid():
serialized_data.save()
return Response(serialized_data.data)
else:
return Response(serialized_data.errors)

many=False, 固然,也能夠不傳這個參數,由於默認是False。經過POSTMAN發送請求,成功。三個接口定義完成了,加上上一節課的get和post,兩個視圖類的接口邏輯以下:

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
32
33
34
35
36
37
38
39
40
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()
return Response(verified_data.data)
else:
return Response(verified_data.errors)


class BookFilterView(APIView):
def get(self, request, nid):
book_obj = Book.objects.get(pk=nid)

serialized_data = BookSerializer(book_obj, many=False)

return Response(serialized_data.data)

def delete(self, request, nid):
book_obj = Book.objects.get(pk=nid).delete()

return Response("")

def put(self, request, nid):
book_obj = Book.objects.get(pk=nid)

serialized_data = BookSerializer(data=request.data, instance=book_obj)

if serialized_data.is_valid():
serialized_data.save()
return Response(serialized_data.data)
else:
return Response(serialized_data.errors)

到此爲止,咱們已經經過序列化組件設計出了符合REST規範的五個經常使用接口,已經足夠優秀了,可是還不夠完美,如今假設,咱們有多個數據接口,好比(Book,Author,Publish…..)等數據表都須要定義相似的接口,能夠預見,咱們須要重複定義相似上面的五個接口,這種方式將會致使大量的重複代碼出現,顯然,咱們的程序還有不少能夠優化的地方。

請同窗們思考,若是是你,你將會如何進行優化呢?

好了,同窗們,結合剛剛上課是給你們回顧的混合類和多繼承,咱們是否可使用下面的方式呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class GetAllData():
def get(self, request):pass


class GetOneData():
def get(self, request, nid):pass


class DeleteOneData():
def delete(self, request, nid):pass


class UpdateOneData():
def put(self, request, nid):pass


class CreateData():
def post(self, request):pass


class BookView(APIView, GetAllData, CreateData):pass


class BookFilterView(APIView, GetOneData, DeleteOneData, UpdateOneData):pass

將每一個接口都寫到獨立的類中,而後使用多繼承,或者成爲mixin的這種方式,就能夠對咱們的程序進行優化,mixin的方式很是常見,在學網絡編程的時候,若是你看過socketserver源碼,就會發現,socketserver中就有對mixin的實現,即,假設咱們須要進程的時候,咱們繼承進程類,若是咱們須要線程的時候,咱們繼承線程類便可。

接下來,咱們一塊兒來看看DRF是如何作的,其餘,它的解決方式與咱們上面的方式的思路是同樣的。

使用mixin優化接口邏輯

mixin的使用方式介紹

urls.py有些區別:

1
2
3
4
5
6
7
8
from django.urls import re_path

from mixiner import views

urlpatterns = [
re_path(r'books/$', views.BookView.as_view()),
re_path(r'books/(?P<pk>\d+)/$', views.BookFilterView.as_view()),
]

views.py

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
32
33
34
35
36
37
38
39
40
41
42
43
# -*- coding: utf-8 -*-
from rest_framework.mixins import (
ListModelMixin,
CreateModelMixin,
UpdateModelMixin,
DestroyModelMixin,
RetrieveModelMixin
)
from rest_framework.generics import GenericAPIView

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

# Create your views here.


class BookView(ListModelMixin, CreateModelMixin, GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer

def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)


class BookFilterView(DestroyModelMixin,
UpdateModelMixin,
RetrieveModelMixin,
GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer

def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)

def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)

def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)

DRF將這五個接口定義在不一樣的ModelMixin中,使用步驟以下:

  • 導入ModelMixin
  • 視圖類繼承所需的ModelMix
  • 再也不繼承APIView,須要繼承generics.GenericAPIView
  • 必須包含兩個類變量:queryset,serializer_class
  • 接口中不須要定義任何邏輯操做,一切交給mixin
  • 每一個接口的返回值不一樣,對應關係:{「get」: 「list」, 「delete」: 「destroy」, 「put」: 「update」, 「get」: 「retrieve」, 「post」: 「create」}

其中有兩個get方法,可是分屬於不一樣的視圖類,請注意在url中的不一樣點,由於咱們統一給的都是QuerySet,因此,須要經過傳入一個名爲pk的命名參數,告訴視圖組件,用戶須要操做的具體數據。

mixin源碼剖析

到底它是如何作的呢?咱們來簡單剖析一下源碼:

  • Django程序啓動,開始初始化,讀取urls.py, 讀取settings, 讀取視圖類
  • 執行as_views(), BookView沒有,須要到父類中找
  • 幾個ModelMixin也沒有,GenericAPIView中沒有,繼續到GenericAPIView(APIView)中找
  • 找到了,而且與以前的邏輯是同樣的,同時咱們發現GenericAPIView中定義了查找queryset和serializer_class類的方法
  • as_view()方法返回從新封裝的視圖函數,開始創建url和視圖函數之間的映射關係
  • 等待用戶請求
  • 接收到用戶請求,根據url找到視圖函數
  • 執行視圖函數的dispatch方法(由於視圖函數的返回值是:return self.dispatch()
  • dispatch分發請求,查找到視圖類的五個方法中的某個
  • 開始執行,好比post請求,返回:self.create(),視圖類自己沒有,則會到父類中查找
  • 最後在CreateModelMixin中查找
  • 執行create()方法,獲取queryset和serializer_class
  • 返回數據

在對單條數據進行操做的幾個方法裏面,好比retrieve,會執行get_object()方法,該方法會根據lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field來查找操做對象,咱們能夠經過修改self.lookup_url_kwarg變量名來自定義參數。

好了,以上就是mixin的源碼剖析。

使用view優化接口邏輯

view的使用方式介紹

看似已經優化的很是完美了,可是,在一個對性能要求極高的項目裏面,咱們的程序還能夠繼續優化,還須要繼續優化,不斷的優化咱們寫的程序,是每一個程序員必備的技能,也是幫助咱們成長的很是重要的手段。一樣的思路,一樣的方式,咱們能夠將多個接口封裝到一個功能類中,請看下面的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- coding: utf-8 -*-
from rest_framework import generics

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

# Create your views here.


class BookView(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer


class BookFilterView(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer

是否是很是簡單,你想到了嗎?

使用viewset優化接口邏輯

這樣就結束了嗎?哈哈哈,仍是那句話,看似已經優化的很是完美了,可是,在一個對性能要求極高的項目裏面,咱們的程序還能夠繼續優化,還須要繼續優化,不斷的優化咱們寫的程序,是每一個程序員必備的技能,也是幫助咱們成長的很是重要的手段。

viewset的使用方式介紹

urls.py有變化哦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.urls import re_path

from viewsetter import views

urlpatterns = [
re_path(r'books/$', views.BookView.as_view({
'get': 'list',
'post': 'create'
})),
re_path(r'books/(?P<pk>\d+)/$', views.BookView.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
})),
]

咱們給as_view()方法傳遞了參數,這就是最神奇的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding: utf-8 -*-
# django rest framework組件
from rest_framework.viewsets import ModelViewSet

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

# Create your views here.


class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer

使用方式很是簡單,接下來,咱們直接看源碼。

viewset源碼剖析
  • Django程序啓動,開始初始化,讀取urls.py, 讀取settings, 讀取視圖類
  • 執行as_views(), BookView沒有,須要到父類(ModelViewSet)中找
  • ModelViewSet繼承了mixins的幾個ModelMixin和GenericViewSet,顯然ModelMixin也沒有,只有GenericViewSet中有
  • GenericViewSet沒有任何代碼,只繼承了ViewSetMixin和generics.GenericAPIView(這個咱們已經認識了)
  • 繼續去ViewSetMixin中查找,找到了as_view類方法,在從新封裝view函數的過程當中,有一個self.action_map = actions
  • 這個actions就是咱們給as_view()傳遞的參數
  • 綁定url和視圖函數(actions)之間的映射關係
  • 等待用戶請求
  • 接收到用戶請求,根據url找到視圖函數
  • 執行視圖函數的dispatch方法(由於視圖函數的返回值是:return self.dispatch()
  • dispatch分發請求,查找到視圖類的五個方法中的某個
  • 開始執行,好比post請求,返回:self.create(),視圖類自己沒有,則會到父類中查找
  • 最後在CreateModelMixin中查找
  • 執行create()方法,獲取queryset和serializer_class
  • 返回數據

這就是viewset的優化方案,整個優化方案最重要的地方就是urls.py中咱們傳入的參數,而後對參數進行映射關係綁定。

今天的所有內容到此就結束了。

今日總結

  • serializer進行put、delete及獲取單條數據的接口設計
  • 視圖組件的使用及源碼剖析

                                    轉自:pizzali

相關文章
相關標籤/搜索