基于类的视图提供了一种替代方法,可以将视图作为Python对象而不是函数来实现。它们不替换基于函数的视图,但与基于函数的视图相比,它们具有某些差异和优势:
与特定HTTP方法相关的代码组织 (GET
, POST
等等)可以通过单独的方法来处理,而不是条件分支。
面向对象的技术,如mixin(多重继承),可以用来将代码分解成可重用的组件。
最初只有视图函数契约,Django将函数 HttpRequest
期望回到 HttpResponse
. 这就是姜戈所提供的范围。
在早期,人们认识到视图开发中有一些常见的习惯用法和模式。引入了基于函数的通用视图来抽象这些模式,并简化了常见情况下的视图开发。
基于功能的通用视图的问题在于,虽然它们很好地涵盖了简单的情况,但无法将它们扩展或自定义到某些配置选项之外,从而限制了它们在许多现实应用程序中的实用性。
基于类的通用视图的创建目标与基于函数的通用视图相同,以使视图开发更容易。但是,通过使用mixin实现解决方案的方法提供了一个工具箱,使得基于类的通用视图比基于函数的视图更具可扩展性和灵活性。
如果您过去尝试过基于函数的通用视图,但发现它们缺乏,那么您不应该将基于类的通用视图视为基于类的等效物,而应该将其视为解决通用视图旨在解决的原始问题的新方法。
Django用于构建基于类的通用视图的Base Class和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
查看请求以确定它是否是 GET
, POST
等,如果定义了匹配方法或引发了匹配方法,则将请求转发给匹配方法。 HttpResponseNotAllowed
如果不是:
# urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path("about/", MyView.as_view()),
]
值得注意的是,方法返回的内容与基于函数的视图返回的内容相同,即 HttpResponse
. 这意味着 http shortcuts 或 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"
另一个选项是将类属性配置为 as_view()
调用urlconf::
urlpatterns = [
path("about/", GreetingView.as_view(greeting="G'day")),
]
备注
当类为分派给它的每个请求实例化时,类属性通过 as_view()
在导入URL时,只配置一次入口点。
mixin是多重继承的一种形式,其中可以组合多个父类的行为和属性。
例如,在基于类的通用视图中,有一个名为 TemplateResponseMixin
其主要目的是定义方法 render_to_response()
. 当与 View
基类,结果是 TemplateView
将请求分派到适当的匹配方法(在 View
它有一个 render_to_response()
使用 template_name
返回的属性 TemplateResponse
对象(在 TemplateResponseMixin
)
mixin是跨多个类重用代码的一种很好的方法,但是它们带来了一些成本。代码越分散在mixin中,就越难读取子类并知道它究竟在做什么,而且如果您正在子类化具有深度继承树的东西,就越难知道从哪个方法重写mixin。
还要注意,只能从一个通用视图继承-也就是说,只能从一个父类继承 View
其余的(如果有的话)应该是混合的。尝试从继承自的多个类继承 View
-例如,尝试使用列表顶部的表单并组合 ProcessFormView
和 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
,通过URLinf配置,或者对一个或多个方法进行子分类和重写(或两者兼而有之!)。
基于类的视图的扩展不限于使用mixin。你也可以使用装饰。由于基于类的视图不是函数,因此根据您使用的 as_view()
或者创建一个子类。
您可以通过装饰的结果来调整基于类的视图 as_view()
法最简单的地方是在URLinf中部署视图::
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
例外。
7月 22, 2024