经过漫长的阅读代码,搞清了启动的过程。先简单做个总结。如有遗漏之后再做补充。
系统的启动,模块的加载
结合之前研究过的registry,系统启动时会发生如下动作:
- 首先加载全局模块
web,web_kanban。(在没有确定数据库地址之前,只能显示数据库选择页面。所以这个时候只需要这两个模块就可以了)- 在
import controller时,由于元类的作用,controller类会自动加载到解释器中
- 在
- 根据配置创建web服务器(线程的、进程的),所有的服务器都使用
odoo.service.wsgi_server.application来处理请求。- 具体处理请求的是
odoo.http.Root- 根据需要看要不要再次加载插件
- 只有当首次接收请求的时候,才会执行加载插件
- 具体处理请求的是
- 启动服务器
- 在启动web服务器之前,首先创建
registry。(在进程的实现中,registry会在进程fork之前创建,fork之后registry会被拷贝到各个进程的内存空间中) - 当数据库选定之后,
registry会根据配置去加载模块- 先加载
base模块- 先创建
base模块的依赖关系图graph - 使用
graph加载base- 获取模块中的所有模型
- 组装配置模型类
- 根据模型的属性,创建新的模型类,并将模型类注册到
registry中 - 根据模型的属性,为新的模型类添加字段,关联关系等
- 根据模型的属性,创建新的模型类,并将模型类注册到
- 初始化模型
- 根据模型类,创建模型类对应的表
- 装载定义在
__manifast__.py中的模块的数据- 获取文件列表
- 调用
odoo.tools.convert.convert_file装载文件。- 判断文件类型,根据文件类型使用不同的方法解析
- 根据情况将数据写入
ir.model.data - 根据情况将数据写入模型自己的数据表中
- 先创建
- 根据配置标注其它需要加载的模块
- 根据标注加载模块
- 使用
graph进行模块的记载(下边以web模块为例,看一下模块数据的加载)- 获取数据文件:
views/webclient_templates.xml - 调用
odoo.tools.convert.convert_file装载文件- 创建
xml专用的解析器对象xml_import - 解析
xml文件- 遍历整个
xml文档树,根据节点的类型调用不同的函数来进行处理。具体到views/webclient_templates.xml,这个文件由template组成,对应的函数是_tag_template。这个函数在结尾调用_tag_record,这个函数会将数据文件里的内容写入ir_model_data表中。
- 遍历整个
- 创建
- 获取数据文件:
- 使用
- 先加载
- 模块加载完毕后,服务器开始运行。等待处理请求。
- 在启动web服务器之前,首先创建
请求处理
- 首次接受到请求
odoo.http.Root根据请求创建JsonRequest或者HttpRequest- 从
Registry中查找ir.http模型 - 使用
ir.http模型_dispatch转发请求_dispatch查询路由表- 如果路由表不存在,使用
odoo.http.routing_map创建路由表 - 使用路由表获取处理请求的具体
controller - 校验用户
- 将
controller放入请求对象中 - 调用
request.dispatch处理请求
- 如果路由表不存在,使用
- 在
request.dispatch函数中(以HttpRequest为例),对请求参数、请求头、等进行校验,然后通过调用_call_function来调用之前放入request的controller。 - 如果
controller中的处理函数设置了延迟生成页面,则在dispatch结束的时候生成页面。
页面生成
请求处理完成后,要生成显示的页面。以入口地址为例:
- 处理
/请求的controller为addon.web.controllers.main.Home,定义在web模块中,处理方式是直接跳转到/web。 - 处理
/web请求的controller同上,使用web.webclient_bootstrap这个模版来生成页面。
class Home(http.Controller):
@http.route('/', type='http', auth="none")
def index(self, s_action=None, db=None, **kw):
return http.local_redirect('/web', query=request.params, keep_hash=True)
@http.route('/web', type='http', auth="none")
def web_client(self, s_action=None, **kw):
ensure_db()
if not request.session.uid:
return werkzeug.utils.redirect('/web/login', 303)
if kw.get('redirect'):
return werkzeug.utils.redirect(kw.get('redirect'), 303)
request.uid = request.session.uid
context = request.env['ir.http'].webclient_rendering_context()
return request.render('web.webclient_bootstrap', qcontext=context)
- 请求的处理,之前已经提到过了。
request.render函数里,会创建Response对象。不管是否使用延迟创建页面,最终是通过response.render来生成页面。 response.render会用ir.ui.view模型来根据模版来创建页面。具体到/web请求的处理,就是使用ir.ui.view模型的render_template函数,利用web.webclient_bootstrap模版来创建页面。- 先用
ir.ui.view模型的get_view_id函数中,先在ir.model.data模型中查找模版:根据模版的名字,获取res_id:select ir_model_data.id from ir_model_data where module='web' and name='webclient_bootstrap' - 使用基类的
browse函数,创建一个View对象,保存res_id - 调用
View的render函数- 调用
ir.qweb的render函数- 调用
QWeb的render函数- 使用
ir.qweb的load读入模版- 使用
ir.ui.view中的read_template读入模版- 从
ir_ui_view表中获取arch_fs信息,然后读取对应的文件作为arch。
- 从
- 使用
- 调用
ast对模版进行处理,生成一个python函数 - 用生成的函数生成最终的页面
- 使用
- 调用
- 调用
- 先用
通过访问/web只是获得了页面的框架、样式表、js等静态资源。然后odoo会通过自己实现的一套js框架,从服务器获取展示所需的其它部分绘制到页面上。
接下来的部分基于对前端的一些猜测:
当浏览器打开由模版生成的页面后,将页面上相关的静态资源下载到本地。其中的js加载到浏览器后,会向服务器发起请求,来获取当前页面上要的元素:
/web/action/load
获取要展示模块的行为
json请求:{jsonrpc:"2.0",method="call",params:{action_id:261}}服务器会从
ir_action中查询action_id对应的记录,并返回action相关的信息{ jsonrpc:"2.0", result:{ ... xml_id:"qingjia.action_qingjia_qingjd", ... res_model:"qingjiaj.qingjd", search_view:"{'name':'default','arch':....}", .. } }odoo的js框架会根据收到的数据,在页面上添加响应的元素(如按钮)
/web/dataset/call_kw/qingjia.qingjd/load_views
加载模块展示需要用到的view json请求:{jsonrpc:"2.0", method:"call", params:{ model:"qingjia.qingjd", method:"load_views", kwargs:{ views:[[null, "list"],[null,"from"],[false,"search"]], ... } }}服务器会服务器返回结果:
{jsonrpc:"2.0" result:{ fields:{ id:{...}, create_date:{...}, ... }, fields_views:{ form:{}, list:{}, search:{}, }, ... } }可以看到在一次请求中,获得了3种view。这些结果会缓存在浏览器,odoo的js框架之后就不需要反复的去获取view。同时js框架会根据以上内容在页面上生成相应的内容。
……
comments powered by Disqus