類別式視圖簡介¶
類別式視圖提供了一種以 Python 物件而非函式來實作視圖的替代方法。它們並非取代函式式視圖,而是在與函式式視圖比較時,具有某些差異和優勢。
與特定 HTTP 方法 (
GET
、POST
等) 相關的程式碼組織,可以透過獨立的方法而不是條件分支來處理。可以使用諸如 mixin (多重繼承) 等物件導向技術,將程式碼分解為可重複使用的元件。
通用視圖、類別式視圖和類別式通用視圖的關係和歷史¶
一開始只有視圖函式契約,Django 將一個 HttpRequest
傳遞給您的函式,並期望傳回一個 HttpResponse
。這是 Django 所提供的全部內容。
早期人們就認識到,在視圖開發中存在一些常見的慣用語和模式。引入了函式式通用視圖,以抽象這些模式並簡化常見案例的視圖開發。
函式式通用視圖的問題在於,雖然它們很好地涵蓋了簡單的案例,但除了某些組態選項之外,沒有辦法擴充或自訂它們,這限制了它們在許多真實應用中的實用性。
建立類別式通用視圖的目標與函式式通用視圖相同,即簡化視圖開發。但是,該解決方案的實作方式,透過使用 mixin,提供了一個工具包,使得類別式通用視圖比其函式式對應物更具可擴充性和彈性。
如果您過去嘗試過函式式通用視圖並發現它們不足,則不應將類別式通用視圖視為類別式等效物,而應將其視為解決通用視圖原本旨在解決的原始問題的全新方法。
Django 用於建構類別式通用視圖的基礎類別和 mixin 工具包旨在實現最大的彈性,因此具有許多掛鉤,形式為您在最簡單的用例中不太可能關心的預設方法實作和屬性。例如,實作並非將您限制為 form_class
的類別式屬性,而是使用 get_form
方法,該方法呼叫 get_form_class
方法,而該方法在其預設實作中會傳回類別的 form_class
屬性。這為您提供了幾種指定要使用的表單的選項,從屬性到完全動態、可呼叫的掛鉤。這些選項似乎為簡單的情況增加了空洞的複雜性,但如果沒有它們,更進階的設計將會受到限制。
使用類別式視圖¶
從本質上講,類別式視圖允許您使用不同的類別實例方法來回應不同的 HTTP 請求方法,而不是在單個視圖函式內使用條件分支程式碼。
因此,在視圖函式中處理 HTTP GET
的程式碼看起來會像這樣
from django.http import HttpResponse
def my_view(request):
if request.method == "GET":
# <view logic>
return HttpResponse("result")
在類別式視圖中,這將變成
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse("result")
由於 Django 的 URL 解析器期望將請求和關聯的參數傳送到可呼叫的函式,而不是類別,因此類別式視圖有一個 as_view()
類別方法,該方法會傳回一個函式,當請求到達與關聯模式相符的 URL 時,可以呼叫該函式。該函式會建立類別的實例,呼叫 setup()
來初始化其屬性,然後呼叫其 dispatch()
方法。dispatch
會檢查請求以確定它是 GET
、POST
等,並將請求轉送到相符的方法 (如果已定義),否則會引發 HttpResponseNotAllowed
。
# urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path("about/", MyView.as_view()),
]
值得注意的是,您的方法傳回的內容與您從函式式視圖傳回的內容相同,即某種形式的 HttpResponse
。這表示在類別式視圖中可以使用 http 快捷方式或 TemplateResponse
物件。
雖然最小的類別式視圖不需要任何類別屬性來執行其工作,但類別屬性在許多類別式設計中很有用,並且有兩種方法可以組態或設定類別屬性。
第一種是標準的 Python 子類化方式,並覆寫子類別中的屬性和方法。因此,如果您的父類別具有像這樣的 greeting
屬性
from django.http import HttpResponse
from django.views import View
class GreetingView(View):
greeting = "Good Day"
def get(self, request):
return HttpResponse(self.greeting)
您可以在子類別中覆寫它
class MorningGreetingView(GreetingView):
greeting = "Morning to ya"
另一個選項是將類別屬性組態為 URLconf 中 as_view()
呼叫的關鍵字引數
urlpatterns = [
path("about/", GreetingView.as_view(greeting="G'day")),
]
注意
雖然您的類別是針對每個派送給它的請求進行實例化的,但是透過 as_view()
進入點設定的類別屬性僅在匯入您的 URL 時組態一次。
使用 mixin¶
Mixin 是一種多重繼承的形式,可以組合多個父類別的行為和屬性。
例如,在通用類別式視圖中,有一個名為 TemplateResponseMixin
的 mixin,其主要目的是定義方法 render_to_response()
。當與 View
基礎類別的行為結合時,結果是一個 TemplateView
類別,它會將請求派送到適當的相符方法 (在 View
基礎類別中定義的行為),並且具有使用 template_name
屬性傳回 TemplateResponse
物件的 render_to_response()
方法 (在 TemplateResponseMixin
中定義的行為)。
Mixin 是跨多個類別重複使用程式碼的絕佳方式,但它們會帶來一些成本。您的程式碼分散在 mixin 中的越多,就越難讀取子類別並知道它確切在做什麼,並且如果您子類化具有深度繼承樹的內容,就越難知道要覆寫哪個 mixin 中的方法。
另請注意,您只能繼承一個通用視圖,也就是說,只有一個父類別可以繼承 View
,其餘 (如果有的話) 應該是 mixin。嘗試從多個繼承自 View
的類別繼承 (例如,嘗試在清單頂端使用表單並結合 ProcessFormView
和 ListView
) 不會如預期般運作。
使用類別式視圖處理表單¶
處理表單的基本函式式視圖可能如下所示
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import MyForm
def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect("/success/")
else:
form = MyForm(initial={"key": "value"})
return render(request, "form_template.html", {"form": form})
類似的類別式視圖可能如下所示
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View
from .forms import MyForm
class MyFormView(View):
form_class = MyForm
initial = {"key": "value"}
template_name = "form_template.html"
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {"form": form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect("/success/")
return render(request, self.template_name, {"form": form})
這是一個最小的範例,但您可以看到,您然後可以透過 URLconf 組態覆寫任何類別屬性 (例如 form_class
) 或子類化並覆寫一個或多個方法 (或兩者!) 來自訂此視圖。
裝飾類別式視圖¶
類別式視圖的擴充不僅限於使用 mixin。您也可以使用裝飾器。由於類別式視圖不是函式,因此裝飾它們的方式會因您使用 as_view()
或建立子類別而異。
在 URLconf 中裝飾¶
您可以透過裝飾 as_view()
方法的結果來調整類別式視圖。執行此操作最簡單的地方是您部署視圖的 URLconf
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
from .views import VoteView
urlpatterns = [
path("about/", login_required(TemplateView.as_view(template_name="secret.html"))),
path("vote/", permission_required("polls.can_vote")(VoteView.as_view())),
]
此方法會以每個實例為基礎套用裝飾器。如果您希望每個視圖實例都被裝飾,則需要採用不同的方法。
裝飾類別¶
若要裝飾基於類別視圖的每個實例,您需要裝飾類別定義本身。要做到這一點,您需要將裝飾器應用於類別的 dispatch()
方法。
類別上的方法與獨立函式略有不同,因此您不能直接將函式裝飾器應用於方法 – 您需要先將其轉換為方法裝飾器。method_decorator
裝飾器將函式裝飾器轉換為方法裝飾器,以便可以將其用於實例方法。例如:
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
class ProtectedView(TemplateView):
template_name = "secret.html"
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
或者,更簡潔地說,您可以裝飾類別,並將要裝飾的方法名稱作為關鍵字參數 name
傳遞
@method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
template_name = "secret.html"
如果您有多個地方使用的常用裝飾器集合,您可以定義一個裝飾器列表或元組,並使用它來代替多次調用 method_decorator()
。以下兩個類別是等效的
decorators = [never_cache, login_required]
@method_decorator(decorators, name="dispatch")
class ProtectedView(TemplateView):
template_name = "secret.html"
@method_decorator(never_cache, name="dispatch")
@method_decorator(login_required, name="dispatch")
class ProtectedView(TemplateView):
template_name = "secret.html"
裝飾器將按照它們傳遞給裝飾器的順序處理請求。在範例中,never_cache()
將在 login_required()
之前處理請求。
在這個範例中,ProtectedView
的每個實例都將具有登入保護。這些範例使用 login_required
,但是,使用 LoginRequiredMixin
可以獲得相同的行為。
注意
method_decorator
將 *args
和 **kwargs
作為參數傳遞到類別上被裝飾的方法。如果您的方法不接受相容的參數集,則會引發 TypeError
例外。