Django 1.8.2 简介

基于类的视图简介

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

  • HTTP 方法(GETPOST 等)可以有各自的方法,而不用通过条件分支来解决。
  • 面向对象的技术例如Mixin(多继承)可以将代码分解成可重用的组件。

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

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

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

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

基于类的通用视图然后应运而生,目的与基于函数的通用视图一样,就是为了使得视图的开发更加容易。然而,它们使用的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.generic import View

class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse('result')

因为Django 的URL 解析器将请求和关联的参数发送给一个可调用的函数而不是一个类,所以基于类的视图有一个as_view() 类方法用来作为类的可调用入口。as_view 入口点创建类的一个实例并调用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 快捷函数TemplateResponse 对象。

虽然基于类的视图的最小实现不需要任何类属性来完成它的功能,但是在许多基于类的设计中类属性非常重要,有两种方式来设置类属性。

第一种方式是Python 标准的方式,子类化并在子类中覆盖属性和方法。所以,如果父类有一个greeting 属性:

from django.http import HttpResponse
from django.views.generic 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 第一次导入时配置。

使用Mixin

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

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

Mixin 是重用多个类的代码的一种极好的方法,但是它们需要一些代价。代码在Mixin 中越分散,子类将越难阅读并知道它的行为;如果你的继承很深,将难以知道应该覆盖哪一个Mixin 的方法。

还要注意,你只能继承一个通用视图 —— 也就是说,只能有一个父类继承View,其它的父类必须是Mixin。继承多个继承自View 的类 将不能像预期的那样工作—— 例如,试图在一个列表的顶部使用表单而组合ProcessFormViewListView

封装as_view() 的Mixin

将共同的行为运用于多个类的一种方法是编写一个封装as_view() 方法的Mixin。

例如,如果你有许多通用视图,它们应该使用login_required() 装饰器,你可以这样实现一个Mixin:

from django.contrib.auth.decorators import login_required

class LoginRequiredMixin(object):
    @classmethod
    def as_view(cls, **initkwargs):
        view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
        return login_required(view)

class MyView(LoginRequiredMixin, ...):
    # this is a generic view
    ...

使用基于类的视图处理表单

一个最基本的用于处理表单的视图函数可能是这样的:

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.generic 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})

这是一个非常简单的情形,但你可以看到你将有机会自定义这个视图,例如通过URLconf 配置覆盖form_class 属性或者子类化并覆盖一个和多个方法。

装饰基于类的视图

基于类的视图的扩展不仅仅局限于使用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())),
]

这个方法在每个实例的基础上运用装饰器。如果想让视图的每个实例都被装饰,你需要一种不同的方法。

装饰类

若要装饰基于类的视图的每个实例,你需要装饰class definition(类定义)本身。可以将装饰器运用到类的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)

在这个例子中,ProtectedView 的每个实例都将有登录保护。

注意:

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