基于类的视图简介 — Django 文档

来自菜鸟教程
Django/docs/2.2.x/topics/class-based-views/intro
跳转至:导航、​搜索

基于类的视图介绍

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

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

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

一开始只有视图函数契约,Django 向你的函数传递了一个 HttpRequest 并期望返回一个 HttpResponse。 这就是 Django 提供的范围。

早期人们认识到在视图开发中发现了常见的习语和模式。 引入了基于函数的通用视图来抽象这些模式并简化常见情况的视图开发。

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

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

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

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查看请求以确定它是否是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")),
]

笔记

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


使用混入

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

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

Mixin 是跨多个类重用代码的绝佳方式,但它们会带来一些成本。 你的代码在 mixin 中分散得越多,就越难阅读子类并知道它到底在做什么,如果你子类化一些具有深层继承树。

另请注意,您只能从一个通用视图继承 - 也就是说,只有一个父类可以从 View 继承,其余(如果有)应该是 mixin。 尝试从多个继承自 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})

这是一个非常简单的案例,但您可以看到,您可以通过覆盖任何类属性来自定义此视图,例如 form_class,通过 URLconf 配置,或子类化和覆盖一个或多个方法(或两者!)。


装饰基于类的视图

基于类的视图的扩展不仅限于使用 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 异常。