类视图

阅读: 14235     评论:2

Django的视图可以分为:

  • 函数视图FBV:def index(request):
  • 类视图CBV:class AboutView(TemplateView):

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

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

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

基于类的视图:

  • 通过HTTP请求方法的不同,将代码分隔在不同的类方法中,比如GET和POST,而不是类函数中的条件判断。
  • 可以使用面向对象的技巧,比如混入。
  • 类具有封装和继承的特性,方便代码复用、分发和重构。

两种视图可以实现同样的功能,本质上是一个东西,没有谁好谁坏之分,只是适用场景不同而已:

  • 简单逻辑、快速处理,请用FBV
  • 代码复用、功能封装,请用CBV

Django 提供了很多适用于各种应用场景的基本视图类,我们一般不从头自己写起,这些类视图都继承django.views.generic.base.View类。比如RedirectView 用于 HTTP 重定向,TemplateView 用于渲染模板。

类视图有很多简单的用法,甚至不需要去views.py中编写代码,比如下面的例子:

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

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

重点关注:

  • TemplateView类视图
  • as_view()方法
  • template_name参数

更通用的使用方法是继承Django提供的各种视图类,所以上面的例子的一般性写法如下:

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

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

但是,由于类不是函数,所以需要在URLconf中使用as_view()这个类方法将一个基于类的视图转换成函数形式的接口。

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

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

上面的AboutView视图不涉及模型的访问,比较简单。让我们看一个书籍列表视图的例子:

首先是路由:

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

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

BookListView视图:

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

class BookListView(ListView):
    model = Book  # 指定模型

    def head(self, request, *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 请求,而不是在单个视图函数里使用if/else代码。

在函数视图里处理 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系统勾连。

每个类视图都有一个as_view()方法,用于在urlconf中进行dispatch。这个方法会创建一个类视图的实例,并调用它的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()),    # 注意as_view要加括号进行调用
]

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

as_view()方法可以传递参数,例如:

urlpatterns = [ path('view/', MyView.as_view(size=42)), ]

传递给视图的参数会在视图的每个实例之间共享。这意味着你不应使用列表、字典或任何其他可变对象作为视图的参数。如果你这样做并且共享对象被修改,则会导致一个请求对另外一个请求产生影响,这显然存在极大的风险,完全不可接受。

每个MyView视图的实例都可以使用 self.size接收传入的参数值42,但该参数必须已经在类中定义了。

基于类的视图不强制你添加任何类属性,当需要的时候,有两种方法来配置或设置类属性。

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

父类:

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是跨多个类重用代码的一个很好的方法,但是它们会带来一些代价。你的代码散布在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())),
]

用法二,在类视图中装饰指定的方法:

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异常,因为参数传递的原因。


 生成PDF文件 中间件 

评论总数: 2


点击登录后方可评论

看了,反正都记不住。。。估计最后就学会课程中的那两个项目,就可以面试去了



打卡