Django CBV 模式的源码解析
通常来说, http 请求的本质就是基于 Socket
Django 的视图函数, 可以基于 FBV 模式, 也可以基于 CBV 模式.
基于 FBV 的模式就是在 Django 的路由映射表里进行 url 和视图函数的关联, 而基于 CBV 的模式则是在 views.py 文件中定义视图类, 在视图类中视图函数, 如 get,post,put,delete 等
使用 Django 新建一个项目, 新建一个路由映射
- from django.conf.urls import url
- from django.contrib import admin
- from app01 import views
- urlpatterns = [ url(r'^cbv/$',views.CBV.as_view())
- ]
对应的 views.py 文件内容:
- from django.shortcuts import render,HttpResponse
- from django.views import View
- class CBV(View):
- def get(self,request):
- return HttpResponse("GET")
- def post(self,request):
- return HttpResponse("POST")
启动项目, 使用浏览器请求 URL
http://127.0.0.1:8000/cbv/
, 浏览器显示结果为:
请求到达 Django 会先执行 Django 中间件里的方法, 然后进行进行路由匹配.
在路由匹配完成后, 会执行 CBV 类中的 as_view 方法.
CBV 中并没有定义 as_view 方法, 由于
CBV 继承自 Django 的 View
, 所以
会执行 Django 的 View 类中的 as_view 方法
Django 的 View 类的 as_view 方法的部分源码
- class View(object):
- """
- Intentionally simple parent class for all views. Only implements
- dispatch-by-method and simple sanity checking.
- """ http_method_names = ['get','post','put','patch','delete','head','options','trace']
- def __init__(self, **kwargs):
- """
- Constructor. Called in the URLconf; can contain helpful extra
- keyword arguments, and other things.
- """
- # Go through keyword arguments, and either save their values to our
- # instance, or raise an error.
- for key, value in six.iteritems(kwargs):
- setattr(self, key, value)
- @classonlymethod
- def as_view(cls, **initkwargs):
- """
- Main entry point for a request-response process.
- """
- for key in initkwargs:
- 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__))
- 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))
- def view(request, *args, **kwargs):
- 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
- return self.dispatch(request, *args, **kwargs)
- view.view_class = cls
- view.view_initkwargs = initkwargs
- # take name and docstring from class
- update_wrapper(view, cls, updated=())
- # and possible attributes set by decorators
- # like csrf_exempt from dispatch
- update_wrapper(view, cls.dispatch, assigned=())
- return view
- 从 View 的源码可以看出, 在 View 类中, 先定义了 http 请求的八种方法
- http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
- 在 as_view 方法中进行判断, 如果请求的方法没在 http_method_names 中, 则会抛出异常,
- 这里的 cls 实际上指的是自定义的 CBV 类
- 接着 as_view 方法中又定义 view 方法, 在 view 方法中对 CBV 类进行实例化, 得到 self 对象, 然后在 self 对象中封装浏览器发送的 request 请求
- self = cls(**initkwargs)
- 最后又调用了 self 对象中的 dispatch 方法并返回 dispatch 方法的值来对 request 进行处理
- 此时,
- 由于 self 对象就是 CBV 实例化得到, 所以会先执行自定义的 CBV 类中的 dispatch 方法. 如果 CBV 类中没有定义 dispatch 方法则执行 Django 的 View 中的 dispatch 方法
- Django 的 View 中的 dispatch 方法源码
- class View(object):
- """
- 中间省略
- """
- 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.
- 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)
在 dispatch 方法中, 把 request.method 转换为小写再判断是否在定义的 http_method_names 中, 如果 request.method 存在于 http_method_names 中, 则使用 getattr 反射的方式来得到 handler
在这里的 dispatch 方法中, self 指的是自定义的 CBV 类实例化得到的对象
从 CBV 类中获取 request.method 对应的方法, 再执行 CBV 中的方法并返回
由此, 可以知道
如果在 Django 项目中使用 CBV 的模式, 实际上调用了 getattr 的方式来执行获取类中的请求方法对应的函数
结论:
CBV 基于反射实现根据请求方式不同, 执行不同的方法
自定义 dispatch 方法
如果想在基于 CBV 模式的项目中在请求某个 url 时执行一些操作, 则可以在 url 对应的类中定义 dispatch 方法
修改 views.py 文件
- class CBV(View):
- def dispatch(self, request, *args, **kwargs):
- func = getattr(self,request.method.lower())
- return func(request,*args,**kwargs)
- def get(self,request):
- return HttpResponse("GET")
- def post(self,request):
- return HttpResponse("POST")
也可以使用继承的方式重写 dispatch 方法:
- class CBV(View):
- def dispatch(self, request, *args, **kwargs):
- print("before")
- res = super(CBV, self).dispatch(request, *args, **kwargs)
- print("after")
- return res
- def get(self,request):
- return HttpResponse("GET")
- def post(self,request):
- return HttpResponse("POST")
刷新浏览器, Django 后台打印结果如下:
浏览器页面结果
同理, 如果有基于 CBV 的多个类, 并且有多个类共用的功能, 为了避免重复, 可以单独定义一个类, 在这个类中重写 dispatch 方法, 然后让 url 对应的视图类继承这个类
修改 urls.py 文件
- from django.conf.urls import url
- from django.contrib import admin
- from app01 import views
- urlpatterns = [
- url(r'^cbv1/$',views.CBV1.as_view()),
- url(r'^cbv2/$',views.CBV2.as_view()),
- ]
views.py 文件内容
- from django.shortcuts import render,HttpResponse
- from django.views import View
- class BaseView(object):
- def dispatch(self, request, *args, **kwargs):
- func = getattr(self, request.method.lower())
- return func(request, *args, **kwargs)
- class CBV1(BaseView,View):
- def get(self,request):
- return HttpResponse("CBV1 GET")
- def post(self,request):
- return HttpResponse("CBV1 POST")
- class CBV2(BaseView,View):
- def get(self,request):
- return HttpResponse("CBV2 GET")
- def post(self,request):
- return HttpResponse("CBV2 POST")
通过 python 的面向对象可以知道, 请求到达视图类时, 会先执行 CBV1 和 CBV2 类中的 dispatch 方法, 然而 CBV1 和 CBV2 类中并没有 dispatch 方法, 则会按照顺序在父类中查找 dispatch 方法, 此时就会执行 BaseView 类中的 dispatch 方法了
用浏览器请求 url
http://127.0.0.1:8000/cbv1/
, 浏览器页面显示
用浏览器请求 url
http://127.0.0.1:8000/cbv2/
, 浏览器页面显示
来源: https://www.cnblogs.com/renpingsheng/p/9531649.html