四十二、python學習之Django框架(二):類檢視與中介軟體
五、類檢視:
1.類檢視引入:
以函式的方式定義的檢視成為函式檢視,即我們常說的檢視函式.
但是,
檢視函式遭遇不同的請求方法(如get和post),並且需要做不同的處理時,我們如果在一個函式中編寫不同的業務邏輯,程式碼可讀性和複用性都不好.
例如:
我們建立一個新的子應用:demoview
python manage.py startapp demoview
在demoview中配置路由部分(urls.py)
from django.conf.urls import url from . import views urlpatterns = [ # 新增一個路由檢視 url(r"^indexview/", views.indexview) ]
在demoview中配置檢視函式部分(views.py)
from django.http import HttpResponse # 建立糊塗函式indexview def indexview(request): # 獲取請求方法,判斷是GET/POST請求 if request.method == "GET": # 根據不同的請求方式,做不同的操作處理 return HttpResponse("indexview get function") else: # 根據不同的請求方式,做不同的操作處理 return HttpResponse("indexview post function")
總結髮現:
上例雖然可以完成根據不同的請求進行不同處理的目的, 但是我們這樣沒有做到分離處理, get和post的處理方式還是放在一個檢視函式內部的, 而且也不利於我們後面內容的擴充套件.所以我們會對它進行改造:
- 在Django中給我們提供了類檢視的概念.
- 即可以使用類來定義一個檢視,解決上面的問題.
總結: 使用類來定義的檢視, 稱為類檢視.
2.類檢視使用:
如果想要使用類檢視需要如下幾步:
- 定義類檢視, 且類檢視繼承自View
- 使用: from django.views.generic import View
- 或者是: from django.views.generic.base import View
- 定義路由,路由的第二個引數需要是一個函式,所以我們會呼叫系統的as_view()方法.
urlpatterns = [
# 類檢視:註冊
url(r'^register/$',views.RegisterView.as_view()),
]
使用瀏覽器訪問我們定義的路由, 檢視結果:
使用類檢視可以將檢視對應的不同請求方式以類中的不同方法區別定義.
程式碼例項:
# 匯入類檢視的父類View
from django.views.generic import View
class RegisterView(View):
"""類檢視:處理註冊"""
def get(self, request):
"""處理GET請求,返回註冊頁面"""
return render(request, 'register.html')
def post(self, request):
"""處理POST請求,實現註冊邏輯"""
return HttpResponse('這裡實現註冊邏輯')
類檢視的好處:
- 程式碼可讀性好
- 類檢視相對於函式檢視有更高的複用性
-
- 如果其他地方需要用到某個類檢視的某個特定邏輯,直接繼承該類檢視即可
但是這樣寫完還不行, 因為我們還需要定義路由部分:
- 如果其他地方需要用到某個類檢視的某個特定邏輯,直接繼承該類檢視即可
urlpatterns = [
# 注意:
# url(路徑, 執行的函式)
# url的第二個引數需要是一個函式
# 我們這裡如果傳入: views.RegisterView 會發現這個是一個類, 不是一個函式,
# 所以我們需要呼叫系統給我們提供的 as_view() 方法
url(r'^registerview/$', views.RegisterView.as_view())
]
注意:
- 如果我們在類檢視函式中沒有定義方法, 但是我們請求了. 會報405找不到請求方法的錯誤.
-
- 例如: 類檢視中沒有定義Get方法, 但是我們使用Get方法進行了請求, 那麼會報405的錯誤: 找不到對應的請求方法.
- 在類檢視中定義的 Get 或者是 POST 都是物件方法, 第一個引數都是self.
- 第二個引數一般情況下都是 request物件. 其他的引數依次往後面寫就可以.
- 我們在使用類檢視的時候, 需要在路由位置進行設定, 設定的第二個引數需要是一個函式, 所以我們這裡呼叫了類以後, 後面需要呼叫 as_view( ) 函式.
效果演示:
3.類檢視原理:
為什麼我們定義url的時候, 呼叫 as_view() 函式,就可以達到結果, 如果不呼叫就會報錯.
到底 as_view() 幫助我們幹了什麼了?
@classonlymethod
def as_view(cls, **initkwargs):
...省略程式碼...
def view(request, *args, **kwargs):
# 這裡的cls是as_view這個函式接收的第一個引數,也就是呼叫當前函式的類.
# 得到呼叫的類了之後, 建立類的物件: self
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
# 呼叫dispatch方法,按照不同請求方式呼叫不同請求方法
return self.dispatch(request, *args, **kwargs)
...省略程式碼...
# 返回真正的函式檢視
return view
# dispatch本身是分發的意思,這裡指的是函式的名字.
def dispatch(self, request, *args, **kwargs):
# self.http_method_names指的是我們的類檢視中,物件方法的名字
# 這裡把所有方法的名字都存放在了http_methods_names中
# 我們會把當前請求的方式轉為小寫,然後判斷是否在列表中存在.
if request.method.lower() in self.http_method_names:
# 如果在裡面, 則進入這裡
# 這裡的getattr作用是獲取當前物件的屬性.
# 下面的引數為:
# self : 類檢視物件
# request.method.lower() : 請求方法的小寫. 例如: 'get' 或 'post'
# http_method_not_allowed : 後續處理方式(不允許請求)
# 下面程式碼整體的意思: 根據類檢視物件, 獲取當前類檢視中對應名稱的方法
# 如果獲取到, 則把方法返回給handle, 否則不允許訪問.
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 如果類檢視中如果沒有的話, 則進入這裡, 表明不允許進行請求.
# 我們會把不允許請求這個欄位返回給handle.
handler = self.http_method_not_allowed
# 最終返回handle(handle裡面要麼包含可以訪問的方法, 要麼就是不允許訪問的欄位)
return handler(request, *args, **kwargs)
4.類檢視使用裝飾器:
日常生活中, 我們很多時候都會使用裝飾器修飾檢視. 那麼, 如果我們定義了一個類檢視, 它是否可以使用裝飾器進行修飾呢?
為了理解方便,我們先來定義一個為函式檢視準備的裝飾器(在設計裝飾器時基本都以函式檢視作為考慮的被裝飾物件),以及一個要被裝飾的類檢視, 來說明一下給類新增裝飾器的實現要點.
程式碼例項:
# 定義一個裝飾器
# func = my_decorator(func)
def my_decorator(func):
# wrapper函式必然會接收一個request物件,因為傳入進來的func這個函式肯定會帶這個引數
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求路徑%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 我們定義的類檢視
class DemoView(View):
# 我們給get方法新增上裝飾器, 然後執行.
@my_decorator
def get(self, request):
print('get方法')
return HttpResponse('ok')
# 類視圖裡面的物件方法: post方法
def post(self, request):
print('post方法')
return HttpResponse('ok')
演示效果:
4.1 在URL配置中修飾:
# 匯入views.py檢視檔案
from . import views
urlpatterns = [
# 我們在路由部分, 把定義好的裝飾器新增到當前的函式上
# 這裡需要注意: as_view() 會返回一個 view() 函式
# 所以我們把裝飾器新增到view()函式上.
url(r'^demo/$', views.my_decorate(views.DemoView.as_view()))
]
執行之後發現, 這樣的方法是可以實現的:
這種方式的弊端:
1.種方式最簡單,但因裝飾行為被放置到了url配置中,單看檢視的時候無法知道此檢視還被添加了裝飾器,不利於程式碼的完整性,不建議使用。
2.此種方式會為類檢視中的所有請求方法都加上裝飾器行為(因為是在檢視入口處,分發請求方式前)。
4.2 回顧一下在類檢視中裝飾器的使用:
如果我們不適用上面的方式, 而是直接把裝飾器作用到類視圖裡面的函式上, 又會出現我們上面呈現的問題, 那麼我們這個問題到底是怎麼來的呢?
我們可以來嘗試一下:
由此我們可以得到結論: 我們不能夠直接給類檢視中的函式新增裝飾器, 否則傳入的引數會出現錯誤.
也就是說, 如果我們能夠解決傳入的引數問題, 那麼我們就可以以這樣的形式來新增裝飾器.
解決方法:
- 由上面的案例我們可知: 我們定義的裝飾器不能夠直接使用到類檢視的方法上.
- 所以我們需要把我們定義的裝飾器進行轉化, 轉化為能夠被類檢視中函式使用的裝飾器
- 轉化的方法需要匯入:
-
- from django.utils.decorators import method_decorator
- 匯入進來之後, 我們需要把自定義的裝飾器用這個方法包裹住轉化, 例如:
-
- @method_decorator(自定義裝飾器)
第一種解決方式:
在類檢視中使用為函式檢視準備的裝飾器時,不能直接新增裝飾器
需要使用method_decorator將其轉換為適用於類檢視方法的裝飾器。
from django.views.generic import View
# 匯入轉換的裝飾器方法:
from django.utils.decorators import method_decorator
# 為特定請求方法新增裝飾器
class DemoView(View):
# 使用轉換的方法將裝飾器轉化:
@method_decorator(my_decorator)
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
顯示效果:
使用post方法的時候::
問題:
- 雖然上面的方式可以解決類檢視新增裝飾器問題, 但是我們這種是給單個函式新增的, 而不是類檢視中的所有函式, 那麼有沒有給所有函式新增裝飾器的方式呢?
- 我們可以對上面的裝飾器方法進行再次改造:
第二種解決方式:
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
# 自定義的裝飾器方法
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 類檢視
class DemoView(View):
# 重寫父類的dispatch方法, 因為這個方法被 as_view() 中的 view() 呼叫
# 所以我們對這個方法新增裝飾器, 也就相當於對整個類檢視的方法新增裝飾器.
@method_decorator(my_decorator)
def dispatch(self, request, *args, **kwargs):
# 重寫父類的這個方法我們不會修改它的任何引數, 所以我們直接呼叫父類的這個方法即可
# 它裡面的引數我們也不動它, 直接還傳遞過去.
return super().dispatch(request, *args, **kwargs)
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
展示效果:
如果剛剛的能夠接受, 那我們再進一步, method_decorator( ) 這個方法其實還可以直接裝飾到類上去. 使當前的檢視類中的某一個函式新增裝飾器方法:
第三種解決方式:
例如:
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 類檢視
# 給類檢視增加上@method_decorator方法
# 增加上之後,並不能夠給其中的某一個函式增加上裝飾器
# 所以我們需要給method_decator配置第二個引數
# 第二個引數就是類中某一個函式的名稱. 意味著給當前這個函式增加上裝飾器.
@method_decorator(my_decorator, name='get')
class DemoView(View):
@method_decorator(my_decorator)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
效果演示:
那麼我們可以進一步聯想: 如果使用@method_decorator在類檢視的位置可以給’get’方法新增裝飾器, 那麼是否意味著也可以給dispatch方法新增呢?
如下所示:
第四種解決方式:
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 類檢視
# 因為我們可以直接給dispatch方法新增裝飾器,意味著, 我們內部不用重寫dispatch方法了.
@method_decorator(my_decorator, name='dispatch')
class DemoView(View):
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
演示效果:
使用method_decorator的原因:
為函式檢視準備的裝飾器,其被呼叫時,第一個引數用於接收request物件
def my_decorate(func):
def wrapper(request, *args, **kwargs): # 第一個引數request物件
...程式碼省略...
return func(request, *args, **kwargs)
return wrapper
而類檢視中請求方法被呼叫時,傳入的第一個引數不是request物件,而是self 檢視物件本身,第二個位置引數才是request物件
class DemoView(View):
def dispatch(self, request, *args, **kwargs):
...程式碼省略...
def get(self, request):
...程式碼省略...
所以如果直接將用於函式檢視的裝飾器裝飾類檢視方法,會導致引數傳遞出現問題。
method_decorator的作用是為函式檢視裝飾器補充第一個self引數,以適配類檢視方法。
如果將裝飾器本身改為可以適配類檢視方法的,類似如下,則無需再使用method_decorator。
def my_decorator(func):
def wrapper(self, request, *args, **kwargs): # 此處增加了self
print('自定義裝飾器被呼叫了')
print('請求路徑%s' % request.path)
return func(self, request, *args, **kwargs) # 此處增加了self
return wrapper
4.3 構造Mixin擴充套件類:
上面的方法已經可以實現給類檢視新增裝飾器的功能了, 那麼還有沒有其他的方式可以給當前的檢視類新增類似的功能呢?
答案是肯定的, 我們現在來看一種全新的新增方式:
使用類擴充套件的形式, 給當前的類檢視新增裝飾器.
我們可以知道, 給類檢視所有方法新增裝飾器有兩種:
- 修改裝飾器接收到的引數
-
- 即把裝飾器的第一個引數修改為self. 使類中所有的方法都可以直接新增上裝飾器.
- 修改類檢視中所有函式都會呼叫的 as_view() 方法
-
- 或者是 dispatch方法, 因為dispatch方法在 as_view() 方法內部,故我們這裡不討論dispatch.
那麼我們可以繼續思考:
類檢視 —————> 繼承自View類 (包含有as_view函式)
如果我們在整個繼承的過程中新增一步, 例如:
類檢視 ———> 繼承自額外擴充套件的類 ———> 繼承自View類(包含有as_view函式)
如果我們在額外擴充套件的類中, 重寫 as_view( ) 方法, 並且對 as_view( ) 方法新增裝飾器, 那麼我們會發現, 類檢視中的所有方法都會被裝飾器裝飾. 故, 我們要實現這一點, 需要分為兩步:
第一步: 建立一個擴充套件類, 並且讓類檢視繼承自該類.
第二步: 在擴充套件類中重寫 as_view 方法,並且給該方法,新增裝飾器
第一步程式碼:
# 定義一個新的擴充套件類,讓該類繼承自View類
class BaseView(View):
pass
第二步程式碼:
class BaseView(View):
# 在擴充套件類中,重寫View類的 as_view 方法, 並且對該方法新增裝飾器.
@classmethod
def as_view(cls, *args, **kwargs):
# 重寫之後, 不對該方法做其他額外操作,所以我們重新呼叫父類的該方法.
view = super().as_view(*args, **kwargs)
# 對父類傳過來的view方法新增裝飾器.
view = my_decorator(view)
return view
我們原來的類檢視程式碼:
# 讓我們的類檢視繼承自擴充套件類
class DemoView(BaseView):
def get(self, request):
print('get')
return HttpResponse('get func')
def post(self, request):
print('post')
return HttpResponse('post func')
演示效果:
結論:
- 我們發現, 經過中間一層額外擴充套件類的裝飾過濾, 我們原來的DemoView中的所有檢視方法是能夠經過裝飾器的.
那麼我們可以繼續思考:
類檢視 —————> 繼承自View類 (包含有as_view函式)
類檢視 ———> 繼承自額外擴充套件的類1 —> 繼承自額外擴充套件的類2 ———> 繼承自View類(包含有as_view函式)
延伸:
我們定義兩個擴充套件類, 並且重寫兩次 as_view 方法, 來看看會發生什麼 :
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
# 定義的第一個裝飾器:
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 定義的第二個裝飾器:
def my_decorator2(func):
def wrapper(request, *args, **kwargs):
print('自定義裝飾器被呼叫了22222')
print('請求的路徑:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 額外增加的第一個擴充套件類
class BaseView(View):
# 第一次重寫父類中的as_view方法
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
# 對獲取的view第一次新增裝飾器
view = my_decorator(view)
return view
# 額外增加的第二個擴充套件類
class Base2View(View):
# 第二次重寫父類中的as_view方法
@classmethod
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs)
# 對獲取的view進行第二次新增裝飾器
view = my_decorator2(view)
return view
# 我們定義的類檢視, 繼承自兩個額外增加的類
class DemoView(BaseView, Base2View):
# 類檢視中的get方法
def get(self, request):
print('get')
return HttpResponse('get func')
# 類檢視中的post方法
def post(self, request):
print('post')
return HttpResponse('post func')
演示效果:
那麼我們來看一下同時呼叫兩個裝飾器是怎樣實現的:
請看下圖:
說明:
- 如果兩個擴充套件類的父類相同: 則兩個父類都會呼叫
- 如果兩個擴充套件類的父類不同: 則只會呼叫第一個父類
綜上:
我們可以把程式碼變成這個樣子:
# 第一個擴充套件類, 讓他繼承自object
class BaseView(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
view = my_decorator(view)
return view
# 第二個擴充套件類,讓他繼承自object
class Base2View(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
view = my_decorator2(view)
return view
# 類檢視, 讓他除了繼承自這兩個父類外, 最後繼承View類.
class DemoView(BaseView, Base2View, View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
說明:
- 因為都是繼承自object,所以擴充套件類中的super.as_view都會去找其他的父類依次執行,最終都會執行到View這個類這裡, 所以肯定會執行View中的as_view方法.
- 如圖所示:
使用Mixin擴充套件類,也會為類檢視的所有請求方法都新增裝飾行為。
六、中介軟體:
Django中的中介軟體是一個輕量級、底層的外掛系統,可以介入Django的請求和響應處理過程,修改Django的輸入或輸出。中介軟體的設計為開發者提供了一種無侵入式的開發方式,增強了Django框架的健壯性。
我們可以使用中介軟體,在Django處理檢視的不同階段對輸入或輸出進行干預。
1. 中介軟體的定義方法:
定義分為三步:
1.在一個子應用中建立一箇中間件檔案. 例如: middleware(別的名字也可以)
2.在setting.py檔案的MIDDLEWARE部分註冊新增.
3.在呼叫檢視時,便會呼叫中介軟體了.
定義一箇中間件工廠函式,然後返回一個可以呼叫的中介軟體。
中介軟體工廠函式需要接收一個可以呼叫的get_response物件。
返回的中介軟體也是一個可以被呼叫的物件,並且像檢視一樣需要接收一個request物件引數,返回一個response物件。
# 中介軟體模板:
def simple_middleware(get_response):
# 此處編寫的程式碼僅在Django第一次配置和初始化的時候執行一次。
def middleware(request):
# 此處編寫的程式碼會在每個請求處理檢視前被呼叫。
response = get_response(request)
# 此處編寫的程式碼會在每個請求處理檢視之後被呼叫。
return response
return middleware
例如,在users應用中新建一個middleware.py檔案,
def my_middleware(get_response):
print('init 被呼叫')
def middleware(request):
print('before request 被呼叫')
response = get_response(request)
print('after response 被呼叫')
return response
return middleware
定義好中介軟體後,需要在settings.py 檔案中添加註冊中介軟體
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'users.middleware.my_middleware', # 新增中介軟體
]
定義一個檢視進行測試:
def demo_view(request):
print('view 檢視被呼叫')
return HttpResponse('OK')
執行結果:
注意:Django執行在除錯模式下,中介軟體init部分有可能被呼叫兩次。
2 多箇中間件的執行順序
- 在請求檢視被處理前,中介軟體由上至下依次執行
- 在請求檢視被處理後,中介軟體由下至上依次執行
示例:
定義兩個中介軟體:
def my_middleware(get_response):
print('init 被呼叫')
def middleware(request):
print('before request 被呼叫')
response = get_response(request)
print('after response 被呼叫')
return response
return middleware
def my_middleware2(get_response):
print('init2 被呼叫')
def middleware(request):
print('before request 2 被呼叫')
response = get_response(request)
print('after response 2 被呼叫')
return response
return middleware
註冊新增兩個中介軟體
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'users.middleware.my_middleware', # 新增
'users.middleware.my_middleware2', # 新增
]
執行結果
init2 被呼叫
init 被呼叫
before request 被呼叫
before request 2 被呼叫
view 檢視被呼叫
after response 2 被呼叫
after response 被呼叫