基于类的视图简介

基于类的视图提供了另一种将视图实现为Python对象而不是函数的方法。 它们不替换基于函数的视图,但与基于函数的视图相比具有一定的差异和优势:

  • 与特定HTTP方法相关的代码组织(GETPOST等)可以通过单独的方法而不是条件分支来解决。
  • 诸如mixins(多重继承)之类的面向对象技术可用于将代码分解为可重用组件。

通用视图,基于类的视图和基于类的通用视图的关系和历史

在开始时只有视图函数契约,Django传递了你的函数HttpRequest并期望返回HttpResponse 这是Django提供的程度。

早期就认识到在视角发展中有共同的习语和模式。 引入了基于函数的通用视图来抽象这些模式并简化常见案例的视图开发。

基于函数的通用视图的问题在于,虽然它们很好地涵盖了简单的情况,但是除了一些简单的配置选项之外,没有办法扩展或定制它们,限制了它们在许多实际应用程序中的用途。

创建基于类的通用视图与基于函数的通用视图具有相同的目标,以使视图开发更容易。 但是,通过使用mixins实现解决方案的方式提供了一个工具包,使得基于类的通用视图比基于函数的对应视图更具可扩展性和灵活性。

如果您在过去尝试过基于函数的通用视图并且发现它们缺乏,那么您不应该将基于类的通用视图简单地视为基于类的等效视图,而应该将其视为解决通用视图的原始问题的新方法解决。

Django用于构建基于类的通用视图的基类和混合的工具包是为了最大的灵活性而构建的,因此在默认方法实现和属性的形式中有许多钩子,在最简单的使用中你不太可能关注它们案例。 例如,不是将您限制为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。 该函数创建类的实例并调用其dispatch()方法。 dispatch查看请求以确定它是否为GETPOST等,并将请求中继到匹配方法(如果已定义) ,或者引发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")),
]

Note

虽然为每个分派给它的请求实例化了类,但是通过as_view()入口点设置的类属性在导入URL时只配置一次。

使用mixins

Mixins是多重继承的一种形式,其中可以组合多个父类的行为和属性。

例如,在基于通用类的视图中,有一个名为TemplateResponseMixin的mixin,其主要目的是定义方法render_to_response() 当与View基类的行为结合使用时,结果是TemplateView类,它将请求分配给适当的匹配方法(视图中定义的行为基类),它有一个render_to_response()方法,它使用template_name属性返回TemplateResponse对象(一种行为)在TemplateResponseMixin中定义。

Mixins是在多个类中重用代码的绝佳方法,但它们需要一些成本。 你的代码散布在mixins中的次数越多,读取子类就越难以知道它正在做什么,并且如果你继承了一个具有一个类的东西,那么知道哪些方法可以覆盖哪些方法就更难了。深度继承树。

另请注意,您只能从一个通用视图继承 - 也就是说,只有一个父类可以从View继承,其余的(如果有的话)应该是mixins。 尝试从多个继承自View的类继承 - 例如,尝试使用列表顶部的表单并组合ProcessFormViewListView - 无法按预期工作。

使用基于类的视图处理表单

处理表单的基于函数的基本视图可能如下所示:

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})

This is a very simple case, but you can see that you would then have the option of customizing this view by overriding any of the class attributes, e.g. form_class, via URLconf configuration, or subclassing and overriding one or more of the methods (or both!).

Decorating class-based views

The extension of class-based views isn’t limited to using mixins. You can also use decorators. Since class-based views aren’t functions, decorating them works differently depending on if you’re using as_view() or creating a subclass.

Decorating in URLconf

The simplest way of decorating class-based views is to decorate the result of the as_view() method. The easiest place to do this is in the URLconf where you deploy your view:

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())),
]

This approach applies the decorator on a per-instance basis. If you want every instance of a view to be decorated, you need to take a different approach.

Decorating the class

To decorate every instance of a class-based view, you need to decorate the class definition itself. To do this you apply the decorator to the dispatch() method of the class.

A method on a class isn’t quite the same as a standalone function, so you can’t just apply a function decorator to the method – you need to transform it into a method decorator first. The method_decorator decorator transforms a function decorator into a method decorator so that it can be used on an instance method. For example:

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)

Or, more succinctly, you can decorate the class instead and pass the name of the method to be decorated as the keyword argument name:

@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

If you have a set of common decorators used in several places, you can define a list or tuple of decorators and use this instead of invoking method_decorator() multiple times. These two classes are equivalent:

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'

The decorators will process a request in the order they are passed to the decorator. In the example, never_cache() will process the request before login_required().

In this example, every instance of ProtectedView will have login protection.

Note

method_decorator passes *args and **kwargs as parameters to the decorated method on the class. If your method does not accept a compatible set of parameters it will raise a TypeError exception.