基于类的视图简介

基于类的视图提供了一种替代方法,可以将视图实现为 Python 对象而不是函数。它们并没有取代基于函数的视图,但在与基于函数的视图相比时,它们具有一定的区别和优势。

  • 与特定 HTTP 方法(GET, POST等)相关的代码组织可以通过单独的方法来处理,而不是条件分支。

  • 可以使用面向对象的技巧,例如 mixin(多重继承)将代码分解为可重用的组件。

泛型视图、基于类的视图和基于类的泛型视图之间的关系和历史

起初只有视图函数约定,Django 将一个HttpRequest 传递给你的函数,并期望返回一个HttpResponse。这就是 Django 提供的全部内容。

早期人们认识到,在视图开发中存在常见的习惯用法和模式。基于函数的泛型视图被引入以抽象这些模式并简化常见情况下的视图开发。

基于函数的泛型视图的问题在于,虽然它们很好地涵盖了简单的情况,但无法超越某些配置选项来扩展或自定义它们,这限制了它们在许多实际应用中的实用性。

基于类的泛型视图与基于函数的泛型视图的目标相同,都是为了简化视图开发。但是,通过使用 mixin 的方法来实现解决方案,使得基于类的泛型视图比基于函数的泛型视图更具可扩展性和灵活性。

如果你过去尝试过基于函数的泛型视图并发现它们不够用,你不应该将基于类的泛型视图视为基于类的等价物,而应该将其视为解决泛型视图旨在解决的原始问题的新方法。

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

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

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

Mixin 是一种在多个类之间重用代码的极好方法,但它也有一定的代价。你的代码分散在 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异常。

返回顶部