在學習django rest framework(下面簡稱drf)以前須要知道python
在學習drf
以前的時候,先簡單說一下須要的預備知識。在django
中,路由匹配以後,會進行路由分發,這個時候會有兩種選擇模式的選擇。也就是FBV
與CBV
。數據庫
fbv就是在url中一個路徑對應一個函數django
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/', views.index) ]
在視圖函數中編程
def index(request): return render(request, 'index.html')
cbv就是在url中一個路徑對應一個類,drf主要使用CBVjson
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/', views.IndexView.as_view()) # 執行類後面的as_view()方法,是父類裏面的方法 ]
在視圖函數中api
from django.views import View class IndexView(View): # 以get形式訪問會執行get函數,通常狀況下獲取數據 def get(self, *args, **kwargs): return HttpResponse('666') # 以post形式訪問的話會執行post函數,通常狀況下發送數據 def post(self, *args, **kwargs): return HttpResponse('999')
咱們在路由匹配的時候看到url(r'^index/', views.IndexView.as_view())
,那這個as_view()
是什麼,既然咱們在視圖類中沒有定義這個as_view()
方法,就應該到父類(也就是IndexView的父類View)中看一下View
。如下是django源碼,路徑是\django\views\generic\base.py,瀏覽器
class View: http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] # 支持的各類http方法 def __init__(self, **kwargs): pass @classonlymethod def as_view(cls, **initkwargs): # url路由匹配進入as_view方法 def view(request, *args, **kwargs): return self.dispatch(request, *args, **kwargs) # 返回dispath方法 return view def dispatch(self, request, *args, **kwargs): # dispath方法是drf的關鍵,dispath方法會經過反射,經過請求的方法,分發到各個視圖類的方法中 pass
所以根據CBV和FBVdjango的生命週期能夠又兩類restful
FBV有區別了,由於再也不是試圖函數而是視圖類,說的詳細一點,先通過父類
View的
dispath方法,進行請求方法的判斷,在分發到視圖類的方法,鏈接數據庫
ORM操做,模板渲染,返回通過中間件,最終交給瀏覽器response字符串。而再drf中主要使用CBV,生命週期就變成了以下
請求經過uwsgi網關,中間件,而後進入路由匹配,這裏就有區別了,先通過drf
中APIView類中的
dispath
方法(這裏假定視圖類沒有重寫APIView
中的dispath
方法),在dispath
中對request請求進行封裝,反射回到視圖類,鏈接數據庫ORM
操做,模板渲染,返回通過中間件,最終交給瀏覽器響應字符串。cookie
說到面向對象就是三個特性,封裝,多態,繼承。
咱們在繼承父類的時候每每會重寫父類中的方法,例如
class A: def get_name(self): return self.name def return_name(self): if hasattr(self, 'name'): return 'name: ' + getattr(self, 'name', None) class B(A): name = "b" def get_name(self): return self.name b = B() b.get_name() # 輸出B b.return_name() # 輸出name: B,這裏因爲B類中沒有實現return_name方法,實例化B獲得b以後,會調用父類A中的return_name方法,hasattr方法會查找類中是否有name屬性,這裏雖然在類A中沒有,會向下查找B類中是否有name屬性,而後返回'name: ' + getattr(self, 'name', None) ,也就是name:b
這是簡單的子類方法重寫父類中的方法,咱們再使用drf的認證,權限等組件是會常常對父類中的方法重寫,從而細粒度的實現本身的功能。
請注意:事情每每不是絕對的,若是像重寫python
內置的基本數據類型,如字典,列表中的特殊方法,就會的到意想不到的結果,就是實例化的對象再也不調用你重寫的方法,而是調用原本的方法。這是由於python
的一些基本類型的方法是由c語言編寫的,python
爲了知足速度,抄近道不會再調用你重寫的特殊方法。
class X(object): def f(self): print( 'x') class A(X): def f(self): print('a') def extral(self): print('extral a') class B(X): def f(self): print('b') def extral(self): print( 'extral b') class C(A, B, X): def f(self): super(C, self).f() print('c') print(C.mro()) c = C() c.f() c.extral()
這樣作也能夠輸出結果
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.X'>, <class 'object'>] # 繼承的順序是 A-->B-->X-->object 這了的object在python3中是一切類的基類,包括object類自己。 a c extral a # 雖然類C中沒有實現接口extral(),卻調用了父類A中的extral()方法
這樣的繼承雖然能夠實現功能,可是有一個很明顯的問題,那就是在面向對象中,必定要指明一個類究竟是什麼。也就是說,若是我想構造一個類,假如是Somthing
,那麼我想讓這個類實現會飛,會游泳,會跑,三種行爲,我能夠這樣作,同時繼承,鳥,魚,馬三個類,像這樣
class Bird: def fly(self): print('fly') class Fish: def swim(self): print('swim') class Horse: def run(self): print('run') class Something(Bird, Fish, Horse): pass s = Something() s.fly() s.swim() s.run()
輸出
fly swim run
但是實現會跑,會飛,會游泳的三種行爲,可是這個類究竟是什麼,是魚,是馬,仍是鳥,也就是說不知道Something究竟是個什麼類。爲了解決這個問題,咱們能夠引用mixin
模式。改寫以下
class BirdMixin: def fly(self): print('fly') class FishMixin: def swim(self): print('swim') class Horse: def run(self): print('run') class Something(BirdMixin, FishMixin, Horse): pass
這樣就解決了上面的問題,也許你會發現,這其實沒有什麼變化,只是在類的命名加上了以Mixin
結尾,其實這是一種默認的聲明,告訴你,Something
類實際上是一種馬,父類是Horse
Horse,繼承其餘兩個類,只是爲了調用他們的方法而已,這種叫作mixin
模式,在drf
的源碼種會用到。
例如drf
中的generics
路徑爲rest_framework/generics.py
class CreateAPIView(mixins.CreateModelMixin, GenericAPIView): pass class ListAPIView(mixins.ListModelMixin, GenericAPIView): pass class RetrieveAPIView(mixins.RetrieveModelMixin, GenericAPIView): pass
至關於每多一次繼承,子類可調用的方法就更多了。
這裏可使用pycharm做爲集成開發工具,建立django項目查看Python和第三方庫源碼很方便,使用pycharm建立一個django項目,而後將django rest framework
做爲第三方包放入django項目中
先來看一下若是不使用drf
怎麼進行用戶認證,一般是用字段驗證的方式,來生成相應的數據庫,在用戶登陸時候,對數據庫查詢,簡單的數據庫設計以下
from django.db import models class UserInfo(models.Model): USER_TYPE = ( (1,'普通用戶'), (2,'VIP'), (3,'SVIP') ) user_type = models.IntegerField(choices=USER_TYPE, default=1) username = models.CharField(max_length=32) password = models.CharField(max_length=64) class UserToken(models.Model): user = models.OneToOneField(UserInfo,on_delete=models.CASCADE) token = models.CharField(max_length=64)
簡單的用戶信息,每一個用戶關聯一個一對一的usertoken作爲驗證
而後在項目目錄下執行生成數據庫命令
python manage.py makemigrations python manage.py migrate
from django.contrib import admin from django.urls import path from django.conf.urls import url from api.views import AuthView urlpatterns = [ path('admin/', admin.site.urls), url(r'^api/v1/auth/$', AuthView.as_view()) ]
api/v1/auth/中的api分別表明接口和版本號,後面會說到
字符串,存儲到數據庫
暫時沒有放回瀏覽器端,真正項目中能夠寫入到瀏覽器cookie
中from django.shortcuts import render, HttpResponse from django.http import JsonResponse from django.views import View from api import models def md5(user): import hashlib import time # 當前時間,至關於生成一個隨機的字符串 ctime = str(time.time()) # token加密 m = hashlib.md5(bytes(user, encoding='utf-8')) m.update(bytes(ctime, encoding='utf-8')) return m.hexdigest() class AuthView(View): def get(self, request, *args, **kwargs): ret = {'code': 1000, 'msg': 'success', 'name': '偷偷'} ret = json.dumps(ret, ensure_ascii=False) return HttpResponse(ret) def post(self, request, *args, **kwargs): ret = {'code': 1000, 'msg': None} try: user = request.POST.get('username') pwd = request.POST.get('password') obj = models.UserInfo.objects.filter(username=user).first() if not obj: # 若是用戶第一次登錄則建立用戶 obj = models.UserInfo.objects.create(username=user, password=pwd) ret['code'] = 1001 ret['msg'] = '建立用戶成功' # 爲用戶建立token token = md5(user) # 存在就更新,不存在就建立 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret)
第一次發送請求
返回請求信息
第二次發送請求
返回請求信息
這裏沒有使用drf的認證組件
假如用戶想獲取本身的訂單信息,發送請求以後返回訂單信息以json
格式的數據返回。
from rest_framework.views import APIView from django.http import JsonResponse from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions from api import models # 這裏直接表示訂單 ORDER_DICT = { 1:{ 'name':'apple', 'price':15 }, 2:{ 'name':'狗子', 'price':100 } } class FirstAuthenticate(BaseAuthentication): # 添加本身的認證邏輯,基類BaseAuthentication中有一個必需要重寫的接口 def authenticate(self, request): pass def authenticate_header(self, request): pass class MyAuthenticate(BaseAuthentication): # 添加本身的認證邏輯,基類BaseAuthentication中有兩個必需要重寫的接口 def authenticate(self, request): token = request._request.GET.get('token') # 獲取token參數 token_obj = models.UserToken.objects.filter(token=token).first() # 在數據庫UserToken查找是否有相應的對象 if not token_obj: # 若是沒有,則報錯 raise exceptions.AuthenticationFailed('用戶認證失敗') return (token_obj.user, token_obj) # 這裏須要返回兩個對象,分別是UserInfo對象和UserToken對象 def authenticate_header(self, request): # 返回相應頭信息 pass class OrderView(APIView): # 用戶想要獲取訂單,就要先經過身份認證、 # 這裏的authentication_classes 就是用戶的認證類 authentication_classes = [FirestAuthenticate, MyAuthenticate] def get(self, request, *args, **kwargs): ret = { 'code': 1024, 'msg': '訂單獲取成功', } try: ret['data'] = ORDER_DICT except Exception as e: pass return JsonResponse(ret)
這裏繼承了rest framek
中的APIView
,在APIView
中將原生的request
進行了封裝,封裝了一些用於認證,權限的類,在請求來的時候,會依次經過FirestAuthenticate
, MyAuthenticate
兩個類,並調用authenticate
進行認證。
發送請求
返回訂單的數據
認證成功
這裏推薦使用pycharm做爲集成開發工具,能夠ctrl+鼠標左鍵
點擊方法,或者類直接進入源碼查看
在路由匹配以後會先進入到APIView
中的as_view
方法中,而後進入到django
的View
中,
因爲子類APIView
已經實現了dispath
方法,接着返回APIView
中的disapth
方法
而後會發現drf
對原生request
作的操做
這裏的initialize_request
,主要進行封裝
而initial則會對調用封裝類中的方法,實現各類功能
至此能夠看到request
在drf
中大概的流程。
在上面第4步和第5步能夠看到APIView
中的兩個方法的initialize_request,initial
咱們進入到initialize_request
,查看authenticators=self.get_authenticators()
這裏的authentication_classes
,實際上是一個全部認證類的集合(指的是一個能夠迭代的容器對象,如list,tuple
等,而不是特指set()
內置類型),
這裏的api_settings
其實就是django
項目的全局配置文件settings.py
,這說明咱們能夠在須要認證的視圖函數多的狀況下使用全局配置使得每個進行認證。
能夠直接在settings.py
中添加全局配置項
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'], }
那麼若是個人個別視圖類不想認證呢?能夠這樣寫
class OrderView(APIView): # 這裏沒有重寫authentication_classes屬性,則使用全局配置的authentication_classes,即在setting.py中的authentication_classes。 def get(self, request, *args, **kwargs): pass class CartView(APIView): authentication_classes = [authenticate.FirstAuthenticate,] # authentication_classes中只包含FirstAuthenticate,則只經過他的認證 def get(self, request, *args, **kwargs): pass class UserInfoView(APIView): authentication_classes = [] # authentication_classes爲空,則不會進行認證 def get(self, request, *args, **kwargs): pass
上面說了想要定義多個認證規則,其實就是封裝多個認證類,那麼這些認證類如何進行認證呢?
這裏的
request
類中有一個
_authenticate
來分析下源碼
def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ for authenticator in self.authenticators: # 找到 authentication_classes,並循環每個認證類 try: user_auth_tuple = authenticator.authenticate(self) # 調用認證類的authenticate方法,也就是上面咱們實現的方法,並將返回值賦值給user_auth_tuple except exceptions.APIException: self._not_authenticated() # 若是出錯調用_not_authenticated,方法,下面會說到 raise if user_auth_tuple is not None: # 若是authenticate方法的返回值不爲空 self._authenticator = authenticator self.user, self.auth = user_auth_tuple # 這也就是爲何認證類的authenticate方法會返回兩個對象的緣由 return self._not_authenticated() # 若是沒有經過認證,則調用_not_authenticated方法 def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """ self._authenticator = None if api_settings.UNAUTHENTICATED_USER: self.user = api_settings.UNAUTHENTICATED_USER() else: self.user = None if api_settings.UNAUTHENTICATED_TOKEN: self.auth = api_settings.UNAUTHENTICATED_TOKEN() else: self.auth = None
_authenticate
方法中調用authenticator.authenticate(self)
方法,返回給user_auth_tuple
,並經過判斷user_auth_tuple
是否爲空,其實就像是我從瀏覽器發送請求,request
中攜帶個人用戶認證信息,在進入視圖類以前,經過一次一次調用認證類來查看我攜帶的認證信息是否正確,若是正確則返回數據庫中正確的User對象。若是不經過或者沒有認證信息,則在_not_authenticated
中按照匿名用戶處理。
來看一下authenticator.authenticate(self)
中的authenticate(self)
具體作了什麼
在authenticate中能夠添加具體的認證邏輯,固然也能夠在視圖類中書寫,可是drf
中提供的組件,可使得代碼耦合度更低,維護性更強,更方便。
上面_not_authenticated
的UNAUTHENTICATED_TOKEN
,UNAUTHENTICATED_USER
說明,也能夠經過在setting.py
中定義匿名用戶的認證。
只要再setting.py中添加以下
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'], "UNAUTHENTICATED_USER": None, # 匿名,request.user = None "UNAUTHENTICATED_TOKEN": None,# 匿名,request.auth = None }
要理解django rest framework
,就要先理解面向對象。子類繼承父類屬性和方法,而在基類中每每以定義抽象接口的形式,強制使子類重寫抽象接口。不過抽象接口這每每是框架開發者作的,而不是咱們要須要作的。實例化的對象能夠調用所類的屬性和方法,其實方法也能夠看做是一種屬性。子類新定義或者重寫父類的屬性,實例化的對象能夠調用父類中的方法查詢到子類的屬性,就是說實例化的對象集全部父類子類於一身。子類中的方法或者屬性會覆蓋掉父類中的方法和屬性,實例化對象調用的時候不會管父類中怎麼樣,因此在變量和方法命名的時候應該注意,或者也可使用super
等操做。
而在django rest framework
中,對原生request
作了封裝。本來咱們能夠再視圖類中的進行的好比訪問限流,用戶認證,權限管理等邏輯,封裝到一個一個類中的方法中,在用戶請求進入視圖類以前,會先查找並迭代相關封裝的類,而後調用這些類的相關方法,根據返回值判斷是否知足認證,權限等功能。若是不經過則不會進入到視圖類執行下一步,並返回相應的提示信息。這樣分開的好處是固然就是最大程度的解耦,各個相關功能相互不影響,又相互關聯,維護性更高。