第十九章 服务混合类
服务混合类作为WC中基础服务提供类,提供了很多全局基础功能。
我们前面提到的模型类也是继承自服务混合类,因此本章所介绍的功能在模型及其子模型中都可以直接使用。
var Model = Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
...
}
RPC服务
服务混合类提供的重要功能之一就是为模型提供RPC接口,模型可以直接使用_rpc方法调用odoo的RPC服务接口。
var query = rpc.buildQuery(params);
var prom = this.call('ajax', 'rpc', query.route, query.params, options, this);
if (!prom) {
prom = new Promise(function () {});
prom.abort = function () {};
}
var abort = prom.abort ? prom.abort : prom.reject;
if (!abort) {
throw new Error("a rpc promise should always have a reject function");
}
prom.abort = abort.bind(prom);
return prom;
},
RPC作为WC的核心功能之一,这里有必要进行一个详细的介绍。
RPC服务不是一个Odoo类,它只是一个仅仅包含了两个函数的集合对象。
RPC服务包含两个函数:
- query: query函数用来执行一个RPC调用,内部实现使用了buildQuery函数。
- buildQuery: 构建RPC调用的路由和参数函数。
RPC细节
想要了解RPC如何使用,那么我们就必须清楚,WC是如何发起一个RPC请求的。
我们知道,我们在浏览器的中所有明确了模型和方法的数据操作都是由JsonRPC代为封装请求,其路由格式为 /web/dataset/call_kw/{model_name}/{method_name}。然后再将方法的参数赋值到参数args,将上下文对象context,模型model和方法名method赋值到kwargs参数中,然后发起Http请求。
而rpc服务的核心就是将用户指定的参数,组装成满足要求的数据,然后发起调用。RPC服务依赖于Ajax服务, 实际的调用由Ajax服务完成。
RPC服务的buildQuery方法用来格式化请求数据,query方法使用Ajax服务完成调用。buildQuery内部针对请求的数据格式不同做了不同的逻辑判断:
- 如果options中包含route那么直接使用options中的route
- 否则检查options中是否指定了model和method,如果是,那么设定请求路由为:/web/dataset/call_kw/{model_name}/{method_name}
- 如果options中包含method,那么设置params参数中的各种参数与method匹配
- 如果method是read_group或web_read_group(分组),那么在请求中设置groupby等参数。
- 如果method是search_read,那么设置参数中的kwargs与options中的参数匹配。
- 如果options的route是/web/dataset/search_read,设置options中的参数与params中的参数匹配。
我们来看一个直接使用rpc服务的示例:
var rpc = require("web.rpc");
rpc.query({
"model": "ir.config_parameter",
"method": "get_param",
"args": ["abc"]
}).then((result) => {
if (result) {
... //do the right ting.
}
});
根据我们之前的学习,query方法的第一个参数即使不用明确指明route和params参数,系统也能够根据params中的数据正确地配置组织相应的数据,也就是说,上面的写法等同于:
var rpc = require("web.rpc");
rpc.query({
route: '/web/dataaset/call_kw/ir.config_parameter/get_param',
params: {
"model": "ir.config_parameter",
"method": "get_param",
"args": ["abc"]
}
}).then((result) => {
if (result) {
... //do the right ting.
}
});
执行动作
服务混合类提供的另外一个重要的功能即提供了直接调用后台动作(ir.actions)的方法。
do_action: function (action, options) {
var self = this;
return new Promise(function (resolve, reject) {
self.trigger_up('do_action', {
action: action,
options: options,
on_success: resolve,
on_fail: reject,
});
});
},
从do_action的定义中可以看出来,其原理是用了trigger_up方法调用了do_action事件。
flags
在ir.actions.act_window中存在一个特殊的字段flags, 该字段并不是一个真实的字段, 只是用来在后端配置传递参数到前端页面的一个控制变量.
这点我们可以从flags在WC中的定义可以看出:
function _getActionInfo(action, props) {
return {
props: Object.assign({}, props, { action, actionId: action.id }),
config: {
actionId: action.id,
actionType: action.type,
actionFlags: action.flags,
displayName: action.display_name || action.name || "",
views: action.views,
},
};
}
WC将后台传递过来的flags参数赋值给action的actionFlags属性, 然后在渲染视图的时候将actionFlags属性传递给前端视图的构造函数, 从而达到控制视图的目的.
this.viewParams = Object.assign({}, actionFlags, {
action,
// legacy views automatically add the last part of the breadcrumbs
breadcrumbs: breadcrumbsToLegacy(breadcrumbs),
modelName: this.props.resModel,
currentId: this.props.resId,
controllerState: {
currentId:
"resId" in this.props
? this.props.resId
: this.props.state && this.props.state.currentId,
resIds: this.props.resIds || resIds,
searchModel,
searchPanel,
},
});
可以将flags理解为WC给后台留的一个隐秘的"武器", 这个"武器"并没有在后端的"武器库"中注册,但是却可以在返回动作中实现对前台视图元素的控制.
这里简单举一个案例用法, 笔者在给上海某BPM公司开发任务管理系统时碰到了下面一个需求, 要求用户在单机列表视图中的查看任务按钮时,弹出的FORM窗口不能显示创建和取消按钮.
很显然,我们需要在按钮的返回动作中返回一个FORM窗体, 但是问题在于如何控制创建和取消按钮的显示, 答案就是使用flags参数,将defaultButtons参数设置为False
return {
"type": "ir.actions.act_window",
"name": "任务反馈汇总",
"view_mode":"form",
"res_model":"k2.worklog.wizard",
"res_id": wizard.id,
"views":[(self.env.ref("k2_project.view_worklogs_wizard_form").id,"form")],
"flags":{
"form":{
"default_buttons": False
}
},
"target":"new"
}