Django 自訂裝飾器的運作原理與編寫方法

裝飾器是在 Django 中為視圖函數或類別輕鬆添加共通邏輯的強大工具。本文將逐步解釋裝飾器的 運作原理編寫方法,幫助您親自編寫自訂裝飾器。

1. 裝飾器是什麼?

裝飾器是 Python 的 高階函數。即,它接受一個函數作為參數並返回一個 新的函數

  • 裝飾器的角色:
    • 可以包裹函數以添加或修改特定功能。
    • 使代碼可重用且簡潔。

2. 裝飾器的基本運作原理

為了理解裝飾器的運作原理,我們來看一個簡單的範例。

def simple_decorator(func):
    def wrapper():
        print("在函數執行前")
        func()
        print("在函數執行後")
    return wrapper

@simple_decorator
def say_hello():
    print("你好!")

say_hello()

輸出結果:

在函數執行前
你好!
在函數執行後

說明

  1. @simple_decoratorsay_hello 函數傳遞給 simple_decorator 函數。
  2. simple_decorator 在内部生成並返回一個新的 wrapper 函數。
  3. say_hello 被呼叫時,執行的是 wrapper 而不是原始函數。
  4. wrapper 函數會在内部執行額外的操作(打印消息),然後呼叫原始函數。

3. 在 Django 中編寫自訂裝飾器的原理

在 Django 中編寫裝飾器時,您必須會處理 視圖函數的參數 request 物件。根據此可以對特定條件下的 HTTP 回應進行操作或執行邏輯。

4. 自訂裝飾器編寫的分步結構

以下是 Django 中自訂裝飾器的基本結構:

  1. functools.wraps 使用: 使用 @wraps 以保持原始函數的名稱、文檔字符串等元數據。
  2. 基於 request 的邏輯添加: 編寫分析或驗證請求物件的邏輯。
  3. 呼叫原始函數或返回替代應答: 根據特定條件阻止原始函數的呼叫並返回其他應答。

以下是範例代碼:

from functools import wraps
from django.http import HttpResponseForbidden

def custom_decorator(view_func):
    @wraps(view_func)
    def _wrapped_view(request, *args, **kwargs):
        # 在請求物件中確認用戶信息
        if not request.user.is_authenticated:
            return HttpResponseForbidden("您必須登錄才能訪問此頁面。")
        
        # 呼叫原始視圖函數
        return view_func(request, *args, **kwargs)
    
    return _wrapped_view

提供一個視覺吸引人的插圖,解釋 Django 自訂裝飾器的運作

5. 如何為裝飾器添加參數?

如果裝飾器需要 參數,則必須使用 包裹兩次函數的結構。原因是第一個函數用於處理裝飾器的參數,而第二個函數負責包裹實際的視圖函數。

基本結構:

def decorator_with_params(param1, param2):
    def actual_decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            # 利用參數 param1, param2 的邏輯
            return view_func(request, *args, **kwargs)
        return _wrapped_view
    return actual_decorator

說明:

  1. 第一個函數 decorator_with_params 用於處理 裝飾器的參數param1, param2)。
  2. 返回內部包裹實際視圖函數的 裝飾器函數actual_decorator)。
  3. actual_decorator 中呼叫原始視圖函數或執行附加邏輯。

6. 實戰範例:時間限制裝飾器

以下是一個僅在特定時段內可訪問的裝飾器範例。這個裝飾器接收一個參數來處理允許的時間範圍。

from datetime import datetime
from django.http import HttpResponseForbidden

def time_restricted_access(start_hour, end_hour):
    def decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            current_hour = datetime.now().hour
            if not (start_hour <= current_hour < end_hour):
                return HttpResponseForbidden("此頁面僅在特定時段內可訪問。")
            return view_func(request, *args, **kwargs)
        return _wrapped_view
    return decorator

@time_restricted_access(9, 17)  # 上午9時到下午5時允許訪問
def working_hours_view(request):
    return render(request, 'working_hours.html')

運作原理說明:

  1. time_restricted_access 是處理 參數start_hour, end_hour)的 第一個函數
  2. 第一個函數返回內部包裹視圖的 裝飾器函數
  3. 裝飾器函數 接收 request 並檢查當前時間(datetime.now().hour),根據時間條件:
    • 如果滿足條件,則呼叫視圖並返回結果。
    • 如果不滿足條件,則使用 HttpResponseForbidden 阻止訪問。

為什麼要包裹兩次函數?

之所以包裹兩次函數,是因為 要將參數處理和視圖函數處理的角色分開

  • 第一個函數:接收參數(start_hour, end_hour)並在內部生成裝飾器。
  • 第二個函數:生成的裝飾器包裹視圖函數並針對請求執行。

這種結構使裝飾器更加靈活和可重用。

7. 在基於類的視圖(CBV)中使用

基於類的視圖中需要使用 method_decorator

from django.utils.decorators import method_decorator
from django.views import View

@method_decorator(time_restricted_access(9, 17), name='dispatch')
class MyView(View):
    def get(self, request):
        return render(request, 'my_template.html')

8. 結論:編寫自訂裝飾器時的注意事項

  1. 保持簡單:裝飾器邏輯應簡單明瞭。
  2. 考慮可重用性:若特定邏輯在多個視圖中使用,請將其提取為裝飾器。
  3. 處理參數:需要參數時,使用包裹兩次函數的結構。
  4. 保留元數據:使用 @wraps 保持原始函數的元數據。

總結

現在,您已了解 Django 中自訂裝飾器的內部運作原理,並能編寫接受參數的裝飾器。根據這篇文章編寫符合您項目的裝飾器吧!