基类View
尽管类视图看上去类的种类繁多,但每个类都是各司其职的,且从类的命名就可以很容易地看出这个类的功能。大致可分为如下三个大的功能块,分别由三个类提供对应的方法:
- 处理 HTTP 请求。根据 HTTP 请求方法的不同做出相应处理。例如同一个视图函数既要处理 get 请求,又要处理 post 请求。这一块的功能由
View
类及其派生类实现。 - 渲染模板。这一块功能由
TemplateResponseMixin
及其派生类实现。 - 获取渲染模板所需的模板变量字典(通常称为 context),这个功能由
ContextMixin
及其派生类实现。
现在我们来看看 View
的具体实现,TemplateResponseMixin
以及ContextMixin
将在接下来的系列文章中讲解。
View
Django 类视图的核心就是这个类,这个类是所有其它类视图的基类,它定义所有类视图共有的初始化逻辑,以及一些共有的方法,以便其它类视图继承。始终记住一点,这个类的功能主要是处理不同的 HTTP 请求,因此这个类的属性和方法也是围绕这个功能点设计的。
__init__
先来看看这个类的初始化:
- 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)
源码中的注释其实已经清楚的说明了这个类的作用。http_method_names
属性记录 HTTP 协议所允许的全部 HTTP 方法。初始化 __init__
方法非常简单,就是将所有传入的关键字参数 kwargs
通过 setattr(self, key, value)
设置为类实例的属性。
dispatch
接下来是一个重要的方法 dispatch
,该方法会根据 HTTP 请求方法的不同而将请求转发给类视图中对应的方法处理,先来看代码实现:
- 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)
首先它通过 request.method
(即 HTTP 请求的方法)判断请求的方法是否是被 HTTP 协议所允许的。如果不合法,就会调用错误处理函数 self.http_method_not_allowed
;如果请求方法是合法的,就会试图根据 request.method
去类中寻到对应的处理方法,如果找不到则还是委托给 self.http_method_not_allowed
处理。
可能只看代码有点糊涂,举个例子就能形象地说明 dispatch
方法的处理逻辑。假设 HTTP 请求的方法为 post,则 request.method.lower() == 'post'
。此时 dispatch
将尝试调用类视图的 post
方法,并返回 post
方法调用后的值。而如果类视图中没有定义 post
方法(例如现在所说的 View
类中就没有定义),或者请求的方法不是 post
而是 HTTP 协议未规定的方法如 foo
,那么 dispatch
就会返回调用 http_method_not_allowed
后的结果。
事实上这个方法的处理逻辑放在视图函数中我们就再熟悉不过了:
- def view(request):
- if request.method.lower() == 'get':
- do_something()
- if request.method.lower() == 'post':
- do_something()
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 http.HttpResponseNotAllowed(self._allowed_methods())
即立即返回一个 HttpResponseNotAllowed
,这一个 HttpResponse
对象,根据 HTTP 规定其状态码为 405,代表不允许的 HTTP 方法。
options
在 HTTP 协议规定的方法中,View
类只实现了一个,即 options
方法:
- def options(self, request, *args, **kwargs):
- """
- Handles responding to requests for the OPTIONS HTTP verb.
- """
- response = http.HttpResponse()
- response['Allow'] = ', '.join(self._allowed_methods())
- response['Content-Length'] = '0'
- return response
HTTP 规定客户端使用该方法查询服务器所能处理的全部 HTTP 方法,对任何视图函数来说该方法的逻辑基本是不变的,所以写在了 View
基类中,至于其它需要处理的 HTTP 方法如 post、get 等方法则由 View
的子类根据其具体功能实现。
当然 View
中还有一个辅助方法,就是返回视图类所定义的全部 HTTP 规定的方法。例如在 View 这个类中只定义了 options
方法,所以只会返回 ['options', ]
。
- def _allowed_methods(self):
- return [m.upper() for m in self.http_method_names if hasattr(self, m)]
as_view
最后剩下一个最重要的方法,即 as_view
方法。如果你曾经使用过类视图,那么最熟悉的应该就是这个方法了。要想让类视图生效,必须在 urls.py 的 URL 模式(Pattern)里做类似如下的配置:
- ...
- urlpatterns = [
- url(r'^$', views.IndexView.as_view(), name='index'),
- ]
Django 使用如上的方式配置 URL 到对应视图函数的路由映射。注意到 url() 函数前两个位置参数需要传递的值,第一个是需要捕获的 url 的正则模式,第二个参数则是一个可调用的对象(即视图函数)。如果我们通过 def 定义视图函数,那么传入的这个可调用对象就是这个函数本身;而如果我们定义的是类视图,则必须调用类视图的 as_view
方法返回一个根据这个类生成的可调用对象。类视图所有的魔法就在这个函数里了,来看看 Django 究竟是如何神奇地把一个类转为一个函数的。
- @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
as_view
方法被调用时允许传递一些关键字参数,不过需要做一个点点检查,第一防止你传入诸如 get、post 这样的关键字参数把类本身的 get、post 方法覆盖了;第二是防止你传入未定义为类属性的参数。最开始的 for 循环就是做这个事。
接下来在 as_view
方法中又定义了一个 view
方法,这个方法相信如果你经常写视图函数的话应该非常眼熟,这就是视图函数的标准定义:接收一个 HttpRequest
对象,以及从 url 捕获的非命名组和命名组参数。只不过在 view 这个视图函数里还多做了一点事,它首先实例化了一个类视图对象,然后把函数的参数设置为了这个类视图实例的属性,接着便调用了实例的 dispatch
方法返回视图函数被要求返回的 HttpResponse
对象(注意 dispatch 方法会根据 HTTP 请求方法的不同去调用对应的处理方法)。接着把类中的一些文档字符串和函数名等更新到定义的 view
函数中,然后 as_view
方法返回这个 view
函数。
所以回过头来再看一下我们的 url 模式定义:
- urlpatterns = [
- url(r'^$', views.IndexView.as_view(), name='index'),
- ]
views.IndexView.as_view()
调用后返回的就是一个在 IndexView
里通过 def 定义的视图函数 view
(注意所有类视图都继承自 View
基类),是不是和你直接在这里放一个视图函数是一样的?
进一步理解 View 的逻辑
你可能对这个定义在类 View
的方法 as_view
中的函数 view
的逻辑还是不理解,这里我们通过一种分离的实现方式来加深一下对它的理解。我们假设写了如下的一个视图函数:
- def view(request, *args, **kwargs):
- if request.method.lower() == 'get':
- do_something()
- if request.method.lower() == 'post':
- do_something()
我们很快发现,在很多的视图函数中都复用了这一段代码:
- if request.method.lower() == 'get':
- do_something()
- if request.method.lower() == 'post':
- do_something()
但是写在函数中的代码复用起来是比较麻烦的,想到代码复用,我们立即想到了类继承,于是我们定义一个辅助类:
- class View(object):
- def __init__(request, *args, **kwargs):
- # init
- def get(request, *args, **kwargs):
- do_something()
- def post(request, *args, **kwargs)
- do_something()
让后我们在 view 中实例化这个类并使用它:
- def view(request, *args, **kwargs):
- view_instance = View(request, *args, **kwargs)
- if request.method.lower() == 'get':
- view_instance.get(request, *args, **kwargs)
- if request.method.lower() == 'post':
- view_instance.post(request, *args, **kwargs)
可以看到,这个辅助的 View
类就充当了上述所分析的类视图 View
的功能,而这个视图函数 view 则充当了定义在类视图 as_view
方法中的 view
函数的功能。这种设计思想就是把视图函数的逻辑定义到类的方法里面去,然后在函数中实例化这个类,通过调用类的方法实现函数逻辑,而把逻辑定义在类中的一个好处就是可以通过继承复用这些方法。但是像上述这种函数与类分离的实现方式很麻烦且不优雅,直接把 view 定义在类里,就是 Django 类视图的实现方式了。
总结
现在我们已经明白了类视图的基本结构,其主要功能就是根据 HTTP 请求方法的不同做出相应处理,具体的实现为 dispatch
方法。类视图的核心思想就是把视图函数的逻辑定义到类的方法里面去,然后在函数中实例化这个类,通过调用类的方法实现函数逻辑。基类 View
定义了所有类视图的基本逻辑框架,接下来我们会继续分析一系列基于这个基类 View
定义的更加具体的通用类视图。
基类View的更多相关文章
- django基类View.as_view()
参考:https://www.zmrenwu.com/post/53/ 详细见参考 一般请求的判断方法: def view(request, *args, **kwargs): if request. ...
- 写个Fragment方便的抽象基类 BaseFragment
package com.zb.zhihuianyang.base; import android.app.Activity; import android.os.Bundle; import andr ...
- MVC的基类
设计一个验证用户身份是否登陆的基类BaseController /// <summary> /// 所有需要进行登录控制的控制器基类 /// </summary> public ...
- iOS控制器之基类设计
题记 在进入新公司后.经过这一个月的重构项目,终于把项目做到了个人相对满意的程度(还有一种不满意的叫老板的需求,提过多次意见也没用= =!).在这次重构中按照以前的思路设计出了个人觉得比较适用的一个基 ...
- Thinkphp源码分析系列(七)–控制器基类
在mvc模式中,c代表的就是控制器,是是应用程序中处理用户交互的部分.通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据.控制器是沟通视图和模型的桥梁,他接受用户请求,并调用模型层去处理用户 ...
- 构建自己的PHP框架--抽象Controller的基类
上一篇博客中,我们将简单的路由解析和执行,从入口文件public/index.php中移入到框架中.入口文件顿时变得清爽无比-- 但是,去我们的controller里看一下,会看到如下的code: p ...
- Android 自定义Activity基类与TitleBar
我们在开发App的时候有时候碰到多个界面有一个共同点的时候,比如,都有相同的TitleBar,并且TitleBar可以设置显示的文字.TitleBar上的点击事件,如果给每一个Activity都写一遍 ...
- 基于MVC4+EasyUI的Web开发框架形成之旅--基类控制器CRUD的操作
在上一篇随笔中,我对Web开发框架的总体界面进行了介绍,其中并提到了我的<Web开发框架>的控制器的设计关系,Web开发框架沿用了我的<Winform开发框架>的很多架构设计思 ...
- Delphi 继承基类的窗体,并显示基类的控件操作。
1. 先建一个普通的窗体,until1 2. 先把类实现基类, 并需要实现基类需要继承的方法, 可以先不用再方法中写实现代码. TForm4 = class(TfrmmtAReportPeriod ...
随机推荐
- Java调用webservice接口方法(SOAP message、xfire、axis)
webservice的 发布一般都是使用WSDL(web service descriptive language)文件的样式来发布的,在WSDL文件里面,包含这个webservice暴露在外面可供使 ...
- IOS微信6.7.4输入框失去焦点,软键盘关闭后,被撑起的页面无法回退到原来正常的位置
近期在开发微信H5页面时碰到这个问题,如图,软键盘弹起后,若原输入框被遮挡,页面整体将会上移,然而当输入框失焦,软键盘收起后,页面未恢复,这也是ios的微信版本更新6.7.4之后才遇到的bug. 目前 ...
- pixhawk原生固件在Windows下环境搭建笔记
首先参考了以下几篇博客 博客1:https://zhuanlan.zhihu.com/p/25198079 博客2:http://blog.csdn.net/oqqenvy12/article/det ...
- 云为 | 提供海外 IT 人才派遣、猎头、人力资源外包服务
云为是大连信为软件开发有限公司为人力资源外包服务创建的品牌,是中国专业的人力资源外包领域的服务商,在信息技术行业为海外企业雇主招聘合格.专业且技能熟练的精英人士.我们的客户涵盖了日本上市公司和株式 ...
- SpringBoot集成mybatis和mybatis generator
利用搭建的基本的spring boot框架,集成 mybatis + generator 1.设置 maven 的相关配置: File - setting - maven 设置 Maven home ...
- centOS7虚拟机连接大网
1.启动vm服务 如果遇到无法启动时,需要还原vm默认配置解决 2.更改vm设置为NAT模式 3.centOS开启DHCP
- PagedList.Mvc只有一行时不显示分页
PagedList.Mvc默认总是显示分页,可以通过设置DisplayMode在只有一行时不显示分页 @Html.PagedListPager(Model, page => Url.Action ...
- 日常入新坑,py一下
首先是IDE,因为我经常在Ubuntu 18和win 10两个系统换来换去,所以IDE必须要能跨平台,所以这里就选了PyCharm.Py划重点—— 从Jet Brains的网站下载安装包,直接跟着默认 ...
- 前端三大框架 Vue.js、AngularJS、React 的区别
Vue.js Vue.js 是一种构建数据驱动的Web界面的渐进式框架,Vue.js 采用自底向上增量开发的设计. Vue.js 轻量高效,数据双向绑定(响应式数据绑定), 它会自动响应数据的变化情况 ...
- Shuffle Cards
C: Shuffle Cards 时间限制: 1 Sec 内存限制: 128 MB提交: 3 解决: 3[提交] [状态] [讨论版] [命题人:admin] 题目描述 Eddy likes to ...