← CBV | 总目录 | 内置显示视图 →
基于类的视图提供了另一种将视图实现为Python对象而不是函数的方法。它们不替换基于函数的视图,但与基于函数的视图相比具有一定的差异和优势:
译者注:这里GV为通用视图(generic views)的简写,CBV为基于类的视图(class-based views)的简写,CBGV就是class-based generic views的简写。
在开始时只有视图函数联系,Django给你的函数传递了 HttpRequest 并且预期返回一个 HttpResponse。这是Django提供的事情。
早期就认识到在视图的开发中有很多共同的风格和模式。引入了基于函数的通用视图(FBGV)来抽象这些模式并简化常见案例的视图开发。
FBGV的问题在于,虽然它们很好地涵盖了简单的情况,但是除了一些简单的配置选项之外,没有办法扩展或定制它们,限制了它们在许多实际应用程序中的用途。
CBV与FBGV具有相同的目标,以使视图开发更容易。但是,通过使用mixins实现解决方案的方式提供了一个工具包,使得CBGV比FBGV更具可扩展性和灵活性。
如果您在过去尝试过FBGV并且发现它们很匮乏,那么您不应该将CBGV简单地视为基于类的等效视图,而应该将其视为解决通用视图的原始问题的新方法解决。
Django用于构建CBGV的基类和混合的工具包是为了最大的灵活性而构建的,因此在默认方法实现和属性的形式中有许多钩子,在最简单的使用中你不太可能会关注它们。例如,之前的做法是限制你只能用基于类的form_class属性,而默认实现中使用一个get_form方法,它调用get_form_class方法,该方法在其默认实现中只返回form_class类的属性。这为您提供了几个选项,用于指定要使用的表单,从简单属性到完全动态的可调用挂钩。这些选项似乎为简单情况增加了空洞复杂性,但如果没有它们,更先进的设计将受到限制。
从本质上讲,CBV允许您使用不同的类实例方法响应不同的HTTP请求方法,而不是在单个视图函数中使用条件分支代码。
那么在GET视图函数中处理HTTP的代码看起来像这样:
from django.http import HttpResponse
def my_view(request):
if request.method == 'GET':
#
return HttpResponse('result')
在CBV中,这将变为:
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request):
#
return HttpResponse('result')
因为Django的URL解析器期望将请求和关联的参数发送到可调用的函数而不是类,所以CBV具有 as_view()类方法,该类方法返回当请求到达匹配关联模式的URL时可以调用的函数。该函数创建类的实例并调用其 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快捷函数 或 TemplateResponse对象在CBV中有效。
虽然最小的CBV不需要任何类属性来执行其工作,但类属性在许多基于类的设计中很有用,并且有两种方法来配置或设置类属性。
第一种是标准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() 的关键字参数 :
urlpatterns = [
path('about/', GreetingView.as_view(greeting="G'day")),
]
注解
虽然为每个分派给它的请求都实例化了类,但通过as_view()入口点设置的类属性在导入URL时只配置一次。
Mixins是多重继承的一种形式,其中可以组合多个父类的行为和属性。
例如,在CBGV中,有一个mixin叫做 TemplateResponseMixin 其主要目的是定义 render_to_response() 方法。当与基类View 的行为结合使用时,结果是一个TemplateView 类,它将请求分配给适当的匹配方法(在View基类中定义的行为),并且具有 render_to_response()
方法 ,此方法使用 template_name 属性返回TemplateResponse 对象(在TemplateResponseMixin中定义的行为)。
Mixins是在多个类中重用代码的绝佳方法,但它们需要一些成本。你的代码散布在mixins中的次数越多,读取子类就越难,并且知道它究竟在做什么,如果你继承了的子类具有深度继承树,那么知道哪些方法可以覆盖就更难了。
另请注意,您只能从一个通用视图继承 - 也就是说,只有一个View父类可以继承,其余的(如果有的话)应该是mixins。尝试从多个继承View的类进行继承- 例如,尝试在列表顶部使用表单并组合ProcessFormView和 ListView - 将无法按预期工作。
处理表单的FBV可能如下所示:
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():
#
return HttpResponseRedirect('/success/')
else:
form = MyForm(initial={'key': 'value'})
return render(request, 'form_template.html', {'form': form})
类似的CBV可能如下所示:
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():
#
return HttpResponseRedirect('/success/')
return render(request, self.template_name, {'form': form})
这是一个非常简单的情况,但您可以看到,您可以选择通过覆盖任何类属性来自定义此视图,例如 form_class,通过URLconf配置,或子类化和覆盖一个或多个方法(或两者一起 )。
CBV的扩展不仅限于使用mixins。您也可以使用装饰器。由于CBV不是函数,因此根据您是否正在使用as_view()或创建子类,装饰它们的工作方式不同。
装饰CBV的最简单方法是装饰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())),
]
此方法基于每个实例应用装饰器。如果您希望对视图的每个实例进行修饰,则需要采用不同的方法。
要装饰每个CBV实例,您需要装饰类的定义本身。为此,您可以将装饰器应用于类的 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实例都将具有登录保护。
注解
method_decorator传递*args和**kwargs 作为类的装饰方法的参数。如果您的方法不接受一组兼容的参数,则会引发 TypeError异常。