Django源码阅读之View.as_view()方法
平时我们在使用django开发项目的时候,很可能一个url对应有两种请求方式,get请求和post请求,这两种请求方式我们要做的操作也不一样。
如果我们不使用类视图来实现这个需求,可能我们就需要写两个视图函数,并且写两个url来对应,虽然说这样是可以实现需求的但是感觉看起来不是那么爽。
这个时候我们一般都是使用类视图来完成这个需求,不知道类视图怎么用的,可以参考一下这篇博客django中类视图详解
这里我就直接定义一个简单的类视图来举个例子
from django.http import HttpResponse
from django.views.generic import View
class IndexView(View):
def get(self,request):
return HttpResponse('index view get method')
def post(self, request):
return HttpResponse('index view post method')
上面我们就定义好了一个简单的类视图,然后我们需要在url中进行配置,像下面这样
from django.urls import path
from .views import IndexView
urlpatterns = [
path('index/', IndexView.as_view())
]
这样,我们就配置好了一个视图与url的映射,当我们以get方法请求/index/这个url的时候,就会返回IndexView.get
这个函数的执行结果,当我们以post方法请求/index/这个url的时候,就会返回IndexView.post
这个函数的执行结果。这样,我们就达到了同一个url的不同请求方法返回不同的数据的需求了。就比我们写两个url来达到这个效果看起来更爽一些。而且在类中我们可以做更多的函数中没有的操作。
那么为什么我们这样写了就能达到这样的效果呢?平时我们在url中添加映射的时候,path函数中的第二个参数就为我们对应的函数视图,而当我们使用类视图的时候,我们就写成了IndexView.as_view(),也就第二个参数的值变成了这个函数的返回结果,那么我们想要了解为什么这样写就能实现这样的而效果,我们就要阅读as_view()这个函数的源代码了。
首先我们找到这个函数的代码。
django.views.generic.View.as_view 函数
from django.utils.decorators import classonlymethod
from functools import update_wrapper
class View:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
# 首先这个函数是被classonlymethod这个装饰器装饰的,其实这个位置叫它描述器更合适一下。我们先来看一下这个描述器干了什么样的事情
@classonlymethod
'''
# 这里我们可以看到,这是一个类,而且继承自classmethod,也就是我们以前常用的类方法的装饰器
# 然后重写了__get__方法。
# 可能大家看不懂为什么要这样写,但是没关系,大家可以先去看一下描述器这个东西,在回来看这个位置就懂了
# 这里我就直接给大家说结果吧,当我们一个类写了__get__方法的时候,我们使用这个类去装饰一个类中的方法,就会先进入__get__方法。
# 其实这个位置就和我们装饰器差不多。这里就不细说了,我么直接看__get__方法吧
class classonlymethod(classmethod):
def __get__(self, instance, cls=None):
# self: 就是当前类的实例对象
# instance: 被装饰的 类的方法 的类 的实例对象,这句话可能有点拗口。其实这个位置就是IndexView的实例对象,
# 当我们以实例对象调用被这个装饰器的函数时,这个值就为当前实例对象,如果我们以类方法的形式调用这个函数,那么这个值就为None
# cls: 被装饰的 类的方法 的类, 也就是IndexView这个类
# 判断instance是否为None, 如果不是None,也就是以是实例对象来调用的这个方法
if instance is not None:
# 抛出异常,说 “这个方法只能是类(class)使用,实例对象(instance)不能使用。”
raise AttributeError("This method is available only on the class, not on instances.")
# 返回父类的__get__方法返回结果
return super().__get__(instance, cls)
'''
# 所以上面这个装饰器,其实和普通的classmethod这个装饰器效果一样,只是不允许使用实例调用这个类方法而已。而我们平时写的类方法,是可以被实例调用的。
def as_view(cls, **initkwargs):
# **initkwargs:这里面的值就是我们调用函数时传入的所有的关键字参数,但是我们一般使用as_view时都没有传入参数,所以这个值一般都为None
# 并且initkwargs是一个字典对象
# 遍历这个字典,拿到的是key
for key in initkwargs:
# 如果这个key在类属性http_method_names这个列表中,抛出异常。
# 大概意思为: 你尝试通过get/put/post/...这些方法的名字作为关键字参数传入到<当前的这个类>中,不要这样做
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
# 判断当前这个类是否有这个属性或方法,如果没有,抛出异常
# <当前的这个类>接收到了一个无效得的关键字key,as_view只能付接收已经存在的class的属性
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
# 定义一个view函数,request就是每个视图中的request这个实例对象,*args表示所有的位置参数, **kwargs表示所有的关键字参数
# 不知道这几个参数为什么是这些值的,大家可以看一下python中的装饰器相关概念,就能知道这几个参数为什么是这些值了
def view(request, *args, **kwargs):
# cls就为当前的这个类,这里也就是IndexView,
# 所以这句代码就是根绝**initkwargs参数,实例化这个类
self = cls(**initkwargs)
# 如果这个类有get方法但是没有head方法
if hasattr(self, 'get') and not hasattr(self, 'head'):
# 赋值self.head = self.get
# 就是让head方法等于get方法
self.head = self.get
# 继续绑定传入的参数值到实例对象的属性中
self.request = request
self.args = args
self.kwargs = kwargs
# 返回self.dispatch函数的执行结果。并且传入相关的参数,这个函数的代码我们在下面阅读。
return self.dispatch(request, *args, **kwargs)
# view就是上面我们定义的函数,因为在python中,万物皆对象,所以函数也是一个对象
# 给函数绑定连个属性,并赋值
view.view_class = cls
view.view_initkwargs = initkwargs
# update_wrapper这个装饰器其实就是改变当前函数的一下文档之类的,大家可以自己查看一下这个函数的用法, 这里我也是直接说结果
# update_wrapper有四个参数
# 第一个参数为需要装饰的函数
# 第二个参数为为提供信息的函数,也就是将这个函数的信息赋值给第一个参数的函数
# 第三个参数(assigned)的默认值为 : ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')
# 第四个参数(updated)的默认值为 :('__dict__',)
# update_wrapper函数的功能就是将第二个参数的这些属一一的赋值给第一个参数指定的函数
# 因为这里将update参数的的值赋值为空元祖,所以这里就是将cls的中(assigned指定的属性,也就是上面的默认值)全部赋值给view
update_wrapper(view, cls, updated=())
# 这里将assigned赋值为空,也就是将cls.dispatch函数的__dict__属性赋值给view的__dict__属性
update_wrapper(view, cls.dispatch, assigned=())
# 然后返回view这个函数,也就是相当于path中的第二个参数的值就为这个view
return view
django.views.generic.View().dispatch 函数
def dispatch(self, request, *args, **kwargs):
# 还是先来理解每个参数代表的含义
# request: 就是每个视图中的第一个参数,request实例
# *args: 所有的位置参数
# **kwargs:所有的关键字参数
# request.method:就能得到当前请求的具体方法如POST,GET,...是一个字符窜类型的数据,大家可以随便写一个视图函数打印一下这个值
# request.method.lower()就能将当前请求的方法的名称变为小写, 如post,get...
# 如果当前请求方法变为小写,在self.http_method_names中
# self.http_method_name是一个类属性,是一个列表,里面存放了很多请求方法的字符窜,上面我们也说到了这个属性
if request.method.lower() in self.http_method_names:
# getattr这个函数大家应该不陌生吧,就是从一个对象中得到一个属性或方法
# 这里就是从self中得到request.method.lower()这个方法,如果没有,得到的就为self.http_method_not_allowed这个方法,下面会说一下这个方法的源码的
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
# 如果请求的方法没有在self.http_method_names这个列表中,赋值handler为self.http_method_not_allowed
else:
handler = self.http_method_not_allowed
# 执行handle函数,并返回结果。例如我们请求方法为get,那么这个handler就是IndexView().get方法,
return handler(request, *args, **kwargs)
django.views.generic.View().http_method_not_allowed 函数
from django.http.response import HttpResponseNotAllowed
import logging
# 获取名字为'django.request'的logger, 这个实在django开始运行的视乎,设置的一个logger,想要了解具体是怎样生成的这个logger
# 可以参考我前面的django源码的阅读 https://blog.csdn.net/xujin0/article/details/102533660
logger = logging.getLogger('django.request')
def http_method_not_allowed(self, request, *args, **kwargs):
# request: 每个函数视图中的第一个参数,request实例
# *args: 所有的位置参数
# **kwargs:所有的关键字参数
# 在控制台底部打印一些提示信息,也叫log日志,这里大家可以看一下logging模块的使用,还是挺简单的
# extra中的值,会被绑定在logging.LogRecord这个对象上,我们在自定义logger中的额handle、formatter和filter中可以使用这个对象做一些操作
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
# HttpResponseNotAllowed是一个继承自HttpResponse的类,这个类就是修改了状态码为405,就是返回一个HttpResponse对象
'''
# 这个函数就是找到这个类中写了的所有相关请求方法,并返回。
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
'''
return HttpResponseNotAllowed(self._allowed_methods())
上面我们阅读源码的时候发现,如果请求的方法我么没有写对应的函数,那么就会执行django.views.generic.View().http_method_not_allowed这个函数。
所以我么也可以重写这个函数,返回一下我们自定义的页面或者数据,来达到提示用户的需求。
例如
from django.views.generic import View
from django.http import HttpResponse
class Index(View):
def get(self, request):
return HttpResponse('get method')
def http_method_not_allowed(self, request, *args, **kwargs):
resp = HttpResponse('你请求的姿势不正确')
# 设置状态码405,表示请求方法不允许
resp.status_code = 405
return resp
上面我们就能够自定义返回结果了。我们可以做很多我们想要的操作
到了这里我们就读完了as_view()函数的所有源代码了,相信大家也知道了为什么path的第二个参数可以放as_view()函数的返回值了吧,其实这个函数就是相当于在根据请求方法,动态进入相应的视图函数中。
同时我们也知道View中有一个类属性http_method_names,里面存放了所有支持的请求方法,那么有时候我们也可以重写这个属性,来屏蔽一些方法,就算我们在视图中写了相应请求的方法,只要在http_method_names中没有,也不会进入这个方法。这就是阅读源码的好处,我们能更好的理解框架是怎样运行的,然后我们可以进行一些改装,来实现一些我们的需求。
如果上面对源码的理解有什么错误的地方,欢迎大家想我提出来,我将不胜感激。