Django基礎七之CBV裝飾器和中介軟體

Hans_Wang發表於2022-03-22

Django基礎七之CBV裝飾器和中介軟體

1. CBV加裝飾器

CBV加裝飾器有三種方法,

案例:要求登入(不管get請求還是post請求)後才可以訪問

HTML程式碼

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>

<div>
    <p>Hello Index</p>
</div>

</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <form action="" method="post">
        <p>username:<input type="text" name="username"></p>
        <p>password:<input type="password" name="password"></p>
        <p><input type="submit" value="提交"></p>
    </form>

</div>
</body>
</html>

views.py

# CBV加裝飾器方法一:
from django.shortcuts import render, HttpResponse, redirect

from django.views import View

from django.utils.decorators import method_decorator  # django提交加裝飾器方法


# Create your views here.

# 裝飾器
def login_auth(func):
    def inner(request, *args, **kwargs):
        if request.session.get("is_login"):
            res = func(*args, **kwargs)
            return res
        else:
            return redirect('/login/')

    return inner


class Index(View):
    # 方法一在每個需要驗證的地方都加上裝飾器
    @method_decorator(login_auth)
    def get(self, request):
        print("get 請求")
        return render(request, "index.html")
	# 個需要驗證的地方加上裝飾器
	@method_decorator(login_auth)
    def post(self, request):
        print("post 請求")
        return HttpResponse("post")


def login(request):
    if request.method == "POST":
        name = request.POST.get("username")
        password = request.POST.get("password")
        if name == "hans" and password == "123":
            request.session['is_login'] = True
            print("登入成功")

    return render(request, "login.html")



# CBV加裝飾器方法二:
from django.shortcuts import render, HttpResponse, redirect

from django.views import View

from django.utils.decorators import method_decorator


# Create your views here.
# 裝飾器
def login_auth(func):
    def inner(request, *args, **kwargs):
        if request.session.get("is_login"):
            res = func(*args, **kwargs)
            return res
        else:
            return redirect('/login/')

    return inner

# 方法二 在類的上面加上,name為具體要加的函式
@method_decorator(login_auth, name='post')
@method_decorator(login_auth, name='get')
class Index(View):
    def get(self, request):
        print("get 請求")
        return render(request, "index.html")


    def post(self, request):
        print("post 請求")
        return HttpResponse("post")


def login(request):
    if request.method == "POST":
        name = request.POST.get("username")
        password = request.POST.get("password")
        if name == "hans" and password == "123":
            request.session['is_login'] = True
            print("登入成功")

    return render(request, "login.html")


# CBV加裝飾器方法三:

from django.shortcuts import render, HttpResponse, redirect

from django.views import View

from django.utils.decorators import method_decorator


# Create your views here.
# 裝飾器
def login_auth(func):
    def inner(request, *args, **kwargs):
        if request.session.get("is_login"):
            res = func(*args, **kwargs)
            return res
        else:
            return redirect('/login/')

    return inner

class Index(View):
    #方法三  使用dispatch給所有的方法新增裝飾器
    @method_decorator(login_auth)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    def get(self, request):
        print("get 請求")
        return render(request, "index.html")


    def post(self, request):
        print("post 請求")
        return HttpResponse("post")


def login(request):
    if request.method == "POST":
        name = request.POST.get("username")
        password = request.POST.get("password")
        if name == "hans" and password == "123":
            request.session['is_login'] = True
            print("登入成功")

    return render(request, "login.html")

urls.py

from django.contrib import admin
from django.urls import path

from wrapperMidd  import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.Index.as_view()),
    path('login/', views.login),
]

訪問地址:http://127.0.0.1:8000/index

get的請求使用POSTMAN工具

2. Django中介軟體

2.1 Django中介軟體介紹

中介軟體是 Django 請求/響應處理的鉤子框架。它是一個輕量級的、低階的“外掛”系統,用於全域性改變 Django 的輸入或輸出。

每個中介軟體元件負責做一些特定的功能,Django中自帶了七個中介軟體

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',   #  安全中介軟體,為請求/響應週期提供了若干安全增強功能。每一項都可以通過設定獨立地啟用或禁用。
    'django.contrib.sessions.middleware.SessionMiddleware', # 啟用會話支援
    'django.middleware.common.CommonMiddleware', # “通用”中介軟體
    'django.middleware.csrf.CsrfViewMiddleware', # CSRF 保護中介軟體,通過在 POST 表單中新增隱藏的表單欄位,並檢查請求的正確值,增加對跨站點偽造請求的保護。
    'django.contrib.auth.middleware.AuthenticationMiddleware', # 驗證中介軟體,將代表當前登入的使用者的 user 屬性新增到每個傳入的 HttpRequest 物件中
    'django.contrib.messages.middleware.MessageMiddleware', # 訊息中介軟體,啟用基於 cookie 和會話的訊息支援
    'django.middleware.clickjacking.XFrameOptionsMiddleware', # X-Frame-Options 中介軟體,簡單的 通過 X-Frame-Options 頭的點選劫持保護。
]

中介軟體(Middleware)在整個Djangorequest/response處理機制中的角色如下所示:

HttpRequest -> Middleware(request) -> View -> Middleware(response) -> HttpResponse

中介軟體常用於許可權校驗、限制使用者請求、列印日誌、改變輸出內容等多種應用場景.而且中介軟體對Django的輸入或輸出的改變是全域性的。

Django 中介軟體作用:

  • 修改請求,即傳送到 view 中的 HttpRequest 物件。
  • 修改響應,即 view 返回的 HttpResponse 物件。

中介軟體執行順序:

image

2.2 自定義中介軟體

中介軟體可以定義四個方法:

  • process_request(self,request)
  • process_view(self, request, view_func, view_args, view_kwargs)
  • process_exception(self, request, exception)
  • process_response(self, request, response)

主要為process_requestprocess_response

2.2.1 自定義中介軟體

在應用目錄下新建一個 py 檔案,名字自定義。

在應用目錄下建立myMiddle.py
myMiddle.py:

from django.utils.deprecation import MiddlewareMixin

class myMinddle(MiddlewareMixin):
    def process_request(self, request):  # 在檢視之前執行
        print("這是自定義中介軟體 請求1")

    def process_response(self,request, response):  #在檢視之後執行
        print("這是自定義中介軟體 響應1")
        return response

把自定義的中介軟體註冊到setting.pyMIDDLEWARE裡面:

setting.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',
    'wrapperMidd.myMinddle.myMinddle',  # 自定義中介軟體
]

測試:

views.py:
    
from django.shortcuts import render, HttpResponse, redirect
def testMinddle(request):
    print("testMinddle")
    return HttpResponse("TEST")

urls.py:
    
from django.contrib import admin
from django.urls import path

from appName  import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('testMinddle/', views.testMinddle),
]

# 訪問:http://127.0.0.1:8000/testMinddle/
# 結果:
"""
這是自定義中介軟體 請求1
testMinddle
這是自定義中介軟體 響應1
"""

增加兩個自定義中介軟體,執行過程:

myMiddle.py:

from django.utils.deprecation import MiddlewareMixin

class myMinddle(MiddlewareMixin):
    def process_request(self, request):
        print("這是自定義中介軟體 請求1")

    def process_response(self,request, response):
        print("這是自定義中介軟體 響應1")
        return response


class myMinddle2(MiddlewareMixin):
    def process_request(self, request):
        print("這是自定義中介軟體 請求2")

    def process_response(self,request, response):
        print("這是自定義中介軟體 響應2")
        return response


setting.py:
MIDDLEWARE = [
    ......
    'wrapperMidd.myMinddle.myMinddle',
    'wrapperMidd.myMinddle.myMinddle2',
]

# 訪問:http://127.0.0.1:8000/testMinddle/
# 結果
"""
這是自定義中介軟體 請求1
這是自定義中介軟體 請求2
testMinddle
這是自定義中介軟體 響應2
這是自定義中介軟體 響應1
"""

如果在第一個中介軟體直接返回,執行順序如果:

myMiddle.py:

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class myMinddle(MiddlewareMixin):
    def process_request(self, request):
        print("這是自定義中介軟體 請求1")
        return HttpResponse("request")   """在這裡直接返回"""
    def process_response(self,request, response):
        print("這是自定義中介軟體 響應1")
        return response


class myMinddle2(MiddlewareMixin):
    def process_request(self, request):
        print("這是自定義中介軟體 請求2")

    def process_response(self,request, response):
        print("這是自定義中介軟體 響應2")
        return response

# 訪問:http://127.0.0.1:8000/testMinddle/
# 結果:
網頁上顯示:request
後臺顯示:
"""
這是自定義中介軟體 請求1
這是自定義中介軟體 響應1
"""

2.2.2 自定義中介軟體總結

  1. 自定義中介軟體先執行process_request再執行views.py裡的檢視函式,最後再執行process_response,而且process_response函式必須要返回 return response

  2. 如果有多個自定義中介軟體,則執行順序按settings.py裡自上而下執行,寫在上面的先執行。執行順序

    自定義中介軟體1 process_request--->自定義中介軟體2 process_request-->檢視函式-->自定義中介軟體2 process_response -->自定義中介軟體1 process_response
    
  3. 如果自定義中介軟體的process_request裡有return返回,而這個中介軟體還是在上面,則它會執行自己定義的process_requestprocess_response,則檢視函式和其他的中介軟體都不執行

  4. 如果自定義中介軟體的process_request裡有return返回,而這個中介軟體上面還有其他的中介軟體,則會自上而下執行,執行到自定義中介軟體的process_request後就會執行process_response,則檢視函式和它下面的中介軟體都不執行

    MIDDLEWARE = [
        ...其他中介軟體...
        '自定義中介軟體1',
        '自定義中介軟體2',  #  自定義中介軟體2裡使用return直接返回 
        '自定義中介軟體3',
    ]
    
    執行順序:
    """
    其他中介軟體 process_request  --> 自定義中介軟體1 process_request --> 自定義中介軟體2 process_request --> 自定義中介軟體2 process_response --> 自定義中介軟體1 process_response -->其他中介軟體 process_response
    """
    檢視函式和自定義中介軟體3是不執行的
    

2.2.3 其他中介軟體函式

process_view

process_view在process_request之後,路由轉發到檢視,執行檢視之前執行。

process_view() 只在 Django 呼叫檢視前被呼叫。它應該返回 None 或 HttpResponse 物件。如果它返回 None ,Django 將繼續處理這個請求,執行任何其他的 process_view() ,然後執行相應的檢視。如果它返回 HttpResponse 物件,Django 不會去影響呼叫相應的檢視;它會將響應中介軟體應用到 HttpResponse 並返回結果。


函式定義:
process_view(request, view_func, view_args, view_kwargs)
request 是一個 HttpRequest 物件。
view_func 是一個 Django 將要使用的 Python 函式。(這是一個真實的函式物件,不是函式的名稱);view_args 是一個用來傳遞給檢視的位置引數列表,;
view_kwargs 是一個用來傳遞給檢視的關鍵字引數字典。
view_args 和 view_kwargs 都不包含第一個檢視引數 ( request )。

process_exception

檢視執行中發生異常時執行。

當檢視引發異常時,Django 會呼叫 process_exception()。process_exception() 應該返回 None 或 HttpResponse 物件。如果它返回一個 HttpResponse 物件,模板響應和響應中介軟體將被應用且會將結果響應返回瀏覽器。否則,就會開始預設異常處理( default exception handling )。

再次,中介軟體在響應階段會按照相反的順序執行,其中包括 process_exception 。如果異常中介軟體返回一個響應,那麼中介軟體之上的中介軟體類的 process_exception 方法根本不會被呼叫。

函式定義:
process_exception(request, exception)
request 是一個 HttpRequest 物件。 exception 是一個由檢視函式引發的 Exception 物件。

process_template_response

檢視函式剛執行完畢,process_response之前執行。


process_template_response() 在檢視被完全執行後呼叫,如果響應例項有 render() 方法,表明它是一個 TemplateResponse 或等效物件。

它必須返回一個實現了 render 方法的響應物件。它可以通過改變``response.template_name`` 和 response.context_data 來改變給定的 response ,或者它可以建立和返回全新的 TemplateResponse 或等效物件。

不需要顯式地渲染響應——一旦所有模板中介軟體被呼叫,響應會被自動渲染。

中介軟體會在響應階段按照相反的順序執行,其中包括 process_template_response() 。


函式定義:
process_template_response(request, response)

request 是一個 HttpRequest 物件。
response 是 TemplateResponse 物件(或者等效物件),它通過 Django 檢視或中介軟體返回。

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class myMinddle(MiddlewareMixin):
    def process_request(self, request):
        print("這是自定義中介軟體 請求1")

    def process_response(self,request, response):
        print("這是自定義中介軟體 響應1")
        return response

    def process_view(self,request, view_func, view_args, view_kwargs):
        print("檢視函式之前執行")

    def process_exception(self,request,exception):
        print("處理檢視函式")
        
訪問http://127.0.0.1:8000/testMinddle/
結果:
這是自定義中介軟體 請求1
檢視函式之前執行
testMinddle
這是自定義中介軟體 響應1


# 檢視函式出錯示例:
這是自定義中介軟體 請求1
檢視函式之前執行
testMinddle
處理檢視函式錯誤
這是自定義中介軟體 響應1

2.3 新版本中介軟體寫法

官網上給的示例:

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # 配置和初始化

    def __call__(self, request):
        # 在這裡編寫檢視和後面的中介軟體被呼叫之前需要執行的程式碼,即process_request()
        response = self.get_response(request)

        # 在這裡編寫檢視呼叫後需要執行的程式碼,即process_response()

        return response

案例:

使用官網上的寫法不用繼承 MiddlewareMixin

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        print("這是自定義中介軟體 SimpleMiddleware的請求")
        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.
        print("這是自定義中介軟體 SimpleMiddleware的響應")

        return response

# 執行結果:
這是自定義中介軟體 SimpleMiddleware的請求
testMinddle
這是自定義中介軟體 SimpleMiddleware的響應

注意

__init__(get_response)

中介軟體必須接受 get_response 引數。還可以初始化中介軟體的一些全域性狀態。記住兩個注意事項:

  • Django僅用 get_response 引數初始化您的中介軟體,因此不能定義 __init__() ,因為需要其他引數。
  • 與每次請求都會呼叫 __call__() 方法不同,當 Web 伺服器啟動後,__init__() 只被呼叫一次

上面只定義了process_requestprocess_response 其中process_viewprocess_exception還是要寫。

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
       
    def __call__(self, request):

        print("這是自定義中介軟體 SimpleMiddleware的請求")
        response = self.get_response(request)
        
        print("這是自定義中介軟體 SimpleMiddleware的響應")

        return response

    def process_view(self,request, view_func, view_args, view_kwargs):
        print("檢視函式之前執行")

    def process_exception(self,request,exception):
        print("處理檢視函式錯誤")

3.Csrf中介軟體

使用Django框架使用django.middleware.csrf.CsrfViewMiddleware中介軟體,在前端頁面提交操作的時候,會報錯:

Forbidden (403)
CSRF verification failed. Request aborted.

解決方法:

如果使用form提交,則在前端頁面里加入:
    {% csrf_token %}
如:
<div>
    <form action="" method="post">
     {% csrf_token %}
    <label>username: <input type="text" name="username"></label>
    <label>password:<input type="password" name="password"></label>
    <label><input type="submit" value="提交"></label>
    </form>
</div>


如果是Ajax提交:
"""一定要匯入jquery"""
<body>
<div>
    <label>username: <input type="text" name="username" id="user"></label>
    <label>password:<input type="password" name="password" id="pwd"></label>
    <input type="button" value="提交" id="btn">

</div>
<script>
    $('#btn').click(function (){
        $.ajax({
            url: "",
            method: "post",
            data: {username: $('#user').val(), password: $('#pwd').val(), csrfmiddlewaretoken: '{{csrf_token}}'},

            success: function (data) {
                console.log(data)

            }
        })

    })

</script>
</body>

# 使用cookie:
使用cookie 則要匯入"""jquery.cookie.min.js"""
<script src="https://cdn.bootcdn.net/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>

const csrftoken = $.cookie('csrftoken');

使用:
<body>
<div>
    <label>username: <input type="text" name="username" id="user"></label>
    <label>password:<input type="password" name="password" id="pwd"></label>
    <input type="button" value="提交" id="btn">

</div>
<script>
    $('#btn').click(function (){
        const csrftoken = $.cookie('csrftoken');
        $.ajax({
            url: "",
            headers:{'X-CSRFToken': csrftoken},  // 加請求頭。
            method: "post",
            data: {username: $('#user').val(), password: $('#pwd').val()},
            success: function (data) {
                console.log(data)

            }
        })

    })

</script>
</body>

全域性使用csrf區域性函式使用,或全域性不使用,區域性函式使用csrf

from django.views.decorators.csrf import csrf_exempt,csrf_protect
# 全域性使用,區域性不使用
@csrf_exempt
def xxx()

# 全域性不使用(禁用掉),區域性使用
@csrf_protect
def yyy()

相關文章