Django类视图

博主     2018年08月12日    分类: Django   阅读:13020     评论:1

所有的类视图都继承django.views.generic.base.View类。

在URLconf中简单的使用通用视图

如果只是简单的做一些属性修改,可以使用as_view()方法,如下所示:

from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    path('about/', TemplateView.as_view(template_name="about.html")),
]

继承通用视图

继承是最强大和有效的使用方式,如下:

# some_app/views.py
from django.views.generic import TemplateView

class AboutView(TemplateView):
    template_name = "about.html"

然后就可以像下面一样使用这个子类了。注意,由于类不是函数,所以需要使用as_view()这个类方法将一个基于类的视图转换成函数形式的接口。

# urls.py
from django.urls import path
from some_app.views import AboutView

urlpatterns = [
    path('about/', AboutView.as_view()),
]

一个访问图书馆最新一本书的例子

路由:

from django.urls import path
from books.views import BookListView

urlpatterns = [
    path('books/', BookListView.as_view()),
]

视图:

from django.http import HttpResponse
from django.views.generic import ListView
from books.models import Book

class BookListView(ListView):
    model = Book

    def head(self, *args, **kwargs):
        last_book = self.get_queryset().latest('publication_date')
        response = HttpResponse('')
        # RFC 1123 date format
        response['Last-Modified'] = last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT')
        return response

这个例子中,如果用户通过GET请求数据,那么将正常的返回响应数据。而如果通过HEAD请求,将使用我们写的head方法中的业务逻辑。

基于类的视图和基于函数的视图比较:

  • 通过HTTP请求方法的不同,将代码分隔在不同的类方法中,比如GET和POST,而不是类函数中的条件判断;
  • 可以使用面向对象的技巧,比如混入
  • 两者没有绝对的好坏和压倒性优势之分

早期,人们在视图开发中发现了一些常见的习语和句式,也就是重复性代码和工作。于是引入了基于函数的通用视图来抽象这些模式,便于一般情况下的视图开发。

基于函数的通用视图的问题在于,尽管它们覆盖了简单的情况,但是除了一些简单的配置选项之外,没有办法扩展或定制它们,限制了它们在许多现实应用中的实用性。

基于类的通用视图与基于函数的通用视图的目标相同,都是想让视图开发更容易。由于类视图可以使用MIXIN等一些面向对象的方法和工具包,使得基于类的通用视图比基于函数的通用视图更具扩展性和灵活性。

Django用来构建基于类的通用视图的基类和混合程序工具包是为了最大程度的灵活性而构建的,因此在默认方法实现和属性中有许多钩子,在最简单的用例中,这些方法是不太可能涉及的。Django给出了几种指定使用什么形式、从简单属性到完全动态可调用钩子的选项。对于简单的情况,这些选择似乎增加了复杂性,但没有它们,更先进的设计将受到限制。

一、使用基于类的视图

在一个函数视图中,处理一个GET请求,通常如下:

from django.http import HttpResponse

def my_view(request):
    if request.method == 'GET':
        # <view logic>
        return HttpResponse('result')

而在类视图中,则通过不同过的类方法来处理:

rom django.http import HttpResponse
from django.views import View

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

上面的继承关系非常重要,不能随便自己瞎写一个类。这些都是Django安利给我们的,不是Python的原生用法。

每个类视图都有一个as_view()方法,用于在urlconf中使用。这个方法会创建一个类视图的实例,并调用它的dispatch()方法。dispatch方法会在类中查找类似GET\POST之类的类方法,然后与请求request中的HTTP方法匹配。匹配上了就调用对应的代码,匹配不上就弹出异常HttpResponseNotAllowed。

# urls.py
from django.urls import path
from myapp.views import MyView

urlpatterns = [
    path('about/', MyView.as_view()),
]

至于return返回的什么,和函数视图是一样样的。

要重写或者覆盖一个类属性有两种办法:

一是继承父类,在子类中重写父类的属性,如下所示:

父类:

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"

注意其中的greeting类属性。

另一种就是简单粗暴的在URLConf路由条目中修改as_view()方法的参数。当然,参数名必须是存在的类属性,你不能随便瞎写!如下所示:

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

但是这种方式有很大的弊端,那就是虽然每次匹配上了url,你的类视图都会被实例化一次,但是你的URLs却是在被导入的时候才配置一次,也就是as_view()方法的参数的配置只有一次。也就是说这么做,就不能再改了!所以,不要偷懒,使用子类吧。

二、使用mixin混入

混入是一种多父类继承的形式,其基础理论知识请参考我的Python教程中多继承相关章节。

MIXIN是跨多个类重用代码的一个很好的方法,但是它们会带来一些代价。你的代码散布在MIXIN中越多,阅读子类就越难,很难知道它到底在做什么。如果你在对具有多级继承树的子类进行分类,就更难以判断子类的方法到底继承的是哪个先祖,俗称‘家谱乱了’。

需要注意的是你的父类中只有一个类可以继承最顶级的View类,其它的必须以mixin方法混入。

三、使用类视图处理表单

一个用来处理表单的函数视图通常是下面这样的:

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

看起来类视图好像比函数视图代码多了很多,复杂了一些,貌似没啥优点啊。但是如果你有多个类似的视图需要编写,那么你就可以发挥子类的继承和复写神操作了,分分钟整出个新的视图来。或者直接在URLConf中修改参数!或者两种操作同时使用!

其实,到现在你应该明白了,类视图适用于大量重复性的视图编写工作,在简单的场景下,没几个视图需要编写,或者各个视图差别很大的情况时,还是函数视图更有效!所以,不要认为类视图是多么高大上的东西,人云亦云!

四、装饰类视图

除了mixin,还可以使用装饰器扩展类视图。装饰器的作用取决于你是在创建子类还是使用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())),
]

上面怎么看都像是函数嵌套调用和链式调用。

同样的,这种方式也是在第一次初始化URL配置时才有效,不能做到每来一个请求都有效。或者使用方法二如下:

用法二,在类视图中装饰:

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)

注意:

  • 上面要把装饰器用在dispatch这个方法上,才能在每次请求到达URL时,实例化类视图时都运行这个装饰器的功能。
  • 不是每个装饰器都能运用在类方法上,需要使用method_decorator这个装饰器的装饰器方法将装饰器运用在类方法上。感觉很绕?其实就是说,我们有很多很多的装饰器,但其中有一些不能直接装饰dispatch这种类方法。那怎么办呢?套层壳!用method_decorator装饰器包裹起来,假装成一个能用的。

有时候,简单地用一下,可以写成下面地精简版:

@method_decorator(login_required, 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'

为了偷懒,我们可以这么做:

decorators = [never_cache, login_required]

@method_decorator(decorators, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

到底哪个好点?感觉都一样啊!除非你同时有几十上百个装饰器需要使用!但这可能么?脱裤子放屁!

唯一需要注意地是装饰器是有先后顺序的。上面的例子中,never_cache就要先于login_required被调用。

最后,偷偷的告诉你使用method_decorator有时会导致TypeError异常,因为参数传递的原因。当然,一般人碰不到的,碰得到的人都不一般,都能自己查Django官方文档掌握问题原因。

更多类视图内容待续。(今天心情不好,吐槽太多,不要喷我....)


评论总数: 1



曾经建议过博主写写类视图,没想到就真写了。感谢。再次建议博主可以详细介绍TemplateView、ListView和DetailView吗?