基於類的視圖提供了另一種將視圖實現爲Python對象而不是函數的方法。它們不替換基於函數的視圖,但與基於函數的視圖相比具有一定的差異和優勢:
- 與特定HTTP方法(GET,POST等)相關的代碼組織可以通過單獨的方法而不是條件分支來解決。
- 諸如mixins(多重繼承)之類的面向對象技術可用於將代碼分解爲可重用組件。
1. GV,CBV和CBGV的關係和歷史
譯者注:這裏GV爲通用視圖(generic views)的簡寫,CBV爲基於類的視圖(class-based views)的簡寫,CBGV就是class-based generic views的簡寫。
在開始時只有視圖函數聯繫,Django給你的函數傳遞了 HttpRequest 並且預期返回一個 HttpResponse。這是Django提供的事情。
早期就認識到在視圖的開發中有很多共同的風格和模式。引入了基於函數的通用視圖(FBGV)來抽象這些模式並簡化常見案例的視圖開發。
FBGV的問題在於,雖然它們很好地涵蓋了簡單的情況,但是除了一些簡單的配置選項之外,沒有辦法擴展或定製它們,限制了它們在許多實際應用程序中的用途。
CBV與FBGV具有相同的目標,以使視圖開發更容易。但是,通過使用mixins實現解決方案的方式提供了一個工具包,使得CBGV比FBGV更具可擴展性和靈活性。
如果您在過去嘗試過FBGV並且發現它們很匱乏,那麼您不應該將CBGV簡單地視爲基於類的等效視圖,而應該將其視爲解決通用視圖的原始問題的新方法解決。
Django用於構建CBGV的基類和混合的工具包是爲了最大的靈活性而構建的,因此在默認方法實現和屬性的形式中有許多鉤子,在最簡單的使用中你不太可能會關注它們。例如,之前的做法是限制你只能用基於類的form_class屬性,而默認實現中使用一個get_form方法,它調用get_form_class方法,該方法在其默認實現中只返回form_class類的屬性。這爲您提供了幾個選項,用於指定要使用的表單,從簡單屬性到完全動態的可調用掛鉤。這些選項似乎爲簡單情況增加了空洞複雜性,但如果沒有它們,更先進的設計將受到限制。
2. 使用CBV
從本質上講,CBV允許您使用不同的類實例方法響應不同的HTTP請求方法,而不是在單個視圖函數中使用條件分支代碼。
那麼在GET視圖函數中處理HTTP的代碼看起來像這樣:
from django.http import HttpResponse
def my_view(request):
if request.method == 'GET':
# <view logic>
return HttpResponse('result')
在CBV中,這將變爲:
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse('result')
因爲Django的URL解析器期望將請求和關聯的參數發送到可調用的函數而不是類,所以CBV具有 as_view()類方法,該類方法返回當請求到達匹配關聯模式的URL時可以調用的函數。該函數創建類的實例並調用其 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對象在CBV中有效。
雖然最小的CBV不需要任何類屬性來執行其工作,但類屬性在許多基於類的設計中很有用,並且有兩種方法來配置或設置類屬性。
第一種是標準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"
另一個方式是將類屬性配置爲函數 as_view() 的關鍵字參數 :
urlpatterns = [
path('about/', GreetingView.as_view(greeting="G'day")),
]
註解
雖然爲每個分派給它的請求都實例化了類,但通過as_view()入口點設置的類屬性在導入URL時只配置一次。
3. 使用mixins
Mixins是多重繼承的一種形式,其中可以組合多個父類的行爲和屬性。
例如,在CBGV中,有一個mixin叫做 TemplateResponseMixin 其主要目的是定義 render_to_response() 方法。當與基類View 的行爲結合使用時,結果是一個TemplateView 類,它將請求分配給適當的匹配方法(在View基類中定義的行爲),並且具有 render_to_response()
方法 ,此方法使用 template_name 屬性返回TemplateResponse 對象(在TemplateResponseMixin中定義的行爲)。
Mixins是在多個類中重用代碼的絕佳方法,但它們需要一些成本。你的代碼散佈在mixins中的次數越多,讀取子類就越難,並且知道它究竟在做什麼,如果你繼承了的子類具有深度繼承樹,那麼知道哪些方法可以覆蓋就更難了。
另請注意,您只能從一個通用視圖繼承 - 也就是說,只有一個View父類可以繼承,其餘的(如果有的話)應該是mixins。嘗試從多個繼承View的類進行繼承- 例如,嘗試在列表頂部使用表單並組合ProcessFormView和 ListView - 將無法按預期工作。
4. 使用CBV處理表單
處理表單的FBV可能如下所示:
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})
類似的CBV可能如下所示:
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})
這是一個非常簡單的情況,但您可以看到,您可以選擇通過覆蓋任何類屬性來自定義此視圖,例如 form_class,通過URLconf配置,或子類化和覆蓋一個或多個方法(或兩者一起 )。
5. 裝飾CBV
CBV的擴展不僅限於使用mixins。您也可以使用裝飾器。由於CBV不是函數,因此根據您是否正在使用as_view()或創建子類,裝飾它們的工作方式不同。
5.1 在URLconf中裝飾
裝飾CBV的最簡單方法是裝飾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())),
]
此方法基於每個實例應用裝飾器。如果您希望對視圖的每個實例進行修飾,則需要採用不同的方法。
5.2 裝飾類
要裝飾每個CBV實例,您需要裝飾類的定義本身。爲此,您可以將裝飾器應用於類的 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實例都將具有登錄保護。
註解
method_decorator傳遞*args和**kwargs 作爲類的裝飾方法的參數。如果您的方法不接受一組兼容的參數,則會引發 TypeError異常。