diff options
| author | qsm-odoo <qsm@odoo.com> | 2017-03-10 12:28:26 +0100 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2017-04-11 19:44:38 +0200 |
| commit | b5b649e5b21d41279f12f0dbe6cfa53ca2c52580 (patch) | |
| tree | 3f62e4586180fafee9f992b0dec52dcdaa61665f /addons/web/static/src/js/views/basic/basic_model.js | |
| parent | ba1a2d7145606e22ae316a636a1852a7ae80a3df (diff) | |
[FIX] web: fetching special data in field widgets
Some field widgets need additional data to render themself. These data
should ideally be fetched in the data fetching phase. In 10.0, these
data were fetched during field rendering which implied that the data
fetching was done sequentially. To limit this effect, before this
commit, these special fields were flagged by the basic view according
to some field properties and the basic model flagged the fields with
the required data. Now, the system has been improved. The field widget
classes can define a key "specialData" which refers to a BasicModel
method. These methods will be used to fetch the required data at the
right time and these data will be available at
`this.record.specialData[this.name]` in the AbstractField instance.
The new system also fixes a bug: the required data was fetched only
at record loading but should in fact be re-fetched at each view
change.
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.js | 221 |
1 files changed, 152 insertions, 69 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 f6c2021f2e8..cdb8bb61f7c 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -41,6 +41,8 @@ odoo.define('web.BasicModel', function (require) { * relationField: {string}, * res_id: {integer|null}, * res_ids: {integer[]}, + * specialData: {Object}, + * _specialDataCache: {Object}, * static: {boolean}, * type: {string} 'record' | 'list' * value: ?, @@ -133,14 +135,6 @@ var BasicModel = AbstractModel.extend({ // save is performed. this.mutex = new concurrency.Mutex(); - // this dictionary is used to hold all many2one informations for widget - // selection. - this.many2ones = {}; - - // this dictionary is used to hold all many2many informations for widget - // many2many_checkboxes. - this.many2manys = {}; - this.localData = Object.create(null); this._super.apply(this, arguments); }, @@ -330,16 +324,6 @@ var BasicModel = AbstractModel.extend({ element.data[fieldName] = this.get(element.data[fieldName]) || []; } } - - // weird way to get out many2ones for widget=selection - if (field.__fetch_selection) { - field.__selection_information = self.many2ones[field.relation]; - } - - // weird way to get out many2manys for widget=many2many_checkboxes - if (field.__fetch_many2manys) { - field.__many2many_information = self.many2manys[field.relation]; - } } // this is not strictly necessary, but it hides some implementation @@ -697,7 +681,11 @@ var BasicModel = AbstractModel.extend({ delete record._changes[name]; } }); - return fieldNames; + return self._fetchSpecialData(record).then(function (fieldNames2) { + // Return the names of the fields that changed (onchange or + // associated special data change) + return _.union(fieldNames, fieldNames2); + }); }); }); }, @@ -1372,6 +1360,147 @@ var BasicModel = AbstractModel.extend({ return $.when.apply($, _.map(groups, this._fetchMany2OneGroup.bind(this))); }, /** + * Check the AbstractField specializations that are (will be) used by the + * given record and fetch the special data they will need. Special data are + * data that the rendering of the record won't need if it was not using + * particular widgets (example of these can be found at the methods which + * start with _fetchSpecial). + * + * @param {Object} record - an element from the localData + * @returns {Deferred<Array>} + * The deferred is resolved with an array containing the names of + * the field whose special data has been changed. + */ + _fetchSpecialData: function (record) { + var self = this; + var fieldNames = []; + return $.when.apply($, _.map(record.fieldNames, function (name) { + var attrs = record.fieldAttrs[name] || {}; + var Widget = attrs.Widget; + if (Widget && Widget.prototype.specialData) { + return self[Widget.prototype.specialData](record, name).then(function (data) { + if (data === undefined) { + return; + } + record.specialData[name] = data; + fieldNames.push(name); + }); + } + })).then(function () { + return fieldNames; + }); + }, + /** + * Fetches all the m2o records associated to the given fieldName. If the + * given fieldName is not a m2o field, nothing is done. + * + * @param {Object} record - an element from the localData + * @param {Object} fieldName - the name of the field + * @param {string[]} [fieldsToRead] - the m2os fields to read (id and + * display_name are automatic). + * @returns {Deferred<any>} + * The deferred is resolved with the fetched special data. If this + * data is the same as the previously fetched one (for the given + * parameters), no RPC is done and the deferred is resolved with + * the undefined value. + */ + _fetchSpecialMany2ones: function (record, fieldName, fieldsToRead) { + var field = record.fields[fieldName]; + if (field.type !== "many2one") { + return $.when(); + } + + var context = record.getContext({fieldName: fieldName}); + var domain = record.getDomain({fieldName: fieldName}); + if (domain.length) { + var localID = record.data[fieldName]; + var element = this.localData[localID]; + domain = ["|", ["id", "=", element.data.id]].concat(domain); + } + + // avoid rpc if not necessary + var specialDataCache = {context: context, domain: domain}; + if (_.isEqual(record._specialDataCache[fieldName], specialDataCache)) { + return $.when(); + } + record._specialDataCache[fieldName] = specialDataCache; + + var self = this; + return this._rpc(field.relation, 'search_read') + .withFields(["id"].concat(fieldsToRead || [])) + .withContext(context) + .withDomain(domain) + .exec() + .then(function (result) { + var ids = _.pluck(result.records, 'id'); + return self._rpc(field.relation, 'name_get') + .args([ids]) + .withContext(context) + .exec() + .then(function (name_gets) { + _.each(result.records, function (rec) { + var name_get = _.find(name_gets, function (n) { + return n[0] === rec.id; + }); + rec.display_name = name_get[1]; + }); + return result.records; + }); + }); + }, + /** + * Fetches all the relation records associated to the given fieldName. If + * the given fieldName is not a relational field, nothing is done. + * + * @param {Object} record - an element from the localData + * @param {Object} fieldName - the name of the field + * @returns {Deferred<any>} + * The deferred is resolved with the fetched special data. If this + * data is the same as the previously fetched one (for the given + * parameters), no RPC is done and the deferred is resolved with + * the undefined value. + */ + _fetchSpecialRelation: function (record, fieldName) { + var field = record.fields[fieldName]; + if (!_.contains(["many2one", "many2many", "one2many"], field.type)) { + return $.when(); + } + + var context = record.getContext({fieldName: fieldName}); + var domain = record.getDomain({fieldName: fieldName}); + + // avoid rpc if not necessary + var specialDataCache = {context: context, domain: domain}; + if (_.isEqual(record._specialDataCache[fieldName], specialDataCache)) { + return $.when(); + } + record._specialDataCache[fieldName] = specialDataCache; + + return this._rpc(field.relation, "name_search") + .args(["", domain]) + .withContext(context) + .exec(); + }, + /** + * Fetches all the m2o records associated to the given fieldName. If the + * given fieldName is not a m2o field, nothing is done. The difference with + * _fetchSpecialMany2ones is that the field given by options.fold_field is + * also fetched. + * + * @param {Object} record - an element from the localData + * @param {Object} fieldName - the name of the field + * @returns {Deferred<any>} + * The deferred is resolved with the fetched special data. If this + * data is the same as the previously fetched one (for the given + * parameters), no RPC is done and the deferred is resolved with + * the undefined value. + */ + _fetchSpecialStatus: function (record, fieldName) { + var foldField = record.fieldAttrs[fieldName].options.fold_field; + var fieldsToRead = foldField ? [foldField] : []; + return this._fetchSpecialMany2ones(record, fieldName, fieldsToRead); + }, + /** * Fetch all data in a ungrouped list * * @param {Object} list a valid resource object @@ -1767,6 +1896,8 @@ var BasicModel = AbstractModel.extend({ relationField: params.relationField, res_id: res_id, res_ids: res_ids, + specialData: {}, + _specialDataCache: {}, static: params.static || false, type: type, // 'record' | 'list' value: value, @@ -1833,38 +1964,6 @@ var BasicModel = AbstractModel.extend({ var field = record.fields[name]; var attrs = record.fieldAttrs[name] || {}; var options = attrs.options || {}; - if (field.__fetch_status && !field.__status_information) { - var field_values = _.mapObject(record.data, function (val, key) { - var fieldType = record.fields[key].type; - if (fieldType === 'many2one') { - var element = self.localData[val]; - return element && element.data.id || false; - } - return val; - }); - var domain = Domain.prototype.stringToArray(field.domain || [], field_values); - var fetch_status_information = self._rpc(field.relation, 'search_read') - .withFields(['id'].concat(options.fold_field ? [options.fold_field] : [])) - .withDomain(domain) - .exec() - .then(function (result) { - var ids = _.pluck(result.records, 'id'); - return self._rpc(field.relation, 'name_get') - .args([ids]) - .withContext(self._getContext(record, {fieldName: name})) - .exec() - .then(function (name_gets) { - _.each(result.records, function (record) { - var name_get = _.find(name_gets, function (n) { - return n[0] === record.id; - }); - record.display_name = name_get[1]; - }); - field.__status_information = result.records; - }); - }); - defs.push(fetch_status_information); - } if (options.always_reload) { if (record.fields[name].type === 'many2one' && record.data[name]) { var element = self.localData[record.data[name]]; @@ -1877,26 +1976,10 @@ var BasicModel = AbstractModel.extend({ })); } } - if (field.__fetch_selection && !self.many2ones[field.relation]) { - var fetchSelection = self._rpc(field.relation, 'name_search') - .args(['', field.domain]) - .exec() - .then(function (result) { - self.many2ones[field.relation] = result; - }); - defs.push(fetchSelection); - } - if (field.__fetch_many2manys) { - var fetchMany2Manys = self._rpc(field.relation, 'name_search') - .args(['', field.domain]) - .exec() - .then(function (result) { - self.many2manys[field.relation] = result; - }); - defs.push(fetchMany2Manys); - } }); + defs.push(this._fetchSpecialData(record)); + return $.when.apply($, defs).then(function () { return record; }); |
