diff options
| author | Aaron Bohy <aab@odoo.com> | 2017-03-31 14:10:46 +0200 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2017-04-11 19:44:38 +0200 |
| commit | 3d959382fd92aca6c48ea2413c646edcc1f178f4 (patch) | |
| tree | cd66edd59c9629b08bf314afd9156dbfb8d1bfa8 /addons/web/static/src/js/views/basic | |
| parent | 2d449f8f1ac314452285e0952a9228b1460a0910 (diff) | |
[FIX] web,mail: one2many records: multiple views
A record of the basic model may need to be displayed in different
views (e.g. a o2m record is displayed in the, e.g., Kanban view
inside the main form view, and if clicked, it is also displayed
in the form view opened in a dialog). However, the fields present
in those views may differ.
The previous implementation of fieldsInfo in the record didn't
handle that, as it was a dict whose keys were field names. With
this commit, the fieldsInfo is dict whose keys are view types, and
the values are dicts whose keys are field names (the fields of
the corresponding views, with they attrs in the view).
This allows to create multiple o2m records in a row in a form view.
Diffstat (limited to 'addons/web/static/src/js/views/basic')
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_model.js | 589 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_renderer.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_view.js | 11 |
3 files changed, 321 insertions, 281 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; diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index d8ddf02d169..2aa4479648b 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -64,7 +64,7 @@ return AbstractRenderer.extend({ */ _getFieldValues: function (record) { var result = {}; - _.each(record.fieldNames, function (name) { + _.each(record.getFieldNames(), function (name) { var field = record.fields[name]; var value = record.data[name]; result[name] = value; diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index 5b8a3a7d0c9..46ecb5d7e38 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -40,9 +40,9 @@ var BasicView = AbstractView.extend({ 'action_buttons' in params ? params.action_buttons : true; this.loadParams.fieldsInfo = viewInfo.fieldsInfo; - this.loadParams.fieldNames = _.keys(viewInfo.fieldsInfo); this.loadParams.fields = viewInfo.fields; this.loadParams.limit = parseInt(viewInfo.arch.attrs.limit, 10) || params.limit; + this.loadParams.viewType = this.viewType; this.recordID = params.recordID; }, @@ -60,9 +60,14 @@ var BasicView = AbstractView.extend({ _loadData: function () { if (this.recordID) { var record = this.model.get(this.recordID); - var fieldNames = _.difference(record.fieldNames, Object.keys(record.data)); + var viewType = this.viewType; + var viewFields = Object.keys(record.fieldsInfo[viewType]); + var fieldNames = _.difference(viewFields, Object.keys(record.data)); if (fieldNames.length && !this.model.isNew(record.id)) { - return this.model.reload(this.recordID, {fieldNames: fieldNames}); + return this.model.reload(this.recordID, { + fieldNames: fieldNames, + viewType: viewType, + }); } else { return $.when(this.recordID); } |
