Django 6.1:基于类的视图

基于类的视图提供另一种将视图实现为 Python 对象而不是函数的方法。它们不能替代基于函数的视图,但与基于函数的视图相比,它们是有某些不同和优势的。

  • 与特定的 HTTP 方法(GET, POST, 等等)关联的代码组织能通过单独的方法替代条件分支来解决。
  • 面向对象技术(比如 mixins 多重继承)可用于将代码分解为可重用组件。

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

最初只有视图函数:Django 把 HttpRequest 对象传给函数,并期望函数返回 HttpResponse 对象。框架提供的约定仅此而已。

早期人们就发现在视图开发过程中有常见的约定和模式。引入了基于函数的通用视图为这些常见情况抽象这些模式和简单视图的开发。

基于函数的通用视图的问题是即便它们可以很好的处理简单案例,但除了一些配置选项之外,没办法扩展或自定义它们,这样就限制了它们在实际应用中用途。

创建基于类的通用视图与基于函数的通用视图具有相同的目标,那就是使视图开发更容易。然而,通过使用 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()。后者检查 HTTP 方法是 GET、POST 还是其他方法,并调用对应的实例方法;如果没有支持该 HTTP 方法的实现,则返回 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() 的配置只在导入 URLconf、调用 as_view() 时设置一次。

使用 mixins

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

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

Mixin 有助于跨类复用代码,但也有代价:代码分散在越多的 mixin 中,就越难理解子类的实际行为;继承层次很深时,也更难判断应当覆盖哪个 mixin 的方法。 也需要注意你只能从一个通用视图继承——只有一个父类可以继承自 View ,剩余的(如果有的话)应该继承自 mixins 。试着从更多的继承自 View 的类继承的话——例如为了在列表上方显示表单而组合 FormView 和 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})

这是一个很小的案例,但你可以看到你可以选择通过覆盖类的任何属性来自定义这个视图,比如 form_class ,通过 URLconf 配置或者子类化和重写一个或多个方法(或者两种都可以)。

装饰基于类的视图

基于类的视图的扩展不仅限于使用 mixins ,你也可以使用装饰器。因为基于类的视图不是函数,所以根据你是使用 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())),
]

这种方式只装饰 URLconf 中这一次 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 错误。

来源与许可

原文:Django 6.1 官方中文文档:基于类的视图。本稿沿用官方中文译文,修正部分术语与语病;示例代码保留原样。© Django Software Foundation and individual contributors。

Django 采用 BSD 三条款许可证,完整声明如下:

Copyright (c) Django Software Foundation and individual contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright notice,
       this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    3. Neither the name of Django nor the names of its contributors may be used
       to endorse or promote products derived from this software without
       specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容