From 608d3d87b2e8c2e1091aaf4b39b967bc472c37f2 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 26 Nov 2020 11:16:53 +0000 Subject: [PATCH] [FIX] web: BasicModel: nested x2many and unknown fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consider the following situation: - have a form view with an x2many field displayed as a list - the x2many field has a form view that contains an x2many field, which isn't in the list - for that x2many field, we can also open a form view to see/edit the related records. As the x2many field isn't in the list, when we open a subrecord, we modify the fieldsInfo to add the information about the x2many field we didn't know before. Doing so automatically applies raw changes that we might have stored earlier (from onchanges) for that field, because we couldn't apply them before knowing the type of that field. Before this commit, the function that applies the raw changes was called recursively on the dataPoint and all its children (thus on the x2many list datapoint), which produced a crash as this function is designed to work on dataPoints of type record. Actually, calling it on sub datapoints is useless, as it properly works recursively, and applies raw changes on x2many fields itself when necessary. This commit ensures that we don't call this function on list datapoints anymore. Closes #62334 OPW 2389991 closes odoo/odoo#62511 X-original-commit: 2b7670e6d709cd311c630c99cb9da7d3d6ae90f4 Signed-off-by: Géry Debongnie (ged) Signed-off-by: Aaron Bohy (aab) --- .../static/src/js/views/basic/basic_model.js | 8 +- .../relational_fields/field_one2many_tests.js | 75 ++++++++++++++++++- 2 files changed, 78 insertions(+), 5 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 b6c2d3baf24..4edd06d4352 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -231,7 +231,7 @@ var BasicModel = AbstractModel.extend({ * @returns {Promise} resolved when the fieldInfo have been set on the given * datapoint and all its children, and all rawChanges have been applied */ - addFieldsInfo: function (dataPointID, viewInfo) { + addFieldsInfo: async function (dataPointID, viewInfo) { var dataPoint = this.localData[dataPointID]; dataPoint.fields = _.extend({}, dataPoint.fields, viewInfo.fields); // complete the given fieldInfo with the fields of the main view, so @@ -245,7 +245,9 @@ var BasicModel = AbstractModel.extend({ // so we might have stored changes for them (e.g. coming from onchange // RPCs), that we haven't been able to process earlier (because those // fields were unknown at that time). So we now try to process them. - return this.applyRawChanges(dataPointID, viewInfo.viewType).then(() => { + if (dataPoint.type === 'record') { + await this.applyRawChanges(dataPointID, viewInfo.viewType); + } const proms = []; const fieldInfo = dataPoint.fieldsInfo[viewInfo.viewType]; // recursively apply the new field info on sub datapoints @@ -278,8 +280,6 @@ var BasicModel = AbstractModel.extend({ }); } return Promise.all(proms); - }); - }, /** * Onchange RPCs may return values for fields that are not in the current diff --git a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js index a34d518684d..f41fa749986 100644 --- a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js +++ b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js @@ -6511,7 +6511,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many: onchange that returns unknow field in list, but not in form', async function (assert) { + QUnit.test('one2many: onchange that returns unknown field in list, but not in form', async function (assert) { assert.expect(5); this.data.partner.onchanges = { @@ -6563,6 +6563,79 @@ QUnit.module('fields', {}, function () { form.destroy(); }); + QUnit.test('multi level of nested x2manys, onchange and rawChanges', async function (assert) { + assert.expect(8); + + this.data.partner.records[0].p = [1]; + this.data.partner.onchanges = { + name: function () { }, + }; + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: ` +
+ + + + + + + + + + + + `, + mockRPC(route, args) { + if (args.method === 'write') { + assert.deepEqual(args.args[1].p[0][2], { + p: [[1, 1, { display_name: 'new name' }]], + }); + } + return this._super(...arguments); + }, + res_id: 1, + }); + + assert.containsOnce(form, '.o_data_row', "the one2many should contain one row"); + + // open the o2m record in readonly first + await testUtils.dom.click(form.$('.o_data_row td:first')); + assert.containsOnce(document.body, ".modal .o_form_readonly"); + await testUtils.dom.click($('.modal .modal-footer .o_form_button_cancel')); + + // switch to edit mode and open it again + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_data_row td:first')); + + assert.containsOnce(document.body, ".modal .o_form_editable"); + assert.containsOnce(document.body, '.modal .o_data_row', "the one2many should contain one row"); + + // open the o2m again, in the dialog + await testUtils.dom.click($('.modal .o_data_row td:first')); + + assert.containsN(document.body, ".modal .o_form_editable", 2); + + // edit the name and click save modal that is on top + await testUtils.fields.editInput($('.modal:nth(1) .o_field_widget[name=display_name]'), 'new name'); + await testUtils.dom.click($('.modal:nth(1) .modal-footer .btn-primary')); + + assert.containsOnce(document.body, ".modal .o_form_editable"); + + // click save on the other modal + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); + + assert.containsNone(document.body, ".modal"); + + // save the main record + await testUtils.form.clickSave(form); + + form.destroy(); + }); + QUnit.test('onchange and required fields with override in arch', async function (assert) { assert.expect(4);