【Django】视图 CBV

本文为 Django 学习总结,讲解视图 CBV。欢迎交流

FBV 与 CBV

我们以前写的视图都是基于 FBV(Function Based View),即将视图写成函数的方式。例如下面这个添加班级的例子:

def add_class(request):
    if request.method == "POST":
        class_name = request.POST.get("class_name")
        models.Classes.objects.create(name=class_name)
        return redirect("/class_list/")
    return render(request, "add_class.html")

还有一种方法称为 CBV(Class Based View),这种方法将视图写成一个类的形式,显得逻辑更加清晰:

class AddClass(View):

    def get(self, request):
        return render(request, "add_class.html")

    def post(self, request):
        class_name = request.POST.get("class_name")
        models.Classes.objects.create(name=class_name)
        return redirect("/class_list/")

get 请求和 post 请求分开写,且缺一不可。在对应的 url 配置也需要做出改动。下面是 FBV 的url 配置:

re_path(r'^add_class/$', views.add_class),

而在 CBV 版本中,需要在 url 中使用 as_view() 方法:

re_path(r'^home/$', views.AddClass.as_view()),

as_view 方法的流程

我们需要研究一下 as_view() 的源码。直接按 Ctrl 并点击 View 进入源码,as_view()View 的一个类方法(@classonlymethod):

@classonlymethod
def as_view(cls, **initkwargs):
	...
    def view(request, *args, **kwargs):
        self = cls(**initkwargs) # 实例化一个AddClass类对象self
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get
        # 执行函数时,django会传给request对象,将其赋值给类对象的request
        self.request = request 
		self.args = args
        self.kwargs = kwargs
        return self.dispatch(request, *args, **kwargs)
    view.view_class = cls
    view.view_initkwargs = initkwargs
	...
    return view

该方法将 cls 即当前类 AddClass 作为参数。类中定义了 view 并返回它,因此在 url 配置中返回的是 view 函数,本质上和 FBV 相同。请求到来时会执行 view 函数。

看下面的代码,执行函数时,django 会传给 request 对象,将其赋值给类对象的 request

self.request = request 
self.args = args
self.kwargs = kwargs

我们在视图中进行打印验证:

class home(View):
    def get(self, request):
        print(self.request is request) # 验证
        return render(request, "AddClass.html")

打印得到结果:

在这里插入图片描述

在这里还返回了 self.dispatch,并传入相应的参数。我们实际拿到的方法是 dispatch 执行得到的结果:

return self.dispatch(request, *args, **kwargs)

as_view() 后面定义了 dispatch 方法:

def dispatch(self, request, *args, **kwargs):
    # Try to dispatch to the right method; if a method doesn't exist,
    # defer to the error handler. Also defer to the error handler if the
    # request method isn't on the approved list.
    # 将method转换为小写,在http_method_names中查找
    if request.method.lower() in self.http_method_names:
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

if 语句将 method 转换为小写,在 http_method_names 中查找。http_method_names 也在 view 中进行了定义:

http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

列表中定义了 8 种请求方式。如果我们在自己的类中定义了同名列表,程序会查找我们自定义的列表,这时如果在列表中没有找到相应的请求方式则会报错。

如果请求方式被允许,则执行一个 getattr 反射,传入请求方式 request.method.lower() 赋值给 handler,这样就可以获取到我们自定义的 getpost 等方法。:

if request.method.lower() in self.http_method_names:
    handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
    handler = self.http_method_not_allowed

如果获取不到 get 请求方式的方法,则在 if 中返回 self.http_method_not_allowed。如果没有定义的请求方式,则会在 else 中返回 self.http_method_not_allowed

http_method_not_allowed 方法如下:

def http_method_not_allowed(self, request, *args, **kwargs):
    logger.warning(
        'Method Not Allowed (%s): %s', request.method, request.path,
        extra={'status_code': 405, 'request': request}
    )
    return HttpResponseNotAllowed(self._allowed_methods())

最后执行 handler 返回结果。

写一个例子验证一下。我们在视图类中定义一个 dispatch 方法:

class AddClass(View):
    def dispatch(self, request, *args, **kwargs):
        return HttpResponse("Mike is a good man")
    def get(self, request):
    	...

因为我们是在执行请求方法 get 前执行 dispatch,因此页面中会打印:

在这里插入图片描述

CBV 加装饰器

我们还可以在类中使用 View 中的 dispatch 方法,并在执行前后可以增添内容,实现装饰器

class AddClass(View):
    def dispatch(self, request, *args, **kwargs):
        # return HttpResponse("Mike is a good man")
        # 执行get之前的操作
        ret = super(home, self).dispatch(request, *args, **kwargs)
        # 执行get之后的操作
        return ret
    def get(self, request):
        ...

现在我们想要计算执行视图的流程需要多少时间,可以使用一个统计时间的装饰器。在执行视图前后时间相减即可。

我们写一个 timer 装饰器:

import time
# 统计时间的装饰器
def timer(func):
    def inner(*args, **kwargs): # 放被装饰的函数参数,任意多个
        # 被装饰函数前后进行操作
        start = time.time() # 获取当前时间
        ret = func(*args, **kwargs)
        # 被装饰函数前后进行操作
        print('执行时间为:{}'.format(time.time()-start))
        return ret
    return inner

然后要将装饰器加到视图函数上

对于 FBV 的视图函数,直接加在函数前面即可:

@timer
def add_class(request):
    if request.method == "POST":
        class_name = request.POST.get("class_name")
        models.Classes.objects.create(name=class_name)
        return redirect("/class_list/")
    return render(request, "add_class.html")

得到执行时间:

在这里插入图片描述

给 CBV 加装饰器有 3 种方法。首先要使用方法装饰器 method_decorator,需要导入模块。

第一种方法,直接使用方法装饰器加在类方法前面,相当于使用了带参数的装饰器:

from django.utils.decorators import method_decorator
class AddClass(View):
    def dispatch(self, request, *args, **kwargs):
        # return HttpResponse("Mike is a good man")
        # 执行get之前的操作
        ret = super(home, self).dispatch(request, *args, **kwargs)
        # 执行get之后的操作
        return ret
    @method_decorator(timer)
    def get(self, request):
        ...
    def post(self, request):
        ...

得到执行时间:

在这里插入图片描述

而如果提交 post 请求则不会打印执行时间。这种方法只给 get 请求使用了装饰器。当我们需要给很多方法加装饰器时,这种方法就比较麻烦了。

第二种方法可以给类中所有方法加装饰器,可以将装饰器加在该类的 dispatch 方法上:

@method_decorator(timer)
def dispatch(self, request, *args, **kwargs):
    # return HttpResponse("Mike is a good man")
    # 执行get之前的操作
    ret = super(home, self).dispatch(request, *args, **kwargs)
    # 执行get之后的操作
    return ret

因为在执行 getpost 等请求方法前会先执行 dispatch

第三种方法是直接加在类上。这时需要指定类中需要装饰的方法:

@method_decorator(timer, name='get')
class AddClass(View):
	...

如果将 name 指定为 dispatch,则相当于加在了 Viewdispatch 方法上:

@method_decorator(timer, name='dispatch')

如果我们在 CBV 中使用 @timer,也可以打印出视图函数的执行时间,也就是说两者在功能上是没区别的,区别在于装饰器内部。

首先使用 @timer 装饰函数,在装饰器中打印函数、request 和参数:

def timer(func):
    def inner(request, *args, **kwargs): # 放被装饰的函数参数,任意多个
        print(func)
        print('************************************')
        print(request, args, kwargs)
        # 被装饰函数前后进行操作
        start = time.time() # 获取当前时间
        ret = func(request, *args, **kwargs)
        # 被装饰函数前后进行操作
        print('执行时间为:{}'.format(time.time()-start))
        return ret
    return inner

打印结果如下:

在这里插入图片描述

打印出的 func 对应第一行的 home.get 函数,而 request 对应最后一行的 homeobject,即 get 函数参数中的 self ,就是对象本身。而最后打印的参数才是我们需要的 request 对象。

而使用 @method_decorator(timer) 打印结果为:

在这里插入图片描述

第一行为 bound method 即绑定的方法,包含了 self 对象。这样就会将正确的 request 参数传到 request 变量中。

因此我们需要记住的是,如果使用了 @method_decorator(timer),则第一个参数就能拿到 request 对象,若不使用则 *args 的第二个参数才是 request 对象。@method_decorator(timer) 会对函数进行处理。

Python 装饰器在实现的时候,被装饰后的函数其实已经是另一个函数了,即函数名等函数属性会发生变化,为了不受其影响,functools 包总提供了 wrap 的装饰器来消除这样的副作用,使得函数能够保留自己的名称和 Docstring。

对于下面的例子,如果我们不加 wrap 则会打印装饰器的名称和 Docstring:

from functools import wraps   
def timer(func):
    # @wrap
    def inner(*args, **kwargs):
        '''decorator'''
        print('call inner function')
        return func(*args, **kwargs)
    return inner

def test():
    """Docstring"""
    print('call test function')
print(test.__name__, test.__doc__)

执行结果为 ('inner', 'decorator')

@wrap 后(解开上面的注释)的打印结果为 ('test', 'Docstring')

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值