Django 2.1.3 視圖層 CBV介紹

CBV | 總目錄 | 內置顯示視圖


基於類的視圖提供了另一種將視圖實現爲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的類進行繼承- 例如,嘗試在列表頂部使用表單並組合ProcessFormViewListView - 將無法按預期工作。

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異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章