Django类视图源码解析及dispatch函数
- 类视图原理
为什么我们定义url的时候, 调用 as_view() 函数,就可以达到结果, 如果不调用就会报错.
到底 as_view() 帮助我们干了什么了?
@classonlymethod
def as_view(cls, **initkwargs):
...省略代码...
def view(request, *args, **kwargs):
# 这里的cls是as_view这个函数接收的第一个参数,也就是调用当前函数的类.
# 得到调用的类了之后, 创建类的对象: self
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
# 给当前这个类,添加对应的属性, 如下所示:
self.request = request
self.args = args
self.kwargs = kwargs
# 调用dispatch方法,按照不同请求方式调用不同请求方法
return self.dispatch(request, *args, **kwargs)
...省略代码...
# 返回真正的函数视图
return view
# dispatch本身是分发的意思,这里指的是函数的名字.
def dispatch(self, request, *args, **kwargs):
# self.http_method_names指的是我们的类视图中,对象方法的名字
# 这里把所有方法的名字都存放在了http_methods_names中
# 我们会把当前请求的方式转为小写,然后判断是否在列表中存在.
if request.method.lower() in self.http_method_names:
# 如果在里面, 则进入这里
# 这里的getattr作用是获取当前对象的属性.
# 下面的参数为:
# self : 类视图对象
# request.method.lower() : 请求方法的小写. 例如: 'get' 或 'post'
# http_method_not_allowed : 后续处理方式(不允许请求)
# 下面代码整体的意思: 根据类视图对象, 获取当前类视图中对应名称的方法
# 如果获取到, 则把方法返回给handle, 否则不允许访问.
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 如果类视图中如果没有的话, 则进入这里, 表明不允许进行请求.
# 我们会把不允许请求这个字段返回给handle.
handler = self.http_method_not_allowed
# 最终返回handle(handle里面要么包含可以访问的方法, 要么就是不允许访问的字段)
return handler(request, *args, **kwargs)
- 类视图使用装饰器
在处理正常项目需求时, 装饰器修会在极大程度上方便我们的工作,避免重复造轮子. 那么, 如果我们定义了一个类视图, 它是否可以使用装饰器进行修饰呢?
为了理解方便,我先来定义一个为函数视图准备的装饰器(在设计装饰器时基本都以函数视图作为考虑的被装饰对象),以及一个要被装饰的类视图, 来说明一下给类添加装饰器的实现要点.
# 定义一个装饰器
# func = my_decorator(func)
def my_decorator(func):
# wrapper函数必然会接收一个request对象,因为传入进来的func这个函数肯定会带这个参数
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求路径%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 我们定义的类视图
class DemoView(View):
# 我们给get方法添加上装饰器, 然后执行.
@my_decorator
def get(self, request):
print('get方法')
return HttpResponse('ok')
# 类视图里面的对象方法: post方法
def post(self, request):
print('post方法')
return HttpResponse('ok')
结论:
我们会发现上面写的代码出错了, 错误原因就是我们定义的装饰器是修饰视图函数的, 而不是修饰类视图的, 所以使用装饰器来强行修饰, 会有问题.
那么我们怎样才能把一个修饰函数的装饰器作用在类上呢?
我们可以思考一下得到: url部分我们其实是有使用 as_view( ) 函数的, 而这个函数最终会给我们返回一个view( ) 函数, 换句话说: 我们在调用类视图之前,必然会调用 view( ) 这个函数, 那我们是否可以把装饰器作用到 view( ) 这个函数上去呢
- 修饰dispatch函数, 实现多个函数装饰
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
# 自定义的装饰器方法
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求的路径:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 类视图
class DemoView(View):
# 重写父类的dispatch方法, 因为这个方法被 as_view() 中的 view() 调用
# 所以我们对这个方法添加装饰器, 也就相当于对整个类视图的方法添加装饰器.
@method_decorator(my_decorator)
def dispatch(self, request, *args, **kwargs):
# 重写父类的这个方法我们不会修改它的任何参数, 所以我们直接调用父类的这个方法即可
# 它里面的参数我们也不动它, 直接还传递过去.
return super().dispatch(request, *args, **kwargs)
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
展示效果 :
- 上式可以变形为: (扩展)
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求的路径:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 类视图
# 给类视图增加上@method_decorator方法
# 增加上之后,需要配置第二个参数, 否则没有任何装饰行为
# 所以我们需要给method_decator配置第二个参数
# 指定把装饰器添加到某个函数上.
@method_decorator(my_decorator, name='dispatch')
class DemoView(View):
def get(self, request):
print('get')
return HttpResponse('getfunc ok')
def post(self, request):
print('post')
return HttpResponse('postfunc ok')
结论:
我们发现, 经过中间一层额外扩展类的装饰过滤, 我们原来的DemoView中的所有视图方法是能够经过装饰器的.
我们可以定义两个扩展类, 并且重写两次 as_view 方法
from django.views.generic import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
# 定义的第一个装饰器:
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求的路径:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 定义的第二个装饰器:
def my_decorator2(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了22222')
print('请求的路径:%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
# 额外增加的第一个扩展类
class BaseView(View):
# 第一次重写父类中的as_view方法
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
# 对获取的view第一次添加装饰器
view = my_decorator(view)
return view
# 额外增加的第二个扩展类
class Base2View(View):
# 第二次重写父类中的as_view方法
@classmethod
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs)
# 对获取的view进行第二次添加装饰器
view = my_decorator2(view)
return view
# 我们定义的类视图, 继承自两个额外增加的类
class DemoView(BaseView, Base2View):
# 类视图中的get方法
def get(self, request):
print('get')
return HttpResponse('get func')
# 类视图中的post方法
def post(self, request):
print('post')
return HttpResponse('post func')
说明:
如果两个扩展类的父类相同: 则两个父类都会调用
如果两个扩展类的父类不同: 则只会调用第一个父类
# 第一个扩展类, 让他继承自object
class BaseView(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
view = my_decorator(view)
return view
# 第二个扩展类,让他继承自object
class Base2View(object):
@classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
view = my_decorator2(view)
return view
# 类视图, 让他除了继承自这两个父类外, 最后继承View类.
class DemoView(BaseView, Base2View, View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
说明:
因为都是继承自object,所以扩展类中的super.as_view都会去找其他的父类依次执行,最终都会执行到View这个类这里, 所以肯定会执行View中的as_view方法
使用Mixin扩展类,也会为类视图的所有请求方法都添加装饰行为!!