[FIX] web: can update one2many with custom field widget

One might design a custom field widget to display/interact with a
one2many field. Before this commit, if this field widget triggered
a field_changed event to update a related record, it crashed,
because the code assumed that there was a view associated with the
field.

Closes #68276
opw~2468238

closes odoo/odoo#68309

X-original-commit: 3826a2645b94e0f62c719814c4dbe4cc6502e1f2
Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
Co-authored-by: Aaron Bohy <aab@odoo.com>
This commit is contained in:
Krzysztof Magusiak
2021-03-24 14:38:11 +00:00
committed by Aaron Bohy
co-authored by Aaron Bohy
parent d613f97320
commit b08afa3475
2 changed files with 74 additions and 1 deletions
@@ -2053,7 +2053,9 @@ var BasicModel = AbstractModel.extend({
case 'UPDATE':
list._changes.push({operation: 'UPDATE', id: command.id});
if (command.data) {
defs.push(this._applyChange(command.id, command.data, { viewType: view.type }));
defs.push(this._applyChange(command.id, command.data, {
viewType: view && view.type,
}));
}
break;
case 'FORGET':
@@ -9764,6 +9764,77 @@ QUnit.module('fields', {}, function () {
form.destroy();
});
QUnit.test('update a one2many from a custom field widget', async function (assert) {
// In this test, we define a custom field widget to render/update a one2many
// field. For the update part, we ensure that updating primitive fields of a sub
// record works. There is no guarantee that updating a relational field on the sub
// record would work. Deleting a sub record works as well. However, creating sub
// records isn't supported. There are obviously a lot of limitations, but the code
// hasn't been designed to support all this. This test simply encodes what can be
// done, and this comment explains what can't (and won't be implemented in stable
// versions).
assert.expect(3);
this.data.partner.records[0].p = [1, 2];
const MyRelationalField = AbstractField.extend({
events: {
'click .update': '_onUpdate',
'click .delete': '_onDelete',
},
async _render() {
const records = await this._rpc({
method: 'read',
model: 'partner',
args: [this.value.res_ids],
});
this.$el.text(records.map(r => `${r.display_name}/${r.int_field}`).join(', '));
this.$el.append($('<button class="update fa fa-edit">'));
this.$el.append($('<button class="delete fa fa-trash">'));
},
_onUpdate() {
this._setValue({
operation: 'UPDATE',
id: this.value.data[0].id,
data: {
display_name: 'new name',
int_field: 44,
},
});
},
_onDelete() {
this._setValue({
operation: 'DELETE',
ids: [this.value.data[0].id],
});
},
});
fieldRegistry.add('my_relational_field', MyRelationalField);
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form>
<field name="p" widget="my_relational_field"/>
</form>`,
res_id: 1,
});
assert.strictEqual(form.$('.o_field_widget[name=p]').text(), 'first record/10, second record/9');
await testUtils.dom.click(form.$('button.update'));
assert.strictEqual(form.$('.o_field_widget[name=p]').text(), 'new name/44, second record/9');
await testUtils.dom.click(form.$('button.delete'));
assert.strictEqual(form.$('.o_field_widget[name=p]').text(), 'second record/9');
form.destroy();
delete fieldRegistry.map.my_relational_field;
});
});
});
});