基于类的视图简介

基于类的视图使用Python 对象实现视图,它提供除函数视图之外的另外一种方式。 它们不替换基于函数的视图,但与基于函数的视图相比具有一定的区别和优势:

  • 组织与特定HTTP方法相关的代码(GETPOST等) 可以通过单独的方法而不是条件分支来解决。
  • 面向对象的技术例如Mixin(多继承)可以将代码分解成可重用的组件。

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

开始的时候只有视图函数,Django 传递一个HttpRequest 给你的函数并期待返回一个HttpResponse Django 曾经提供的就这么些内容。

在早期,我们认识到在视图开发过程中有共同的用法和模式。 这时我们引入基于函数的通用视图来抽象这些模式以简化常见情形的视图开发。

基于函数的视图的问题在于,虽然它们很好地覆盖了简单的情形,但是不能扩展或自定义它们,即使是一些简单的配置选项,这让它们在现实应用中受到很多限制。

基于类的通用视图然后应运而生,目的与基于函数的通用视图一样,就是为了使得视图的开发更加容易。 然而,它们使用的Mixin解决办法使得基于类的通用视图比基于函数的视图更加容易扩展和更加灵活。

如果你以前使用基于函数的通用视图并发现它们的不足,你不能认为基于类的通用视图只是简单地用基于类的方法实现一个等价的替代,你应该认为它们是解决原始问题的一个全新的方法。

Django 使用基类和Mixin来构建基于类的通用视图,为用户提供了最大的灵活性,默认的方法包含很多属性和方法的钩子,但是简单的用法中不需要考虑他们,也可以正常工作。 例如,不是将您限制为form_class的基于类的属性,而是使用调用get_form_class方法的get_form方法,其默认实现只返回类的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。 该函数创建一个类的实例并调用其dispatch()方法。 dispatch 查看请求是GET 还是POST 等等,并将请求转发给相应的方法,如果该方法没有定义则引发HttpResponseNotAllowed

# urls.py
from django.conf.urls import url
from myapp.views import MyView

urlpatterns = [
    url(r'^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"

另外一种方式是在URLconf 中用as_view() 调用的关键字参数配置类的属性:

urlpatterns = [
    url(r'^about/$', GreetingView.as_view(greeting="G'day")),
]

对于每个请求都会实例化类的一个实例,但是as_view() 入口点设置的类属性只在URL 第一次导入时配置。

使用mixins

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

例如,在通用的基于类的视图中,有一个Mixin 叫做 TemplateResponseMixin,它的主要目的是定义render_to_response() 方法。 它与View 基类的组合是TemplateView 类,这个类可以调度请求给正确的方法(TemplateResponseMixin 基类中定义的行为),同时还具有一个render_to_response() 方法,该方法使用template_name 属性来返回一个TemplateResponse 对象( View 中定义的行为)。

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 = [
    url(r'^about/$', login_required(TemplateView.as_view(template_name="secret.html"))),
    url(r'^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(ProtectedView, self).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 的每个实例都将有登录保护。

*args 传递**kwargsmethod_decorator 参数给类上被装饰的方法。 如果你的方法不接受与之兼容的参数集,它将引发一个TypeError 异常。