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 87c9f3c57bc..62898a338bd 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -825,7 +825,6 @@ var BasicModel = AbstractModel.extend({ _applyChange: function (recordID, changes) { var self = this; var record = this.localData[recordID]; - var onChangeFields = []; // the fields that have changed and that have an on_change var field; var defs = []; record._changes = record._changes || {}; @@ -840,12 +839,19 @@ var BasicModel = AbstractModel.extend({ } else { record._changes[fieldName] = changes[fieldName]; } - if (field.onChange) { - onChangeFields.push(fieldName); - } } return $.when.apply($, defs).then(function () { + var onChangeFields = []; // the fields that have changed and that have an on_change + for (var fieldName in changes) { + field = record.fields[fieldName]; + if (field.onChange) { + var isX2Many = field.type === 'one2many' || field.type === 'many2many'; + if (!isX2Many || (self._isX2ManyValid(record._changes[fieldName] || record.data[fieldName]))) { + onChangeFields.push(fieldName); + } + } + } var onchangeDef; if (onChangeFields.length) { onchangeDef = self._performOnChange(record, onChangeFields).then(function (result) { @@ -1948,6 +1954,29 @@ var BasicModel = AbstractModel.extend({ var fieldsInfo = element.fieldsInfo; return Object.keys(fieldsInfo && fieldsInfo[element.viewType] || {}); }, + /** + * return true if a list element is 'valid'. Such an element is valid if it + * has no sub record with an unset required field. + * + * This method is meant to be used to check if a x2many change will trigger + * an onchange. + * + * @param {string} id id for a local resource of type 'list'. This is + * assumed to be a list element for a one2many + * @returns {boolean} + */ + _isX2ManyValid: function (id) { + var isValid = true; + var element = this.get(id, {raw: true}); + _.each(element.data, function (rec) { + for (var fieldName in rec.fields) { + if (rec.fields[fieldName].required && !rec.data[fieldName]) { + isValid = false; + } + } + }); + return isValid; + }, /** * Helper method for the load entry point. * diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 23274f395a8..6e0fc199b6c 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -91,7 +91,7 @@ QUnit.module('relational_fields', { turtle: { fields: { display_name: { string: "Displayed name", type: "char" }, - turtle_foo: {string: "Foo", type: "char", default: "My little Foo Value"}, + turtle_foo: {string: "Foo", type: "char"}, turtle_bar: {string: "Bar", type: "boolean", default: true}, turtle_int: {string: "int", type: "integer", sortable: true}, turtle_qux: {string: "Qux", type: "float", digits: [16,1], required: true, default: 1.5}, @@ -2677,6 +2677,7 @@ QUnit.module('relational_fields', { QUnit.test('one2many list editable = top', function (assert) { assert.expect(6); + this.data.turtle.fields.turtle_foo.default = "default foo turtle"; var form = createView({ View: FormView, model: 'partner', @@ -2711,7 +2712,7 @@ QUnit.module('relational_fields', { assert.strictEqual(form.$('.o_data_row').length, 2, "should have 2 data rows"); - assert.strictEqual(form.$('tr.o_data_row:first input').val(), 'My little Foo Value', + assert.strictEqual(form.$('tr.o_data_row:first input').val(), 'default foo turtle', "first row should be the new value"); assert.ok(form.$('tr.o_data_row:first').hasClass('o_selected_row'), "first row should be selected"); @@ -2722,6 +2723,7 @@ QUnit.module('relational_fields', { QUnit.test('one2many list editable = bottom', function (assert) { assert.expect(6); + this.data.turtle.fields.turtle_foo.default = "default foo turtle"; var form = createView({ View: FormView, @@ -2757,7 +2759,7 @@ QUnit.module('relational_fields', { assert.strictEqual(form.$('.o_data_row').length, 2, "should have 2 data rows"); - assert.strictEqual(form.$('tr.o_data_row:eq(1) input').val(), 'My little Foo Value', + assert.strictEqual(form.$('tr.o_data_row:eq(1) input').val(), 'default foo turtle', "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"); @@ -2800,6 +2802,54 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('one2many list editable, no onchange when required field is not set', function (assert) { + assert.expect(7); + + this.data.turtle.fields.turtle_foo.required = true; + this.data.partner.onchanges = { + turtles: function (obj) { + obj.int_field = obj.turtles.length; + }, + }; + this.data.partner.records[0].int_field = 0; + this.data.partner.records[0].turtles = []; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
', + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + res_id: 1, + }); + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_form_field[name="int_field"]').val(), "0", + "int_field should start with value 0"); + form.$('.o_form_field_x2many_list_row_add a').click(); + assert.strictEqual(form.$('.o_form_field[name="int_field"]').val(), "0", + "int_field should still be 0 (no onchange should have been done yet"); + + assert.verifySteps(['read', 'default_get'], "no onchange should have been applied"); + + form.$('.o_field_widget[name="turtle_foo"]').val("some text").trigger('input'); + assert.strictEqual(form.$('.o_form_field[name="int_field"]').val(), "1", + "int_field should now be 1 (the onchange should have been done"); + + form.destroy(); + }); + QUnit.module('FieldMany2Many'); QUnit.test('many2many kanban: edition', function (assert) {