[FIX] web: FieldMany2Many with editable lists: save changes

This rev. fixes 2 issues about many2many fields displayed with
an editable list view, occuring when the user edited some values
in the list.

First, the many2many field was re-rendered each time one of its
sub-widgets triggered a 'field_changed' event, and thus the row
the user was editing was no more in edition and the focused field
was no more focused. This was quite annoying.

Second, when the user saved changes made in the editable list view,
only a command 6 (REPLACE_ALL) was sent, so updates of subrecords
were never saved.
This commit is contained in:
Aaron Bohy
2017-05-05 13:31:48 +02:00
parent e8f9bb683d
commit 18c3dbc2e9
3 changed files with 138 additions and 32 deletions
@@ -588,6 +588,25 @@ var FieldX2Many = AbstractField.extend({
isSet: function () {
return true;
},
/**
* @override
* @param {Object} record
* @param {OdooEvent} [ev] an event that triggered the reset action
* @returns {Deferred}
*/
reset: function (record, ev) {
if (ev && ev.target === this && ev.data.changes && this.view.arch.tag === 'tree' && this.editable) {
var command = ev.data.changes[this.name];
if (command.operation === 'UPDATE') {
var state = record.data[this.name];
var fieldNames = state.getFieldNames();
this.renderer.confirmChange(state, command.id, fieldNames);
return $.when();
}
}
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Private
@@ -833,15 +852,6 @@ var FieldOne2Many = FieldX2Many.extend({
*/
reset: function (record, ev) {
var self = this;
if (ev && ev.target === this && ev.data.changes && self.view.arch.tag === 'tree' && this.editable) {
var command = ev.data.changes[this.name];
if (command.operation === 'UPDATE') {
var state = record.data[this.name];
var fieldNames = state.getFieldNames();
this.renderer.confirmChange(state, command.id, fieldNames);
return $.when();
}
}
return this._super.apply(this, arguments).then(function () {
if (ev && ev.target === self && ev.data.changes && self.view.arch.tag === 'tree') {
if (ev.data.changes[self.name].operation === 'CREATE') {
@@ -1847,8 +1847,17 @@ var BasicModel = AbstractModel.extend({
// of a 'delete' and a 'link' commands with the exact diff
// because 1) performance-wise it doesn't change anything
// and 2) to guard against concurrent updates (policy: force
// an complete override of the actual value of the m2m)
// a complete override of the actual value of the m2m)
commands[fieldName].push(x2ManyCommands.replace_with(relIds));
// generate update commands for records that have been
// updated (it may happen with editable lists)
_.each(relData, function (relRecord) {
if (!_.isEmpty(relRecord._changes)) {
var changes = self._generateChanges(relRecord);
var command = x2ManyCommands.update(relRecord.res_id, changes);
commands[fieldName].push(command);
}
});
} else if (type === 'one2many') {
var removedIds = _.difference(list.res_ids, relIds);
var addedIds = _.difference(relIds, list.res_ids);
@@ -3293,9 +3293,10 @@ QUnit.module('relational_fields', {
});
QUnit.test('many2many list (non editable): edition', function (assert) {
assert.expect(9);
assert.expect(27);
this.data.partner.records[0].timmy = [12, 14];
this.data.partner_type.records.push({id: 15, display_name: "bronze", color: 6});
this.data.partner_type.fields.float_field = {string: 'Float', type: 'float'};
var form = createView({
View: FormView,
@@ -3311,7 +3312,20 @@ QUnit.module('relational_fields', {
'</form>' +
'</field>' +
'</form>',
archs: {
'partner_type,false,list': '<tree><field name="display_name"/></tree>',
'partner_type,false,search': '<search><field name="display_name"/></search>',
},
res_id: 1,
mockRPC: function (route, args) {
assert.step(_.last(route.split('/')));
if (args.method === 'write' && args.model === 'partner') {
assert.deepEqual(args.args[1].timmy, [
[6, false, [12, 15]],
]);
}
return this._super.apply(this, arguments);
},
});
assert.ok(!form.$('.o_list_record_delete').length,
@@ -3338,22 +3352,53 @@ QUnit.module('relational_fields', {
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
'value of subrecord should have been updated');
// create new subrecords
// TODO when 'Add an item' will be implemented
// add new subrecords
form.$('.o_field_x2many_list_row_add a').click();
assert.strictEqual($('.modal .o_list_view').length, 1,
"a modal should be open");
assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1,
"the list should contain one row");
$('.modal .o_list_view .o_data_row').click(); // select a record
assert.strictEqual($('.modal .o_list_view').length, 0,
"the modal should be closed");
assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 3,
'should contain 3 subrecords');
// delete subrecords
form.$('.o_list_record_delete:first()').click();
assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 1,
'should contain 1 subrecord');
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'silver',
'the remaining subrecord should be "aaa"');
form.$('.o_list_record_delete:nth(1)').click();
assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2,
'should contain 2 subrecords');
assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), 'new name',
'the updated row still has the correct values');
// save
form.$buttons.find('.o_form_button_save').click();
assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2,
'should contain 2 subrecords');
assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(),
'new name', 'the updated row still has the correct values');
assert.verifySteps([
'read', // main record
'read', // relational field
'read', // relational record in dialog
'write', // save relational record from dialog
'read', // relational field (updated)
'search_read', // list view in dialog
'read', // relational field (updated)
'write', // save main record
'read', // main record
'read', // relational field
]);
form.destroy();
});
QUnit.test('many2many list (editable): edition', function (assert) {
assert.expect(11);
assert.expect(30);
this.data.partner.records[0].timmy = [12, 14];
this.data.partner_type.records.push({id: 15, display_name: "bronze", color: 6});
this.data.partner_type.fields.float_field = {string: 'Float', type: 'float'};
var form = createView({
View: FormView,
@@ -3364,11 +3409,22 @@ QUnit.module('relational_fields', {
'<tree editable="top">' +
'<field name="display_name"/><field name="float_field"/>' +
'</tree>' +
'<form string="Partners">' +
'<field name="display_name"/>' +
'</form>' +
'</field>' +
'</form>',
archs: {
'partner_type,false,list': '<tree><field name="display_name"/></tree>',
'partner_type,false,search': '<search><field name="display_name"/></search>',
},
mockRPC: function (route, args) {
assert.step(_.last(route.split('/')));
if (args.method === 'write') {
assert.deepEqual(args.args[1].timmy, [
[6, false, [12, 15]],
[1, 12, {display_name: 'new name'}],
]);
}
return this._super.apply(this, arguments);
},
res_id: 1,
});
@@ -3395,22 +3451,53 @@ QUnit.module('relational_fields', {
assert.ok(form.$('.o_list_view tbody tr:first()').hasClass('o_selected_row'),
'first row should be in edition');
form.$('.o_list_view input:first()').val('new name').trigger('input');
// FIXME: this doesn"t work for now as the x2many fields are reset on field changed
// form.$('.o_list_view tbody td:nth(1)').click(); // click on second record to validate the first one
// assert.ok(!form.$('.o_list_view tbody tr:first()').hasClass('o_selected_row'),
// 'first row should not be in edition anymore');
assert.ok(form.$('.o_list_view .o_data_row:first').hasClass('o_selected_row'),
'first row should still be in edition');
assert.strictEqual(form.$('.o_list_view input[name=display_name]').get(0),
document.activeElement, 'edited field should still have the focus');
form.$el.click(); // click outside the list to validate the row
assert.ok(!form.$('.o_list_view tbody tr:first').hasClass('o_selected_row'),
'first row should not be in edition anymore');
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name',
'value of subrecord should have been updated');
assert.verifySteps(['read', 'read']);
// add new subrecords
// TODO when 'Add an item' will be implemented
form.$('.o_field_x2many_list_row_add a').click();
assert.strictEqual($('.modal .o_list_view').length, 1,
"a modal should be open");
assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1,
"the list should contain one row");
$('.modal .o_list_view .o_data_row').click(); // select a record
assert.strictEqual($('.modal .o_list_view').length, 0,
"the modal should be closed");
assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 3,
'should contain 3 subrecords');
// delete subrecords
form.$('.o_list_record_delete:first()').click();
assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 1,
'should contain 1 subrecord');
assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'silver',
'the remaining subrecord should be "aaa"');
form.$('.o_list_record_delete:nth(1)').click();
assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2,
'should contain 2 subrecord');
assert.strictEqual(form.$('.o_list_view tbody .o_data_row td:first').text(),
'new name', 'the updated row still has the correct values');
// save
form.$buttons.find('.o_form_button_save').click();
assert.strictEqual(form.$('.o_list_view td.o_list_number').length, 2,
'should contain 2 subrecords');
assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(),
'new name', 'the updated row still has the correct values');
assert.verifySteps([
'read', // main record
'read', // relational field
'search_read', // list view in dialog
'read', // relational field (updated)
'write', // save main record
'read', // main record
'read', // relational field
]);
form.destroy();
});