diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index d7e65f5c4bf..e9fa7dd09d1 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -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') { 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 cde2d2479ac..17cb4d277c2 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -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); diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 4d4388b508d..fc613fc6d45 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -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', { '' + '' + '', + archs: { + 'partner_type,false,list': '', + 'partner_type,false,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', { '' + '' + '' + - '
' + - '' + - '' + '
' + '', + archs: { + 'partner_type,false,list': '', + 'partner_type,false,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(); });