summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/basic/basic_model.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js/views/basic/basic_model.js')
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js589
1 files changed, 312 insertions, 277 deletions
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index b4d343d7eb0..00e2fc43f6d 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -23,7 +23,6 @@ odoo.define('web.BasicModel', function (require) {
* count: {integer},
* data: {Object|Object[]},
* domain: {*[]},
- * fieldNames: {string[]},
* fields: {Object},
* fieldsInfo: {Object},
* getContext: {function},
@@ -145,7 +144,7 @@ var BasicModel = AbstractModel.extend({
/**
* Add a default record to a list object. This method actually make a new
- * record with the makeDefaultRecord method, then add it to the list object.
+ * record with the _makeDefaultRecord method, then add it to the list object.
*
* @param {string} listID a valid handle for a list object
* @param {Object} [options]
@@ -160,13 +159,13 @@ var BasicModel = AbstractModel.extend({
var position = (options && options.position) || 'top';
var params = {
context: context,
- fieldsInfo: list.fieldsInfo,
- fieldNames: list.fieldNames,
fields: list.fields,
+ fieldsInfo: list.fieldsInfo,
parentID: list.id,
relationField: list.relationField,
+ viewType: list.viewType,
};
- return this.makeDefaultRecord(list.model, params).then(function (id) {
+ return this._makeDefaultRecord(list.model, params).then(function (id) {
list.count++;
if (position === 'top') {
list.data.unshift(id);
@@ -243,11 +242,11 @@ var BasicModel = AbstractModel.extend({
record.res_ids.splice(index + 1, 0, res_id);
return self.load({
fieldsInfo: record.fieldsInfo,
- fieldNames: record.fieldNames,
fields: record.fields,
modelName: record.model,
res_id: res_id,
res_ids: record.res_ids.slice(0),
+ viewType: record.viewType,
});
});
},
@@ -400,8 +399,6 @@ var BasicModel = AbstractModel.extend({
*
* @param {any} params
* @param {Object} [params.fieldsInfo={}] contains the fieldInfo of each field
- * @param {Array} [params.fieldNames] the name of fields to load, the list
- * of all fields by default
* @param {Object} params.fields contains the description of each field
* @param {string} [params.type] 'record' or 'list'
* @param {string} [params.recordID] an ID for an existing resource.
@@ -409,9 +406,20 @@ var BasicModel = AbstractModel.extend({
*/
load: function (params) {
params.type = params.type || (params.res_id !== undefined ? 'record' : 'list');
+ // FIXME: the following seems only to be used by the basic_model_tests
+ // so it should probably be removed and the tests should be adapted
+ params.viewType = params.viewType || 'default';
+ if (!params.fieldsInfo) {
+ var fieldsInfo = {};
+ for (var fieldName in params.fieldNames) {
+ fieldsInfo[params.fieldNames[fieldName]] = {};
+ }
+ params.fieldsInfo = {};
+ params.fieldsInfo[params.viewType] = fieldsInfo;
+ }
if (params.type === 'record' && params.res_id === undefined) {
- return this.makeDefaultRecord(params.modelName, params);
+ return this._makeDefaultRecord(params.modelName, params);
}
var dataPoint = this._makeDataPoint(params);
return this._load(dataPoint).then(function () {
@@ -419,195 +427,13 @@ var BasicModel = AbstractModel.extend({
});
},
/**
- * When one needs to create a record from scratch, a not so simple process
- * needs to be done:
- * - call the /default_get route to get default values
- * - fetch all relational data
- * - apply all onchanges if necessary
- * - fetch all relational data
- *
- * This method tries to optimize the process as much as possible. Also,
- * it is quite horrible and should be refactored at some point.
- *
- * @param {any} params
- * @param {string} modelName model name
- * @param {Object} params.context the context for the new record
- * @param {Object} [params.fieldsInfo={}] contains the fieldInfo of each field
- * @param {Array} params.fieldNames the name of fields to load, the list
- * of all fields by default
- * @param {Object} params.fields contains the description of each field
- * @param {Object} params.context the context for the new record
- * @returns {Deferred -> string} resolves to the id for the created resource
- */
- makeDefaultRecord: function (modelName, params) {
- var self = this;
- var fields_key = _.without(params.fieldNames, '__last_update');
-
- return this._rpc({
- model: modelName,
- method: 'default_get',
- args: [fields_key],
- context: params.context,
- })
- .then(function (result) {
- // fill default values for missing fields
- for (var i = 0; i < params.fieldNames.length; i++) {
- var fieldName = params.fieldNames[i];
- if (!(fieldName in result)) {
- var field = params.fields[fieldName];
- if (field.type === 'one2many' || field.type === 'many2many') {
- result[fieldName] = [];
- } else {
- result[fieldName] = null;
- }
- }
- }
-
- var data = {};
- var record = self._makeDataPoint({
- modelName: modelName,
- data: data,
- fields: params.fields,
- fieldNames: params.fieldNames,
- fieldsInfo: params.fieldsInfo,
- context: params.context,
- parentID: params.parentID,
- relationField: params.relationField,
- res_ids: params.res_ids,
- });
-
- var defs = []; // FIXME: remove defs?
- _.each(params.fieldNames, function (name) {
- var field = params.fields[name];
- data[name] = null;
- record._changes = record._changes || {};
- if (field.type === 'many2one' && result[name]) {
- var rec = self._makeDataPoint({
- context: record.context,
- data: {id: result[name]},
- modelName: field.relation,
- });
- record._changes[name] = rec.id;
- } else if (field.type === 'one2many' || field.type === 'many2many') {
- var fieldInfo = record.fieldsInfo[name];
- var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
-
- var x2manyList = self._makeDataPoint({
- context: record.context,
- fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo,
- fields: fieldInfo.relatedFields || view && view.fields,
- limit: fieldInfo.limit,
- modelName: field.relation,
- parentID: record.id,
- rawContext: fieldInfo && fieldInfo.context,
- relationField: field.relation_field,
- res_ids: [],
- static: true,
- type: 'list',
- });
- record._changes[name] = x2manyList.id;
- var many2ones = {};
- var r;
- _.each(result[name], function (value) {
- if (_.isArray(value)) {
- // value is a command
- if (value[0] === 0) {
- // CREATE
- r = self._makeDataPoint({
- modelName: x2manyList.model,
- context: x2manyList.context,
- fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo,
- fields: fieldInfo.relatedFields || view && view.fields,
- });
- x2manyList._changes.push(r.id);
- r._changes = value[2];
-
- // this is necessary so the fields are initialized
- for (var fieldName in value[2]) {
- r.data[fieldName] = null;
- }
-
- for (var name in r._changes) {
- if (r.fields[name].type === 'many2one') {
- var rec = self._makeDataPoint({
- context: r.context,
- modelName: r.fields[name].relation,
- data: {id: r._changes[name]}
- });
- r._changes[name] = rec.id;
- many2ones[name] = true;
- }
- }
- }
- if (value[0] === 6) {
- // REPLACE_WITH
- x2manyList._changes = [];
- }
- } else {
- // value is an id
- r = self._makeDataPoint({
- modelName: x2manyList.model,
- context: x2manyList.context,
- fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo,
- fields: fieldInfo.relatedFields || view && view.fields,
- res_id: value,
- });
- if (!x2manyList._changes) {
- x2manyList._changes = [];
- }
- x2manyList._changes.push(r.id);
- }
- });
-
- // fetch many2ones display_name
- _.each(_.keys(many2ones), function (name) {
- defs.push(self._fetchNameGets(x2manyList, name));
- });
- } else if (field.type === 'date') {
- // process date: convert into a moment instance
- record._changes[name] = fieldUtils.parse.date(result[name]);
- } else if (field.type === 'datetime') {
- // process datetime: convert into a moment instance
- record._changes[name] = fieldUtils.parse.datetime(result[name]);
- } else {
- record._changes[name] = result[name];
- }
- });
- return $.when.apply($, defs)
- .then(function () {
- var shouldApplyOnchange = false;
- var field;
- for (var field_name in record.data) {
- field = record.fields[field_name];
- if (field.onChange) {
- shouldApplyOnchange = true;
- }
- }
- if (shouldApplyOnchange) {
- return self._applyOnChange(record, fields_key);
- } else {
- return $.when();
- }
- })
- .then(function () {
- return self._fetchRelationalData(record);
- })
- .then(function () {
- return self._postprocess(record);
- })
- .then(function () {
- return record.id;
- });
- });
- },
- /**
* This helper method is designed to help developpers that want to use a
* field widget outside of a view. In that case, we want a way to create
* data without actually performing a fetch.
*
* @param {string} model name of the model
* @param {Object[]} fields a description of field properties
- * @param {Object} [fieldInfo] various field fieldInfo that we want to set
+ * @param {Object} [fieldInfo] various field info that we want to set
* @returns {string} the local id for the created resource
*/
makeRecord: function (model, fields, fieldInfo) {
@@ -616,10 +442,15 @@ var BasicModel = AbstractModel.extend({
_.each(fields, function (field) {
record_fields[field.name] = _.pick(field, 'type', 'relation', 'domain');
});
+ var viewInfo = {};
+ viewInfo.default = fieldInfo || _.mapObject(fields, function () {
+ return {};
+ });
var record = this._makeDataPoint({
modelName: model,
fields: record_fields,
- fieldsInfo: fieldInfo,
+ fieldsInfo: viewInfo,
+ viewType: 'default',
});
_.each(fields, function (field) {
if ('value' in field) {
@@ -670,10 +501,10 @@ var BasicModel = AbstractModel.extend({
var params = {
context: element.context,
fieldsInfo: element.fieldsInfo,
- fieldNames: element.fieldNames,
fields: element.fields,
+ viewType: element.viewType,
};
- return this.makeDefaultRecord(element.model, params);
+ return this._makeDefaultRecord(element.model, params);
}
this.discardChanges(id);
}
@@ -703,7 +534,7 @@ var BasicModel = AbstractModel.extend({
if (element.type === 'record') {
element.offset = _.indexOf(element.res_ids, element.res_id);
}
- var loadOptions = _.pick(options, 'fieldNames');
+ var loadOptions = _.pick(options, 'fieldNames', 'viewType');
return this._load(element, loadOptions).then(function (result) {
return result.id;
});
@@ -762,7 +593,8 @@ var BasicModel = AbstractModel.extend({
}
if (method === 'create') {
- _.each(record.fieldNames, function (name) {
+ var fieldNames = record.getFieldNames();
+ _.each(fieldNames, function (name) {
changes[name] = changes[name] || record.data[name];
if (changes[name] === null) {
delete changes[name];
@@ -799,34 +631,19 @@ var BasicModel = AbstractModel.extend({
});
},
/**
- * Set fieldNames.
- * Set fields on a record element (current fields as defaults)
- * Set fieldsInfo.
- * It is mostly useful for the cases where a record element is shared
- * between various views, such as a one2many with a tree and a form view.
+ * Completes the fields and fieldsInfo of a dataPoint with the given ones.
+ * It is useful for the cases where a record element is shared between
+ * various views, such as a one2many with a tree and a form view.
*
* @param {string} recordID a valid element ID
- * @param {Object} props
- * @param {Object} props.fieldNames
- * @param {Object} props.fields
- * @param {Object} props.fieldsInfo
- * @return {Object}
- * @return {Object} result.fieldNames the previous field names
- * @return {Object} result.fields the previous description fields
- * @return {Object} result.fieldsInfo the previous field fieldInfo
+ * @param {Object} viewInfo
+ * @param {Object} viewInfo.fields
+ * @param {Object} viewInfo.fieldsInfo
*/
- setFieldProps: function (recordID, props) {
+ addFieldsInfo: function (recordID, viewInfo) {
var record = this.localData[recordID];
- var result = {
- fieldNames: record.fieldNames,
- fields: record.fields,
- fieldsInfo: record.fieldsInfo,
- };
- record.mode = props.mode;
- record.fieldNames = Object.keys(props.fieldsInfo);
- record.fields = _.defaults(props.fields, record.fields);
- record.fieldsInfo = props.fieldsInfo;
- return result;
+ record.fields = _.defaults(record.fields, viewInfo.fields);
+ record.fieldsInfo = _.defaults(record.fieldsInfo, viewInfo.fieldsInfo);
},
/**
* For list resources, this changes the orderedBy key, then performs the
@@ -1029,7 +846,8 @@ var BasicModel = AbstractModel.extend({
var commands = this._generateX2ManyCommands(record);
// compute field values
- var currentData = _.pick(_.extend(rawData, commands), record.fieldNames);
+ var fieldNames = record.getFieldNames();
+ var currentData = _.pick(_.extend(rawData, commands), fieldNames);
if (record.relationField) {
// if there is a relation field, this means that record is an elem
// in a one2many. The relation field is the corresponding many2one
@@ -1099,16 +917,16 @@ var BasicModel = AbstractModel.extend({
fields: list.fields,
fieldsInfo: list.fieldsInfo,
modelName: list.model,
- fieldNames: _.keys(command[2]),
+ viewType: list.viewType,
};
if (command[0] === 1) {
params.res_id = command[1];
}
rec = self._makeDataPoint(params);
var data = command[2];
- _.each(rec.fieldNames, function (name) {
+ _.each(Object.keys(data), function (name) {
var field = rec.fields[name];
- if (field.type === 'many2one') {
+ if (field && field.type === 'many2one') {
var r = self._makeDataPoint({
modelName: field.relation,
data: {
@@ -1161,7 +979,7 @@ var BasicModel = AbstractModel.extend({
var self = this;
var list = this.localData[record._changes[fieldName] || record.data[fieldName]];
var field = record.fields[fieldName];
- var fieldInfo = record.fieldsInfo[fieldName];
+ var fieldInfo = record.fieldsInfo[record.viewType][fieldName];
var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
var rec;
var defs = [];
@@ -1179,7 +997,10 @@ var BasicModel = AbstractModel.extend({
// list's fields with those in the created record
var newRecord = this.localData[command.id];
_.defaults(list.fields, newRecord.fields);
+ _.defaults(list.fieldsInfo, newRecord.fieldsInfo);
newRecord.fields = list.fields;
+ newRecord.fieldsInfo = list.fieldsInfo;
+ newRecord.viewType = list.viewType;
list._changes.push(newRecord.id);
break;
case 'ADD_M2M':
@@ -1193,9 +1014,10 @@ var BasicModel = AbstractModel.extend({
rec = self._makeDataPoint({
context: record.context,
modelName: field.relation,
- fields: fieldInfo.relatedFields || view && view.fields,
- fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo,
+ fields: view ? view.fields : fieldInfo.relatedFields,
+ fieldsInfo: view ? view.fieldsInfo : fieldInfo.fieldsInfo,
res_id: d.id,
+ viewType: view ? view.type : fieldInfo.viewType,
});
list_records[d.id] = rec;
list._changes.push(rec.id);
@@ -1205,15 +1027,16 @@ var BasicModel = AbstractModel.extend({
// _changes so this is a very specific case)
// this could be optimized by registering the fetched records in the list's _cache
// so that if a record is removed and then re-added, it won't be fetched twice
+ var fieldNames = list.getFieldNames();
var def = this._rpc({
model: list.model,
method: 'read',
- args: [_.pluck(data, 'id'), list.fieldNames]
+ args: [_.pluck(data, 'id'), fieldNames]
})
.then(function (records) {
_.each(records, function (record) {
list_records[record.id].data = record;
- self._parseServerData(list.fieldNames, list.fields, record);
+ self._parseServerData(fieldNames, list.fields, record);
});
});
defs.push(def);
@@ -1272,12 +1095,12 @@ var BasicModel = AbstractModel.extend({
_buildOnchangeSpecs: function (record) {
// TODO: replace this function by some generic tree function in utils
var specs = {};
- _.each(record.fieldNames, function (name) {
+ _.each(record.getFieldNames(), function (name) {
var field = record.fields[name];
- var fieldInfo = record.fieldsInfo[name];
+ var fieldInfo = record.fieldsInfo[record.viewType][name];
specs[name] = (field.onChange) || "";
_.each(fieldInfo.views, function (view) {
- _.each(view.fieldsInfo, function (field, subname) {
+ _.each(view.fieldsInfo[view.type], function (field, subname) {
specs[name + '.' + subname] = (view.fields[subname].onChange) || "";
});
});
@@ -1335,13 +1158,18 @@ var BasicModel = AbstractModel.extend({
* For a given resource of type 'record', fetch all data.
*
* @param {Object} record local resource
- * @param {string[]} [fieldNames] the list of fields to fetch. If not given,
- * fetch all the fields in record.fieldNames (+ display_name)
+ * @param {Object} [options]
+ * @param {string[]} [options.fieldNames] the list of fields to fetch. If
+ * not given, fetch all the fields in record.fieldNames (+ display_name)
+ * @param {string} [optinos.viewType] the type of view for which the record
+ * is fetched (usefull to load the adequate fields), by defaults, uses
+ * record.viewType
* @returns {Deferred -> Object} resolves to the record
*/
- _fetchRecord: function (record, fieldNames) {
+ _fetchRecord: function (record, options) {
var self = this;
- fieldNames = fieldNames || _.uniq(record.fieldNames.concat(['display_name']));
+ var fieldNames = options && options.fieldNames ||
+ _.uniq(record.getFieldNames().concat(['display_name']));
return this._rpc({
model: record.model,
method: 'read',
@@ -1356,7 +1184,7 @@ var BasicModel = AbstractModel.extend({
self._parseServerData(fieldNames, record.fields, record.data);
})
.then(function () {
- return self._fetchX2Manys(record, fieldNames).then(function () {
+ return self._fetchX2Manys(record, options).then(function () {
return self._postprocess(record);
});
});
@@ -1369,7 +1197,7 @@ var BasicModel = AbstractModel.extend({
* batching the requests if possible (for example, if 3 many2ones are in
* relation on the same model, then we can probably fetch them in one rpc)
*
- * This method is currently only called by makeDefaultRecord, it should be
+ * This method is currently only called by _makeDefaultRecord, it should be
* called by the onchange methods at some point.
*
* @todo fix bug: returns a list of deferred, not a deferred
@@ -1382,7 +1210,7 @@ var BasicModel = AbstractModel.extend({
var toBeFetched = [];
// find all many2one related records to be fetched
- _.each(record.fieldNames, function (name) {
+ _.each(record.getFieldNames(), function (name) {
var field = record.fields[name];
if (field.type === 'many2one') {
var localId = (record._changes && record._changes[name]) || record.data[name];
@@ -1417,8 +1245,8 @@ var BasicModel = AbstractModel.extend({
_fetchSpecialData: function (record) {
var self = this;
var fieldNames = [];
- return $.when.apply($, _.map(record.fieldNames, function (name) {
- var fieldInfo = record.fieldsInfo[name] || {};
+ return $.when.apply($, _.map(record.getFieldNames(), function (name) {
+ var fieldInfo = record.fieldsInfo[record.viewType][name] || {};
var Widget = fieldInfo.Widget;
if (Widget && Widget.prototype.specialData) {
return self[Widget.prototype.specialData](record, name).then(function (data) {
@@ -1551,7 +1379,7 @@ var BasicModel = AbstractModel.extend({
* the undefined value.
*/
_fetchSpecialStatus: function (record, fieldName) {
- var foldField = record.fieldsInfo[fieldName].options.fold_field;
+ var foldField = record.fieldsInfo[record.viewType][fieldName].options.fold_field;
var fieldsToRead = foldField ? [foldField] : [];
return this._fetchSpecialMany2ones(record, fieldName, fieldsToRead).then(function (m2os) {
_.each(m2os, function (m2o) {
@@ -1575,7 +1403,7 @@ var BasicModel = AbstractModel.extend({
_fetchSpecialDomain: function (record, fieldName) {
var context = record.getContext({fieldName: fieldName});
- var domainModel = record.fieldsInfo[fieldName].options.model;
+ var domainModel = record.fieldsInfo[record.viewType][fieldName].options.model;
if (record.data.hasOwnProperty(domainModel)) {
domainModel = record._changes && record._changes[domainModel] || record.data[domainModel];
}
@@ -1642,24 +1470,31 @@ var BasicModel = AbstractModel.extend({
* useless rpcs.
*
* @param {Object} record local resource
- * @param {string[]} [fieldNames] list of fields to fetch
+ * @param {Object} [options]
+ * @param {string[]} [options.fieldNames] the list of fields to fetch.
+ * If not given, fetch all the fields in record.fieldNames
+ * @param {string} [options.viewType] the type of view for which the main
+ * record is fetched (useful to load the adequate fields), by defaults,
+ * uses record.viewType
* @returns {Deferred}
*/
- _fetchX2Manys: function (record, fieldNames) {
+ _fetchX2Manys: function (record, options) {
var self = this;
var defs = [];
- fieldNames = fieldNames || record.fieldNames;
+ var fieldNames = options && options.fieldNames || record.getFieldNames();
+ var viewType = options && options.viewType || record.viewType;
_.each(fieldNames, function (fieldName) {
var field = record.fields[fieldName];
if (field.type === 'one2many' || field.type === 'many2many') {
- var fieldInfo = record.fieldsInfo[fieldName];
+ var fieldInfo = record.fieldsInfo[viewType][fieldName];
var rawContext = fieldInfo && fieldInfo.context;
var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
+ var fieldsInfo = view ? view.fieldsInfo : (fieldInfo.fieldsInfo || {});
var ids = record.data[fieldName] || [];
var list = self._makeDataPoint({
count: ids.length,
- fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo,
- fields: fieldInfo.relatedFields || view && view.fields,
+ fieldsInfo: fieldsInfo,
+ fields: view ? view.fields : fieldInfo.relatedFields,
limit: fieldInfo.limit,
modelName: field.relation,
res_ids: ids,
@@ -1668,6 +1503,7 @@ var BasicModel = AbstractModel.extend({
parentID: record.id,
rawContext: rawContext,
relationField: field.relation_field,
+ viewType: view ? view.type : fieldInfo.viewType,
});
record.data[fieldName] = list.id;
if (!fieldInfo.__no_fetch) {
@@ -1691,7 +1527,7 @@ var BasicModel = AbstractModel.extend({
_fetchX2ManyBatched: function (list, fieldName) {
var self = this;
var field = list.fields[fieldName];
- var fieldInfo = list.fieldsInfo[fieldName];
+ var fieldInfo = list.fieldsInfo[list.viewType][fieldName];
var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
// step 1: collect ids
@@ -1755,10 +1591,11 @@ var BasicModel = AbstractModel.extend({
*/
_fetchX2ManysBatched: function (list) {
var defs = [];
- for (var i = 0; i < list.fieldNames.length; i++) {
- var field = list.fields[list.fieldNames[i]];
+ var fieldNames = list.getFieldNames();
+ for (var i = 0; i < fieldNames.length; i++) {
+ var field = list.fields[fieldNames[i]];
if (field.type === 'many2many' || field.type === 'one2many') {
- defs.push(this._fetchX2ManyBatched(list, list.fieldNames[i]));
+ defs.push(this._fetchX2ManyBatched(list, fieldNames[i]));
}
}
return $.when.apply($, defs);
@@ -1853,7 +1690,8 @@ var BasicModel = AbstractModel.extend({
context.set_eval_context(this._getEvalContext(element));
if (options && options.fieldName) {
- var fieldInfo = element.fieldsInfo[options.fieldName];
+ var viewType = options.viewType || element.viewType;
+ var fieldInfo = element.fieldsInfo[viewType][options.fieldName];
if (fieldInfo && fieldInfo.context) {
context.add(fieldInfo.context);
} else {
@@ -1885,7 +1723,8 @@ var BasicModel = AbstractModel.extend({
*/
_getDomain: function (element, options) {
if (options && options.fieldName) {
- var fieldInfo = element.fieldsInfo[options.fieldName];
+ var viewType = options.viewType || element.viewType;
+ var fieldInfo = element.fieldsInfo[viewType][options.fieldName];
if (fieldInfo && fieldInfo.domain) {
return Domain.prototype.stringToArray(
fieldInfo.domain,
@@ -1931,6 +1770,16 @@ var BasicModel = AbstractModel.extend({
return evalContext;
},
/**
+ * Returns the list of field names of the given element according to its
+ * default view type.
+ *
+ * @param {Object} element an element from the localData
+ * @returns {string[]} the list of field names
+ */
+ _getFieldNames: function (element) {
+ return Object.keys(element.fieldsInfo[element.viewType] || {});
+ },
+ /**
* Helper method for the load entry point.
*
* @see load
@@ -1942,7 +1791,7 @@ var BasicModel = AbstractModel.extend({
*/
_load: function (dataPoint, options) {
if (dataPoint.type === 'record') {
- return this._fetchRecord(dataPoint, options && options.fieldNames);
+ return this._fetchRecord(dataPoint, options);
}
if (dataPoint.type === 'list' && dataPoint.groupedBy.length) {
return this._readGroup(dataPoint);
@@ -1979,13 +1828,6 @@ var BasicModel = AbstractModel.extend({
id: {type: 'integer'},
}, params.fields);
- if (!params.fieldsInfo) {
- params.fieldsInfo = {};
- for (var fieldName in fields) {
- params.fieldsInfo[fieldName] = {};
- }
- }
-
var dataPoint = {
_cache: type === 'list' ? {} : undefined,
_changes: null,
@@ -1994,9 +1836,8 @@ var BasicModel = AbstractModel.extend({
count: params.count || res_ids.length,
data: params.data || (type === 'record' ? {} : []),
domain: params.domain || [],
- fieldNames: params.fieldNames || Object.keys(params.fieldsInfo),
fields: fields,
- fieldsInfo: params.fieldsInfo || {},
+ fieldsInfo: params.fieldsInfo,
groupedBy: params.groupedBy || [],
id: _.uniqueId(params.modelName + '_'),
isOpen: params.isOpen,
@@ -2015,16 +1856,208 @@ var BasicModel = AbstractModel.extend({
static: params.static || false,
type: type, // 'record' | 'list'
value: value,
+ viewType: params.viewType,
};
dataPoint.getContext = this._getContext.bind(this, dataPoint);
dataPoint.getDomain = this._getDomain.bind(this, dataPoint);
+ dataPoint.getFieldNames = this._getFieldNames.bind(this, dataPoint);
this.localData[dataPoint.id] = dataPoint;
return dataPoint;
},
/**
+ * When one needs to create a record from scratch, a not so simple process
+ * needs to be done:
+ * - call the /default_get route to get default values
+ * - fetch all relational data
+ * - apply all onchanges if necessary
+ * - fetch all relational data
+ *
+ * This method tries to optimize the process as much as possible. Also,
+ * it is quite horrible and should be refactored at some point.
+ *
+ * @private
+ * @param {any} params
+ * @param {string} modelName model name
+ * @param {Object} params.context the context for the new record
+ * @param {Object} params.fieldsInfo contains the fieldInfo of each view,
+ * for each field
+ * @param {Object} params.fields contains the description of each field
+ * @param {Object} params.context the context for the new record
+ * @param {string} params.viewType the key in fieldsInfo of the fields to load
+ * @returns {Deferred -> string} resolves to the id for the created resource
+ */
+ _makeDefaultRecord: function (modelName, params) {
+ var self = this;
+ var fieldNames = Object.keys(params.fieldsInfo[params.viewType]);
+ var fields_key = _.without(fieldNames, '__last_update');
+
+ return this._rpc({
+ model: modelName,
+ method: 'default_get',
+ args: [fields_key],
+ context: params.context,
+ })
+ .then(function (result) {
+ // fill default values for missing fields
+ for (var i = 0; i < fieldNames.length; i++) {
+ var fieldName = fieldNames[i];
+ if (!(fieldName in result)) {
+ var field = params.fields[fieldName];
+ if (field.type === 'one2many' || field.type === 'many2many') {
+ result[fieldName] = [];
+ } else {
+ result[fieldName] = null;
+ }
+ }
+ }
+
+ var data = {};
+ var record = self._makeDataPoint({
+ modelName: modelName,
+ data: data,
+ fields: params.fields,
+ fieldsInfo: params.fieldsInfo,
+ context: params.context,
+ parentID: params.parentID,
+ relationField: params.relationField,
+ res_ids: params.res_ids,
+ viewType: params.viewType,
+ });
+
+ var defs = []; // FIXME: remove defs?
+ _.each(fieldNames, function (name) {
+ var field = params.fields[name];
+ data[name] = null;
+ record._changes = record._changes || {};
+ if (field.type === 'many2one' && result[name]) {
+ var rec = self._makeDataPoint({
+ context: record.context,
+ data: {id: result[name]},
+ modelName: field.relation,
+ });
+ record._changes[name] = rec.id;
+ } else if (field.type === 'one2many' || field.type === 'many2many') {
+ var fieldInfo = record.fieldsInfo[record.viewType][name];
+ var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode];
+ var fieldsInfo = view ? view.fieldsInfo : fieldInfo.fieldsInfo;
+ var fields = view ? view.fields : fieldInfo.relatedFields;
+ var viewType = view ? view.type : fieldInfo.viewType;
+
+ var x2manyList = self._makeDataPoint({
+ context: record.context,
+ fieldsInfo: fieldsInfo,
+ fields: fields,
+ limit: fieldInfo.limit,
+ modelName: field.relation,
+ parentID: record.id,
+ rawContext: fieldInfo && fieldInfo.context,
+ relationField: field.relation_field,
+ res_ids: [],
+ static: true,
+ type: 'list',
+ viewType: viewType,
+ });
+ record._changes[name] = x2manyList.id;
+ var many2ones = {};
+ var r;
+ _.each(result[name], function (value) {
+ if (_.isArray(value)) {
+ // value is a command
+ if (value[0] === 0) {
+ // CREATE
+ r = self._makeDataPoint({
+ modelName: x2manyList.model,
+ context: x2manyList.context,
+ fieldsInfo: fieldsInfo,
+ fields: fields,
+ viewType: viewType,
+ });
+ x2manyList._changes.push(r.id);
+ r._changes = value[2];
+
+ // this is necessary so the fields are initialized
+ for (var fieldName in value[2]) {
+ r.data[fieldName] = null;
+ }
+
+ for (var name in r._changes) {
+ if (r.fields[name].type === 'many2one') {
+ var rec = self._makeDataPoint({
+ context: r.context,
+ modelName: r.fields[name].relation,
+ data: {id: r._changes[name]}
+ });
+ r._changes[name] = rec.id;
+ many2ones[name] = true;
+ }
+ }
+ }
+ if (value[0] === 6) {
+ // REPLACE_WITH
+ x2manyList._changes = [];
+ }
+ } else {
+ // value is an id
+ r = self._makeDataPoint({
+ modelName: x2manyList.model,
+ context: x2manyList.context,
+ fieldsInfo: fieldsInfo,
+ fields: fields,
+ res_id: value,
+ viewType: viewType,
+ });
+ if (!x2manyList._changes) {
+ x2manyList._changes = [];
+ }
+ x2manyList._changes.push(r.id);
+ }
+ });
+
+ // fetch many2ones display_name
+ _.each(_.keys(many2ones), function (name) {
+ defs.push(self._fetchNameGets(x2manyList, name));
+ });
+ } else if (field.type === 'date') {
+ // process date: convert into a moment instance
+ record._changes[name] = fieldUtils.parse.date(result[name]);
+ } else if (field.type === 'datetime') {
+ // process datetime: convert into a moment instance
+ record._changes[name] = fieldUtils.parse.datetime(result[name]);
+ } else {
+ record._changes[name] = result[name];
+ }
+ });
+ return $.when.apply($, defs)
+ .then(function () {
+ var shouldApplyOnchange = false;
+ var field;
+ for (var field_name in record.data) {
+ field = record.fields[field_name];
+ if (field.onChange) {
+ shouldApplyOnchange = true;
+ }
+ }
+ if (shouldApplyOnchange) {
+ return self._applyOnChange(record, fields_key);
+ } else {
+ return $.when();
+ }
+ })
+ .then(function () {
+ return self._fetchRelationalData(record);
+ })
+ .then(function () {
+ return self._postprocess(record);
+ })
+ .then(function () {
+ return record.id;
+ });
+ });
+ },
+ /**
* parse the server values to javascript framwork
*
* @param {[string]} fieldNames
@@ -2070,7 +2103,7 @@ var BasicModel = AbstractModel.extend({
* quite a lot of computations to determine what needs to be fetched. This
* method is doing that.
*
- * @see _fetchRecord @see makeDefaultRecord
+ * @see _fetchRecord @see _makeDefaultRecord
*
* @param {any} record
* @returns {Deferred -> Object} resolves to the finished resource
@@ -2078,9 +2111,9 @@ var BasicModel = AbstractModel.extend({
_postprocess: function (record) {
var self = this;
var defs = [];
- _.each(record.fieldNames, function (name) {
+ _.each(record.getFieldNames(), function (name) {
var field = record.fields[name];
- var fieldInfo = record.fieldsInfo[name] || {};
+ var fieldInfo = record.fieldsInfo[record.viewType][name] || {};
var options = fieldInfo.options || {};
if (options.always_reload) {
if (record.fields[name].type === 'many2one' && record.data[name]) {
@@ -2114,7 +2147,7 @@ var BasicModel = AbstractModel.extend({
*/
_readGroup: function (list) {
var self = this;
- var fields = _.uniq(list.fieldNames.concat(list.groupedBy));
+ var fields = _.uniq(list.getFieldNames().concat(list.groupedBy));
return this._rpc({
model: list.model,
method: 'read_group',
@@ -2146,7 +2179,6 @@ var BasicModel = AbstractModel.extend({
domain: group.__domain,
context: list.context,
fields: list.fields,
- fieldNames: list.fieldNames,
fieldsInfo: list.fieldsInfo,
value: group[rawGroupBy],
aggregateValues: aggregateValues,
@@ -2155,6 +2187,7 @@ var BasicModel = AbstractModel.extend({
limit: list.limit,
openGroupByDefault: list.openGroupByDefault,
type: 'list',
+ viewType: list.viewType,
});
list.data.push(newGroup.id);
list.count += newGroup.count;
@@ -2201,6 +2234,7 @@ var BasicModel = AbstractModel.extend({
var ids = [];
var missingIds = [];
var upper_bound = list.limit ? Math.min(list.offset + list.limit, list.count) : list.count;
+ var fieldNames = list.getFieldNames();
for (var i = list.offset; i < upper_bound; i++) {
var id = list.res_ids[i];
ids.push(id);
@@ -2212,7 +2246,7 @@ var BasicModel = AbstractModel.extend({
def = this._rpc({
model: list.model,
method: 'read',
- args: [missingIds, list.fieldNames],
+ args: [missingIds, fieldNames],
context: {} // FIXME
});
} else {
@@ -2228,14 +2262,14 @@ var BasicModel = AbstractModel.extend({
dataPoint = self._makeDataPoint({
data: _.findWhere(records, {id: id}),
fieldsInfo: list.fieldsInfo,
- fieldNames: list.fieldNames,
fields: list.fields,
modelName: list.model,
parentID: list.id,
+ viewType: list.viewType,
});
// add many2one records
- self._parseServerData(list.fieldNames, dataPoint.fields, dataPoint.data);
+ self._parseServerData(fieldNames, dataPoint.fields, dataPoint.data);
list._cache[id] = dataPoint.id;
}
list.data.push(dataPoint.id);
@@ -2272,10 +2306,11 @@ var BasicModel = AbstractModel.extend({
*/
_searchReadUngroupedList: function (list) {
var self = this;
+ var fieldNames = list.getFieldNames();
return this._rpc({
route: '/web/dataset/search_read',
model: list.model,
- fields: list.fieldNames,
+ fields: fieldNames,
domain: list.domain || [],
limit: list.limit,
offset: list.offset,
@@ -2287,14 +2322,14 @@ var BasicModel = AbstractModel.extend({
var dataPoint = self._makeDataPoint({
data: record,
fields: list.fields,
- fieldNames: list.fieldNames,
fieldsInfo: list.fieldsInfo,
modelName: list.model,
parentID: list.id,
+ viewType: list.viewType,
});
// add many2one records
- self._parseServerData(list.fieldNames, dataPoint.fields, dataPoint.data);
+ self._parseServerData(fieldNames, dataPoint.fields, dataPoint.data);
return dataPoint.id;
});
list.data = data;