From 7b8001a847e9a89dc7203c5cf2ead715b9bcd45b Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 25 Oct 2017 10:15:25 +0200 Subject: [FIX] hr_expense,web: explicit link_to command for o2m The ORM doesn't automatically link subrecords when it receives an update command for a one2many. However, it may be necessary when the default_get returns existing records for a one2many field. It only worked before this rev. for the hr_expense model because of an hack from 2016 (to reproduce: go to Expenses, check some expenses from the list view, in Actions, click on 'Submit to Manager', the new hr_expense_sheet contains the checked expenses (one2many), and the webclient only sent an update command by linked subrecord). This rev. fixes the problem properly: when the default_get returns existing subrecords for a one2many field, a link_to command (4) is generated alongside the update (1) command. --- addons/web/static/src/js/views/basic/basic_model.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'addons/web/static/src/js/views/basic/basic_model.js') 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 c450e2bedc7..1d8e68c3e62 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -2634,13 +2634,21 @@ var BasicModel = AbstractModel.extend({ } commands[fieldName].push(command); } else if (_.contains(addedIds, list.res_ids[i])) { - // this is a new id + // this is a new id (maybe existing in DB, but new in JS) relRecord = _.findWhere(relRecordAdded, {res_id: list.res_ids[i]}); changes = this._generateChanges(relRecord, options); if ('id' in changes) { + // the subrecord already exists in db delete changes.id; - commands[fieldName].push(x2ManyCommands.update(relRecord.res_id, changes)); + if (this.isNew(record.id)) { + // if the main record is new, link the subrecord to it + commands[fieldName].push(x2ManyCommands.link_to(relRecord.res_id)); + } + if (!_.isEmpty(changes)) { + commands[fieldName].push(x2ManyCommands.update(relRecord.res_id, changes)); + } } else { + // the subrecord is new, so create it commands[fieldName].push(x2ManyCommands.create(changes)); } } -- cgit v1.2.3 From 6fa63eb15ca809fce2770f8ee75af592a4609c6a Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 25 Oct 2017 13:18:35 +0200 Subject: [FIX] web: default value for o2m inside o2m A traceback was raised when opening a new record for a model with a one2many displayed inside a one2many (e.g. simply displaying the number of records in the relation), and with an onchange setting a default value to the inner one2many (for example, linking it to existing records). As the inner o2m has no subviews, it has no fieldsInfo, and the code assumed that fieldsInfo was always set. This was for example reproducible from v11 as follows: - create a product (with MTO/Buy and call for tender (need purchase agreement module)) - create an SO with this product and confirm it - go to purchase order form view and add a move_dest_ids field in the tree definition of field order_line - go to menu purchase agreement and choose the PA generated by your SO - add a vendor, confirm the PA and click on 'New Quotation'. --- addons/web/static/src/js/views/basic/basic_model.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'addons/web/static/src/js/views/basic/basic_model.js') 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 1d8e68c3e62..2cf96815b5e 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -2881,7 +2881,8 @@ var BasicModel = AbstractModel.extend({ * @returns {boolean} */ _isFieldProtected: function (record, fieldName, viewType) { - var fieldInfo = record.fieldsInfo[viewType || record.viewType][fieldName]; + var fieldInfo = record.fieldsInfo && + (record.fieldsInfo[viewType || record.viewType][fieldName]); if (fieldInfo) { var rawModifiers = JSON.parse(fieldInfo.modifiers || "{}"); var modifiers = this._evalModifiers(record, rawModifiers); -- cgit v1.2.3 From c163351e8a6776e046f04e6c264bf4372499dbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 25 Oct 2017 21:50:37 +0200 Subject: [FIX] web: revert to previous state when onchange fails When an onchange fails (for example, because the onchange python code raised a ValidationError, it is convenient to revert the form view to a valid state, which is the previous state. To do that, we keep in the _applyChange function the initial state, and restore it in the case the onchange fails. --- .../web/static/src/js/views/basic/basic_model.js | 41 ++++++++++++---------- 1 file changed, 23 insertions(+), 18 deletions(-) (limited to 'addons/web/static/src/js/views/basic/basic_model.js') 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 2cf96815b5e..27a1e12ecfe 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1109,6 +1109,10 @@ var BasicModel = AbstractModel.extend({ if (!options.doNotSetDirty) { record._isDirty = true; } + var initialData = {}; + this._visitChildren(record, function (elem) { + initialData[elem.id] = $.extend(true, {}, _.pick(elem, 'data', '_changes')); + }); // apply changes to local data for (var fieldName in changes) { @@ -1137,12 +1141,18 @@ var BasicModel = AbstractModel.extend({ } } } - var onchangeDef; + var onchangeDef = $.Deferred(); if (onChangeFields.length) { - onchangeDef = self._performOnChange(record, onChangeFields, options.viewType).then(function (result) { - delete record._warning; - return _.keys(changes).concat(Object.keys(result && result.value || {})); - }); + self._performOnChange(record, onChangeFields, options.viewType) + .then(function (result) { + delete record._warning; + onchangeDef.resolve(_.keys(changes).concat(Object.keys(result && result.value || {}))); + }).fail(function () { + self._visitChildren(record, function (elem) { + _.extend(elem, initialData[elem.id]); + }); + onchangeDef.resolve({}); + }); } else { onchangeDef = $.Deferred().resolve(_.keys(changes)); } @@ -3232,15 +3242,18 @@ var BasicModel = AbstractModel.extend({ }); return $.when.apply($, defs) .then(function () { - return self._performOnChange(record, fields_key).then(function () { + var def = $.Deferred(); + self._performOnChange(record, fields_key).always(function () { if (record._warning) { if (params.allowWarning) { delete record._warning; } else { - return $.Deferred().reject(); + def.reject(); } } + def.resolve(); }); + return def; }) .then(function () { return self._fetchRelationalData(record); @@ -3307,10 +3320,7 @@ var BasicModel = AbstractModel.extend({ * @param {string[]} fields changed fields * @param {string} [viewType] current viewType. If not set, we will assume * main viewType from the record - * @returns {Deferred} Note: this deferred cannot fail. It is either in the - * 'success' or 'pending' state. If the onchange rpc fails, it will be - * resolved with an empty dictionary. Note that the initial change was - * already applied. + * @returns {Deferred} */ _performOnChange: function (record, fields, viewType) { var self = this; @@ -3330,9 +3340,7 @@ var BasicModel = AbstractModel.extend({ var context = this._getContext(record, options); var currentData = this._generateOnChangeData(record, {changesOnly: false}); - var def = $.Deferred(); - - self._rpc({ + return self._rpc({ model: record.model, method: 'onchange', args: [idList, currentData, fields, onchangeSpec, context], @@ -3357,12 +3365,9 @@ var BasicModel = AbstractModel.extend({ record._domains = _.extend(record._domains, result.domain); } return self._applyOnChange(result.value, record).then(function () { - def.resolve(result); + return result; }); - }).fail(function () { - def.resolve({}); }); - return def; }, /** * Once a record is created and some data has been fetched, we need to do -- cgit v1.2.3