不斷的優化咱們寫的程序,是每一個程序員必備的技能和職業素養,也是幫助咱們成長的很是重要的手段。git
引入
經過上一節課的學習,咱們已經經過DRF的序列化組件,設計出了符合rest規範的GET、POST接口邏輯,咱們知道操做數據庫主要有增刪改查幾種方式,增長,查詢兩種接口邏輯咱們都實現完畢了,接下來,咱們繼續實現剩下的兩種,刪除和修改。程序員
今日概要
- serializer進行put、delete及獲取單條數據的接口設計
- 視圖組件的使用及源碼剖析
知識點複習回顧
- 昨日回顧
- RESTful api接口規範
- 混入類、多繼承
- 函數的參數
在開始以前,按照慣例,咱們複習前兩節課學習過的知識點、以及學習今天的新知識點所需掌握的知識點。github
知識點複習回顧一:RESTful api接口規範
第一節課的那張圖你們還有印象嗎?若是沒有印象,請同窗們思考一下,我給你們總結的REST的最重要的一句話,那就是:url用來惟必定位資源,http請求方式用來定位用戶行爲。數據庫
根據這句話,咱們設計了下面的RESTful api:django
![view](http://static.javashuo.com/static/loading.gif)
上面就是關於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) |
今日詳細
好了,知識點的複習和補充,我們就到這裏,接下來,正式開始今天的內容,首先開始設計剩下三個接口邏輯。
使用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): |
使用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
|
|
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
|
|
是否是很是簡單,你想到了嗎?
使用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()方法傳遞了參數,這就是最神奇的地方。
使用方式很是簡單,接下來,咱們直接看源碼。
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