转载:http://book.odoomommy.com/chapter3/README6.html
第六章 基础模型
基础模型(Basic Model)包含了Python模型数据与web client通信所必须的全部业务逻辑,其作用是给出了一个对其余的web client对象来说足够简单和统一的API,以帮助它们(视图、字段Widget)来实现查询和更新数据库中的数据 的需求。
基础模型本质上是一个哈希字典,其值可能是整数、对象或者元对象。每个哈希字典的对象都代表了一小块数据,并且可以通过id作为key进行重载或者更新。
下面是一个数据点(data point)的样例:
var dataPoint = {
_cache: {Object|undefined}
_changes: {Object|null},
aggregateValues: {Object},
context: {Object},
count: {integer},
data: {Object|Object[]},
domain: {*[]},
fields: {Object},
fieldsInfo: {Object},
getContext: {function},
getDomain: {function},
getFieldNames: {function},
groupedBy: {string[]},
id: {integer},
isOpen: {boolean},
loadMoreOffset: {integer},
limit: {integer},
model: {string},
offset: {integer},
openGroupByDefault: {boolean},
orderedBy: {Object[]},
orderedResIDs: {integer[]},
parentID: {string},
rawContext: {Object},
relationField: {string},
res_id: {integer|null},
res_ids: {integer[]},
specialData: {Object},
_specialDataCache: {Object},
static: {boolean},
type: {string} 'record' | 'list'
value: ?,
};
需要注意的几点:
- id: 与res_id完全没有关系的id,是web client端的概念,与数据库中的记录没有任何关系。
- res_id: 数据库中的记录id,如果它的值是数字或者虚拟ID(虚拟ID是一个由数字和中划线和其他信息组成的字符串),它是一个数据库中真实存在的记录。如果它的值是一个virtual_和数字组成的值,那么它就是一个还未保存到数据库中的值(通常出现在create模式中)。
- res_ids: 代表了数据点被使用的上下文。举例来说,在list视图中打开了某一个form记录,假设被打开的记录是1,那么res_id=1,而res_ids可能的值是[1,2,3]。
- offset: 分页中使用,如果需要加载另外一页时使用。
- count: 被操作的记录数,不能使用res_id的原因是,res_ids的数据可能很大,或者由于domain或分页的原因,res_ids的值并非全部的数据。
- model: 模型的名称,例如'res.partner'
- fields: 从模型中获取的所有的字段的描述信息,它的属性可能被视图更新,所以字段类型依赖于数据点的上下文。
- fields_names: 相关字段名称组成的列表,通常,它暗示了视图中出现的字段列表,只有出现在视图中的字段才应该被加载到这里。
- _cache和_changes都是私有属性,不应该在Model外被使用。
特性
我们知道分组视图可以显示某些列的汇总信息,那么什么类型的字段可以被分组,是由BasicModel的AGGREGATABLE_TYPES属性指定的,只有三种字段可以被分组: float、integer和monetary。
const AGGREGATABLE_TYPES = ['float', 'integer', 'monetary'];
我们知道x2many字段有一组命令用来创建或者更新记录的值,同样的,在web client中更新x2many字段的值也是用的同一组命令(0-6),稍微不同的是[0,_,valus]命令的参数是一个virtual id,它意味着当此命令给到基础模型时,它会产生一个virtual_加数字类型的记录Id, 系统用它来定位元素或者之后进行更新。
var x2ManyCommands = {
// (0, virtualID, {values})
CREATE: 0,
create: function (virtualID, values) {
delete values.id;
return [x2ManyCommands.CREATE, virtualID || false, values];
},
// (1, id, {values})
UPDATE: 1,
update: function (id, values) {
delete values.id;
return [x2ManyCommands.UPDATE, id, values];
},
// (2, id[, _])
DELETE: 2,
delete: function (id) {
return [x2ManyCommands.DELETE, id, false];
},
// (3, id[, _]) removes relation, but not linked record itself
FORGET: 3,
forget: function (id) {
return [x2ManyCommands.FORGET, id, false];
},
// (4, id[, _])
LINK_TO: 4,
link_to: function (id) {
return [x2ManyCommands.LINK_TO, id, false];
},
// (5[, _[, _]])
DELETE_ALL: 5,
delete_all: function () {
return [5, false, false];
},
// (6, _, ids) replaces all linked records with provided ids
REPLACE_WITH: 6,
replace_with: function (ids) {
return [6, false, ids];
}
};
从代码中,我们可以总结出来一点,就是对web client的x2many Widget而言其实不存在什么更新编辑之说,其编辑的本质是先进行删除又重新创建,这个特性造成的结果就是(我们在onchange一章中提到过),onchange事件返回的永远是一个新创建的NewID,而不是原先的记录ID。
属性
基础模型中有一个属性OPEN_GROUP_LIMIT, 意思是当数据量超过这个数据,分组将进行折叠操作。还有一个属性noCacheModels,是一个模型列表,此列表中的模型在进行操作时将不作缓存处理。
OPEN_GROUP_LIMIT: 10, // after this limit, groups are automatically folded
// list of models for which the DataManager's cache should be cleared on
// create, update and delete operations
noCacheModels: [
'ir.actions.act_window',
'ir.filters',
'ir.ui.view',
]
初始化
基础模型的初始化过程,先实例化了一个互斥锁,用来确保操作能够按序进行。然后初始化了一个批量RPC请求属性batchedRPCsRequests,然后实例化了一个空对象localData用来存储后续的数据。
init: function () {
// this mutex is necessary to make sure some operations are done
// sequentially, for example, an onchange needs to be completed before a
// save is performed.
this.mutex = new concurrency.Mutex();
// this array is used to accumulate RPC requests done in the same call
// stack, so that they can be batched in the minimum number of RPCs
this.batchedRPCsRequests = [];
this.localData = Object.create(null);
this._super.apply(this, arguments);
},
公开方法
addDefaultRecord
给list对象添加默认记录,方法接受两个参数,listID和options,listID是数据集中的id,options的参数只有一个position,其值可以为top或者bottom,表示新记录应该出现在列表的顶部还是底部,默认为top。
addDefaultRecord: function (listID, options) {
var self = this;
var list = this.localData[listID];
var context = _.extend({}, this._getDefaultContext(list), this._getContext(list));
var position = (options && options.position) || 'top';
var params = {
context: context,
fields: list.fields,
fieldsInfo: list.fieldsInfo,
parentID: list.id,
position: position,
viewType: list.viewType,
};
return this._makeDefaultRecord(list.model, params).then(function (id) {
list.count++;
if (position === 'top') {
list.data.unshift(id);
} else {
list.data.push(id);
}
var record = self.localData[id];
list._cache[record.res_id] = id;
return id;
});
}
此方法实际上使用了_makeDefaultRecord方法创建了一个新的记录,然后将其添加到了list对象中,默认记录直接被添加到了数据中,也就意味着只能被List或者看板控制器使用(对于x2many字段来说,实际存储的是命令组)。