From e7dfa4af775a93b291ccd570c05970f2ac2b9187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 6 Apr 2017 09:59:05 +0200 Subject: [PATCH] [FIX] web: add support for editable=top/bottom in o2m Before this commit, we lost the functionality of editing records top or bottom. However, it as almost ready, but we needed to change the way commands are generated, to respect the actual order in the UI. Also, the mock server needed to be updated, to make sure he also save the new ids in the correct order. --- .../static/src/js/fields/relational_fields.js | 4 +- .../static/src/js/views/basic/basic_model.js | 37 ++++---- .../test/fields/relational_fields_tests.js | 92 +++++++++++++++++++ addons/web/static/test/helpers/mock_server.js | 12 +-- 4 files changed, 121 insertions(+), 24 deletions(-) diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index c7ff956fd7b..496aaee06f0 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -777,8 +777,8 @@ var FieldOne2Many = FieldX2Many.extend({ return this._super.apply(this, arguments).then(function () { if (event && event.target === self && self.view.arch.tag === 'tree') { if (event.data.changes[self.name].operation === 'CREATE') { - // todo: get last record if editable = bottom - var newID = self.value.data[0].id; + var index = self.editable === 'top' ? 0 : self.value.data.length - 1; + var newID = self.value.data[index].id; self.renderer.editRecord(newID); } } 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 1f8d5ce94c6..c95325dbb87 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1058,7 +1058,7 @@ var BasicModel = AbstractModel.extend({ return this.notifyChanges(id, command.data); })); } else { - defs.push(this.addDefaultRecord(list.id)); + defs.push(this.addDefaultRecord(list.id, {position: command.position})); } break; case 'UPDATE': @@ -1715,20 +1715,29 @@ var BasicModel = AbstractModel.extend({ var removedIds = _.difference(list.res_ids, relIds); var addedIds = _.difference(relIds, list.res_ids); var keptIds = _.intersection(list.res_ids, relIds); + // the didChange variable keeps track of the fact that at // least one id was updated var didChange = false; - var command, i, relRecord; - for (i = 0; i < keptIds.length; i++) { - relRecord = _.findWhere(relData, {res_id: keptIds[i]}); - if (!_.isEmpty(relRecord._changes)) { - var changes = this._generateChanges(relRecord); - command = x2ManyCommands.update(relRecord.res_id, changes); - didChange = true; - } else { - command = x2ManyCommands.link_to(keptIds[i]); + var command, relRecord; + for (var i = 0; i < relIds.length; i++) { + if (_.contains(keptIds, relIds[i])) { + // this is an id that already existed + relRecord = _.findWhere(relData, {res_id: relIds[i]}); + if (!_.isEmpty(relRecord._changes)) { + var changes = this._generateChanges(relRecord); + command = x2ManyCommands.update(relRecord.res_id, changes); + didChange = true; + } else { + command = x2ManyCommands.link_to(relIds[i]); + } + commands[fieldName].push(command); + } else if (_.contains(addedIds, relIds[i])) { + // this is a new id + relRecord = _.findWhere(relData, {res_id: relIds[i]}); + relRecord = this.get(relRecord.id, {raw: true}); + commands[fieldName].push(x2ManyCommands.create(_.omit(relRecord.data, 'id'))); } - commands[fieldName].push(command); } if (changesOnly && !didChange && addedIds.length === 0 && removedIds.length === 0) { // in this situation, we have no changed ids, no added @@ -1736,11 +1745,7 @@ var BasicModel = AbstractModel.extend({ // last changes commands[fieldName] = []; } - for (i = 0; i < addedIds.length; i++) { - relRecord = _.findWhere(relData, {res_id: addedIds[i]}); - relRecord = this.get(relRecord.id, {raw: true}); - commands[fieldName].push(x2ManyCommands.create(_.omit(relRecord.data, 'id'))); - } + // add delete commands for (i = 0; i < removedIds.length; i++) { commands[fieldName].push(x2ManyCommands.delete(removedIds[i])); } diff --git a/addons/web/static/test/fields/relational_fields_tests.js b/addons/web/static/test/fields/relational_fields_tests.js index 7852a2b1e63..eba4d57d012 100644 --- a/addons/web/static/test/fields/relational_fields_tests.js +++ b/addons/web/static/test/fields/relational_fields_tests.js @@ -2628,6 +2628,98 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('one2many list editable = top', function (assert) { + assert.expect(6); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'write') { + var commands = args.args[1].turtles; + assert.strictEqual(commands[0][0], 0, + "first command is a create"); + assert.strictEqual(commands[1][0], 4, + "second command is a link to"); + } + return this._super.apply(this, arguments); + }, + }); + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "should start with one data row"); + + form.$('.o_form_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('.o_data_row').length, 2, + "should have 2 data rows"); + assert.ok(form.$('tr.o_data_row:first:contains(My little Foo Value)').length, 1, + "first row should be the new value"); + assert.ok(form.$('tr.o_data_row:first').hasClass('o_selected_row'), + "first row should be selected"); + + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + + QUnit.test('one2many list editable = bottom', function (assert) { + assert.expect(6); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'write') { + var commands = args.args[1].turtles; + assert.strictEqual(commands[0][0], 4, + "first command is a link to"); + assert.strictEqual(commands[1][0], 0, + "second command is a create"); + } + return this._super.apply(this, arguments); + }, + }); + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "should start with one data row"); + + form.$('.o_form_field_x2many_list_row_add a').click(); + + assert.strictEqual(form.$('.o_data_row').length, 2, + "should have 2 data rows"); + assert.ok(form.$('tr.o_data_row:eq(1):contains(My little Foo Value)').length, 1, + "second row should be the new value"); + assert.ok(form.$('tr.o_data_row:eq(1)').hasClass('o_selected_row'), + "second row should be selected"); + + form.$buttons.find('.o_form_button_save').click(); + form.destroy(); + }); + QUnit.module('FieldMany2Many'); QUnit.test('many2many kanban: edition', function (assert) { diff --git a/addons/web/static/test/helpers/mock_server.js b/addons/web/static/test/helpers/mock_server.js index 3cff90bfb45..de5e02934f0 100644 --- a/addons/web/static/test/helpers/mock_server.js +++ b/addons/web/static/test/helpers/mock_server.js @@ -896,26 +896,26 @@ var MockServer = Class.extend({ continue; } if (_.contains(['one2many', 'many2many'], field.type)) { + var ids = []; // convert commands _.each(value, function (command) { if (command[0] === 0) { // CREATE var id = self._mockCreate(field.relation, command[2]); - record[field_changed].push(id); + ids.push(id); } else if (command[0] === 1) { // UPDATE self._mockWrite(field.relation, [[command[1]], command[2]]); } else if (command[0] === 2) { // DELETE - record[field_changed] = _.without(record[field_changed], command[1]); } else if (command[0] === 4) { // LINK_TO - // nothing to do, this command is called by a one2many, - // and should do nothing in a non concurrent environment. + ids.push(command[1]); } else if (command[0] === 5) { // DELETE ALL - record[field_changed] = []; + ids = []; } else if (command[0] === 6) { // REPLACE WITH - record[field_changed] = command[2]; + ids = command[2]; } else { console.error('Command ' + JSON.stringify(command) + ' not supported by the MockServer'); } }); + record[field_changed] = ids; } else if (field.type === 'many2one') { if (value) { var relatedRecord = _.findWhere(this.data[field.relation].records, {