DRF之檢視元件
不斷的優化我們寫的程式,是每個程式設計師必備的技能和職業素養,也是幫助我們成長的非常重要的手段。
引入
通過上一節課的學習,我們已經通過DRF的序列化元件,設計出了符合rest規範的GET、POST介面邏輯,我們知道操作資料庫主要有增刪改查幾種方式,增加,查詢兩種介面邏輯我們都實現完畢了,接下來,我們繼續實現剩下的兩種,刪除和修改。
今日概要
- serializer進行put、delete及獲取單條資料的介面設計
- 檢視元件的使用及原始碼剖析
知識點複習回顧
- 昨日回顧
- RESTful api介面規範
- 混入類、多繼承
- 函式的引數
在開始之前,按照慣例,我們複習前兩節課學習過的知識點、以及學習今天的新知識點所需掌握的知識點。
知識點複習回顧一:RESTful api介面規範
第一節課的那張圖大家還有印象嗎?如果沒有印象,請同學們思考一下,我給大家總結的REST的最重要的一句話,那就是:url用來唯一定位資源,http請求方式用來定位使用者行為。
根據這句話,我們設計了下面的RESTful api:
上面就是關於REST規範的回顧,請牢記:url用來唯一定位資源,http請求方式用來定位使用者行為。
知識點複習回顧二:混入類、多繼承
我有一個Animal類,它包含如下方法:
1 | class Animal(object): |
可以看到,Dog類繼承了Animal類,但是Dog並沒有飛行和喵的功能,所以,如果直接繼承Animal會有一些問題,請同學們思考,如何解決這個問題呢?
好了,其實我們有多中方式可以解決這個問題,比如:
1 |
class Animal(object): |
我們將不同的功能封裝到了獨立的類中,然後採用一種Mixin(混合類)的方式,其實也就是多繼承,來解決這個問題,這在Python中是比較常見的解決方式,比如socketserver模組就用到了這種方式,當我們需要執行緒的時候,可以繼承執行緒類,當我們需要程序的時候,可以繼承程序類。
知識點複習回顧三:函式的引數
接下來,我們一起回顧一下函式的引數,假設我有函式如下:
1 |
def func(a, b, c=1, *args, **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 |
from rest_framework.views import APIView |
請注意,在序列化時,我們除了傳入data引數外,還需告訴序列化元件,我們需要更新哪條資料,也就是instance,另外,我們使用的序列化類還是之前那個:
1 |
class BookSerializer(serializers.ModelSerializer): |
使用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 |
class BookFilterView(APIView): |
用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 |
class BookFilterView(APIView): |
many=False, 當然,也可以不傳這個引數,因為預設是False。通過POSTMAN傳送請求,成功。三個介面定義完成了,加上上一節課的get和post,兩個檢視類的介面邏輯如下:
1 |
class BookView(APIView): |
到此為止,我們已經通過序列化元件設計出了符合REST規範的五個常用介面,已經足夠優秀了,但是還不夠完美,現在假設,我們有多個數據介面,比如(Book,Author,Publish…..)等資料表都需要定義類似的介面,可以預見,我們需要重複定義類似上面的五個介面,這種方式將會導致大量的重複程式碼出現,顯然,我們的程式還有很多可以優化的地方。
請同學們思考,如果是你,你將會如何進行優化呢?
好了,同學們,結合剛剛上課是給大家回顧的混合類和多繼承,我們是否可以使用下面的方式呢?
1 |
class GetAllData(): |
將每個介面都寫到獨立的類中,然後使用多繼承,或者成為mixin的這種方式,就可以對我們的程式進行優化,mixin的方式非常常見,在學網路程式設計的時候,如果你看過socketserver原始碼,就會發現,socketserver中就有對mixin的實現,即,假設我們需要程序的時候,我們繼承程序類,如果我們需要執行緒的時候,我們繼承執行緒類即可。
接下來,我們一起來看看DRF是如何做的,其他,它的解決方式與我們上面的方式的思路是一樣的。
使用mixin優化介面邏輯
mixin的使用方式介紹
urls.py有些區別:
1 |
from django.urls import re_path |
views.py
1 |
# -*- coding: utf-8 -*- |
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 |
# -*- coding: utf-8 -*- |
是不是非常簡單,你想到了嗎?
使用viewset優化介面邏輯
這樣就結束了嗎?哈哈哈,還是那句話,看似已經優化的非常完美了,但是,在一個對效能要求極高的專案裡面,我們的程式還可以繼續優化,還需要繼續優化,不斷的優化我們寫的程式,是每個程式設計師必備的技能,也是幫助我們成長的非常重要的手段。
viewset的使用方式介紹
urls.py有變化哦:
1 |
from django.urls import re_path |
我們給as_view()方法傳遞了引數,這就是最神奇的地方。
1 |
# -*- coding: utf-8 -*- |
使用方式非常簡單,接下來,我們直接看原始碼。
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及獲取單條資料的介面設計
- 檢視元件的使用及原始碼剖析