在前面的博客中已經學會了給視圖函數加裝飾器來判斷是用戶是否登陸,把沒有登陸的用戶請求跳轉到登陸頁面。咱們經過給幾個特定視圖函數加裝飾器實現了這個需求。可是之後添加的視圖函數可能也須要加上裝飾器,這樣是否是稍微有點繁瑣。html
下面的內容即將讓咱們經過一些特色的方式實現控制全局的效果,指揮若定於室內(室外太熱~~~)python
中間件
介紹
什麼是中間件?
官方的說法:中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的插件系統,用於在全局範圍內改變Django的輸入和輸出。每一箇中間件組件都負責作一些特定的功能。數據庫
可是因爲其影響的是全局,因此須要謹慎使用,使用不當會影響性能。django
說的直白一點中間件是幫助咱們在視圖函數執行以前和執行以後均可以作一些額外的操做,它本質上就是一個自定義類,類中定義了幾個方法,Django框架會在請求的特定的時間去執行這些方法。瀏覽器
咱們一直都在使用中間件,只是沒有注意到而已,打開Django項目的Settings.py文件,看到下圖的MIDDLEWARE配置項。session
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', ]
MIDDLEWARE配置項是一個列表(列表是有序的,記住這一點,後面你就知道爲何要強調有序二字),列表中是一個個字符串,這些字符串實際上是一個個類,也就是一個個中間件。app
咱們以前已經接觸過一個csrf相關的中間件了?咱們一開始讓你們把他註釋掉,再提交post請求的時候,就不會被forbidden了,後來學會使用csrf_token以後就再也不註釋這個中間件了。框架
那接下來就學習中間件中的方法以及這些方法何時被執行。函數
自定義中間件
中間件能夠定義五個方法,分別是:(主要的是process_request和process_response)post
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
以上方法的返回值能夠是None或一個HttpResponse對象,若是是None,則繼續按照django定義的規則向後繼續執行,若是是HttpResponse對象,則直接將該對象返回給用戶。
自定義一箇中間件示例
process_request
process_request有一個參數,就是request,這個request和視圖函數中的request是同樣的(在交給Django後面的路由以前,對這個request對象能夠進行一系列的操做)。
因爲request對象是同樣的,因此咱們能夠對request對象進行一系列的操做,包括request.變量名=變量值,這樣的操做,咱們能夠在後續的視圖函數中經過相同的方式便可獲取到咱們在中間件中設置的值。
它的返回值能夠是None也能夠是HttpResponse對象。返回值是None的話,按正常流程繼續走,交給下一個中間件處理,若是是HttpResponse對象,Django將不執行視圖函數,而將相應對象返回給瀏覽器。
咱們來看看多箇中間件時,Django是如何執行其中的process_request方法的。
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request): print("MD1裏面的 process_request") class MD2(MiddlewareMixin): def process_request(self, request): print("MD2裏面的 process_request") pass
在settings.py的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', 'middlewares.MD1', # 自定義中間件MD1 'middlewares.MD2' # 自定義中間件MD2 ]
此時,咱們訪問一個視圖,會發現終端中打印以下內容:
MD1裏面的 process_request MD2裏面的 process_request app01 中的 index視圖
把MD1和MD2的位置調換一下,再訪問一個視圖,會發現終端中打印的內容以下:
MD2裏面的 process_request MD1裏面的 process_request app01 中的 index視圖
看結果咱們知道:視圖函數仍是最後執行的,MD2比MD1先執行本身的process_request方法。
在打印一下兩個自定義中間件中process_request方法中的request參數,會發現它們是同一個對象。
由此總結一下:
- 中間件的process_request方法是在執行視圖函數以前執行的。
- 當配置多箇中間件時,會按照MIDDLEWARE中的註冊順序,也就是列表的索引值,從前到後依次執行的。
- 不一樣中間件之間傳遞的request都是同一個對象
process_response
多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。
定義process_response方法時,必須給方法傳入兩個形參,request和response。request就是上述例子中同樣的對象,response是視圖函數返回的HttpResponse對象(也就是說這是Django後臺處理完以後給出一個的一個具體的視圖)。該方法的返回值(必需要有返回值)也必須是HttpResponse對象。若是不返回response而返回其餘對象,則瀏覽器不會拿到Django後臺給他的視圖,而是個人中間件中返回的對象
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request): print("MD1裏面的 process_request") def process_response(self, request, response): print("MD1裏面的 process_response") return response class MD2(MiddlewareMixin): def process_request(self, request): print("MD2裏面的 process_request") pass def process_response(self, request, response): print("MD2裏面的 process_response") return response
訪問一個視圖,看一下終端的輸出:
MD2裏面的 process_request MD1裏面的 process_request app01 中的 index視圖 MD1裏面的 process_response MD2裏面的 process_response
看結果可知:
process_response方法是在視圖函數以後執行的,而且順序是MD1比MD2先執行。(此時settings.py中 MD2比MD1先註冊)
多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。
process_view
process_view(self, request, view_func, view_args, view_kwargs)
該方法有四個參數
request是HttpRequest對象。
view_func是Django即將使用的視圖函數。 (它是實際的函數對象,而不是函數的名稱做爲字符串。)
view_args是將傳遞給視圖的位置參數的列表.
view_kwargs是將傳遞給視圖的關鍵字參數的字典。 view_args和view_kwargs都不包含第一個視圖參數(request)。
Django會在調用視圖函數以前調用process_view方法。
它應該返回None或一個HttpResponse對象。 若是返回None,Django將繼續處理這個請求,執行任何其餘中間件的process_view方法,而後在執行相應的視圖。 若是它返回一個HttpResponse對象,那麼將不會執行Django的視圖函數,而是直接在中間件中掉頭,倒敘執行一個個process_response方法,最後返回給瀏覽器
給MD1和MD2添加process_view方法:
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request): print("MD1裏面的 process_request") def process_response(self, request, response): print("MD1裏面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD1 中的process_view") print(view_func, view_func.__name__) class MD2(MiddlewareMixin): def process_request(self, request): print("MD2裏面的 process_request") pass def process_response(self, request, response): print("MD2裏面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD2 中的process_view") print(view_func, view_func.__name__)
訪問index視圖函數,看一下輸出結果:
MD2裏面的 process_request MD1裏面的 process_request -------------------------------------------------------------------------------- MD2 中的process_view <function index at 0x000001DE68317488> index -------------------------------------------------------------------------------- MD1 中的process_view <function index at 0x000001DE68317488> index app01 中的 index視圖 MD1裏面的 process_response MD2裏面的 process_response
process_view方法是在Django路由系統以後,視圖系統以前執行的,執行順序按照MIDDLEWARE中的註冊順序從前到後順序執行的
process_exception
process_exception(self, request, exception)
該方法兩個參數:
一個HttpRequest對象
一個exception是視圖函數異常產生的Exception對象。
這個方法只有在視圖函數中出現異常了才執行,它返回的值能夠是一個None也能夠是一個HttpResponse對象。若是是HttpResponse對象,Django將調用模板和中間件中的process_response方法,並返回給瀏覽器,不然將默認處理異常。若是返回一個None,則交給下一個中間件的process_exception方法來處理異常。它的執行順序也是按照中間件註冊順序的倒序執行。
給MD1和MD2添加上這個方法:
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request): print("MD1裏面的 process_request") def process_response(self, request, response): print("MD1裏面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD1 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD1 中的process_exception") class MD2(MiddlewareMixin): def process_request(self, request): print("MD2裏面的 process_request") pass def process_response(self, request, response): print("MD2裏面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD2 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD2 中的process_exception")
若是視圖函數中無異常,process_exception方法不執行。
想辦法,在視圖函數中拋出一個異常:
def index(request): print("app01 中的 index視圖") raise ValueError("呵呵") return HttpResponse("O98K")
在MD1的process_exception中返回一個響應對象:
class MD1(MiddlewareMixin): def process_request(self, request): print("MD1裏面的 process_request") def process_response(self, request, response): print("MD1裏面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD1 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD1 中的process_exception") return HttpResponse(str(exception)) # 返回一個響應對象
看輸出結果:
MD2裏面的 process_request MD1裏面的 process_request -------------------------------------------------------------------------------- MD2 中的process_view <function index at 0x0000022C09727488> index -------------------------------------------------------------------------------- MD1 中的process_view <function index at 0x0000022C09727488> index app01 中的 index視圖 呵呵 MD1 中的process_exception MD1裏面的 process_response MD2裏面的 process_response
注意,這裏並無執行MD2的process_exception方法,由於MD1中的process_exception方法直接返回了一個響應對象。
process_template_response(用的比較少)
process_template_response(self, request, response)
它的參數,一個HttpRequest對象,response是TemplateResponse對象(由視圖函數或者中間件產生)。
process_template_response是在視圖函數執行完成後當即執行,可是它有一個前提條件,那就是視圖函數返回的對象有一個render()方法(或者代表該對象是一個TemplateResponse對象或等價方法)。
class MD1(MiddlewareMixin): def process_request(self, request): print("MD1裏面的 process_request") def process_response(self, request, response): print("MD1裏面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD1 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD1 中的process_exception") return HttpResponse(str(exception)) def process_template_response(self, request, response): print("MD1 中的process_template_response") return response class MD2(MiddlewareMixin): def process_request(self, request): print("MD2裏面的 process_request") pass def process_response(self, request, response): print("MD2裏面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD2 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD2 中的process_exception") def process_template_response(self, request, response): print("MD2 中的process_template_response") return response
views.py中:
def index(request): print("app01 中的 index視圖") def render(): print("in index/render") return HttpResponse("O98K") rep = HttpResponse("OK") rep.render = render return rep
訪問index視圖,終端輸出的結果:
MD2裏面的 process_request MD1裏面的 process_request -------------------------------------------------------------------------------- MD2 中的process_view <function index at 0x000001C111B97488> index -------------------------------------------------------------------------------- MD1 中的process_view <function index at 0x000001C111B97488> index app01 中的 index視圖 MD1 中的process_template_response MD2 中的process_template_response in index/render MD1裏面的 process_response MD2裏面的 process_response
從結果看出:
視圖函數執行完以後,當即執行了中間件的process_template_response方法,順序是倒序,先執行MD1的,在執行MD2的,接着執行了視圖函數返回的HttpResponse對象的render方法,返回了一個新的HttpResponse對象,接着執行中間件的process_response方法。
2|3****中間件的執行流程
上一部分,咱們瞭解了中間件中的5個方法,它們的參數、返回值以及何時執行,如今總結一下中間件的執行流程。
請求到達中間件以後,先按照正序執行每一個註冊中間件的process_request方法,process_request方法返回的值是None,就依次執行,若是返回的值是HttpResponse對象,再也不執行後面的process_request方法,而是執行當前對應中間件的process_response方法(注意不是掉頭執行全部的process_response方法),將HttpResponse對象返回給瀏覽器。也就是說:若是MIDDLEWARE中註冊了6箇中間件,執行過程當中,第3箇中間件返回了一個HttpResponse對象,那麼第4,5,6中間件的process_request和process_response方法都不執行,順序執行3,2,1中間件的process_response方法。
process_request方法都執行完後,匹配路由,找到要執行的視圖函數,先不執行視圖函數,先執行中間件中的process_view方法,process_view方法返回None,繼續按順序執行,全部process_view方法執行完後執行視圖函數。假如中間件3 的process_view方法返回了HttpResponse對象,則4,5,6的process_view以及視圖函數都不執行,直接從最後一箇中間件,也就是中間件6的process_response方法開始倒序執行。
process_template_response和process_exception兩個方法的觸發是有條件的,執行順序也是倒序。總結全部的執行流程以下:
2|4****中間件版登陸驗證
中間件版的登陸驗證須要依靠session,因此數據庫中要有django_session表。
urls.py
from app02 import views as v2 urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/',v2.login), url(r'^home/',v2.home), url(r'^index/',v2.index) ]
views.py
from django.shortcuts import render,redirect,HttpResponse from app02 import models # Create your views here. def login(request): error_msg='' if request.method=='POST': username=request.POST.get('username') password=request.POST.get('password') user_obj=models.User.objects.filter(username=username,password=password) if user_obj: #設置session request.session['login']='ok' #獲取用戶想直接訪問的URL url=request.GET.get('next') #若是有,就跳轉到客戶初始想訪問的URL if not url: #沒有則默認跳轉到home頁面 url='/home/' return redirect(url) else: error_msg='username or password error!' return render(request,'login.html',{'error_msg':error_msg}) def home(request): return HttpResponse('<h1>這是home頁面 只有登陸了才能看到</h1>') def index(request): return HttpResponse('<h1>這是index頁面 也只有登陸了才能看到<h1>')
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登錄頁面</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <form action="" method="post"> {% csrf_token %} <label for="">username:<input type="text" name="username"></label> <label for="">password:<input type="password" name="password"></label> <input type="submit" value="submit"> </form> <h1 style="color: red">{{ error_msg }}</h1> </body> </html>
middlewares.py
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect class Check_Login(MiddlewareMixin): def process_request(self,request): next_url=request.path_info if not next_url.startswith('/login/'): is_login=request.session.get('login','') if not is_login: return redirect('/login/?next={}'.format(next_url))
在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', 'middleware.my_middleware.Check_Login', ]
附:Django請求流程圖