基于类的视图介绍

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

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

  • 面向对象的技术,如mixin(多重继承),可以用来将代码分解成可重用的组件。

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

最初只有视图函数契约,Django将函数 HttpRequest 期望回到 HttpResponse . 这就是 Django 所提供的范围。

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

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

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

如果您在过去尝试过基于函数的泛型视图,但发现它们缺乏,则不应将基于类的泛型视图视为基于类的等效视图,而应将其视为解决泛型视图要解决的原始问题的新方法。

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时可以调用该函数。函数创建类的实例,调用 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 shortcutsTemplateResponse 对象可以在基于类的视图中使用。

虽然基于类的最小视图不需要任何类属性来执行它的工作,但是类属性在许多基于类的设计中是有用的,并且有两种方法来配置或设置类属性。

第一种是标准的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() 调用urlconf::

urlpatterns = [
    path("about/", GreetingView.as_view(greeting="G'day")),
]

备注

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

使用Mixin

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

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

mixin是跨多个类重用代码的一种很好的方法,但是它们带来了一些成本。代码越分散在mixin中,就越难读取子类并知道它究竟在做什么,而且如果您正在子类化具有深度继承树的东西,就越难知道从哪个方法重写mixin。

还要注意,只能从一个通用视图继承-也就是说,只能从一个父类继承 View 其余的(如果有的话)应该是混合的。尝试从继承自的多个类继承 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() 或者创建一个子类。

室内装饰

可以通过装饰 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 decorator将函数decorator转换为方法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 例外。