[FIX] web: BasicModel: nested x2many and unknown fields

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) <ged@openerp.com>
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Aaron Bohy
2020-11-27 11:13:08 +00:00
parent 43dd87851f
commit 608d3d87b2
2 changed files with 78 additions and 5 deletions
@@ -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
@@ -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: `
<form>
<field name="name"/>
<field name="p">
<tree><field name="display_name"/></tree>
<form>
<field name="display_name"/>
<field name="p">
<tree><field name="display_name"/></tree>
<form><field name="display_name"/></form>
</field>
</form>
</field>
</form>`,
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);