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 8239692711e..992af62387e 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -302,6 +302,9 @@ var BasicModel = AbstractModel.extend({ for (var fieldName in element.data) { field = element.fields[fieldName]; + if (!field) { + continue; + } // get relational datapoint if (field.type === 'many2one') { @@ -842,18 +845,32 @@ var BasicModel = AbstractModel.extend({ }); }, /** - * Set and return the fieldattrs on a record element. + * Set fieldNames. + * Set fields on a record element (current fields as defaults) + * Set fieldattrs. * It is mostly useful for the cases where a record element is shared * between various views, such as a one2many with a tree and a form view. * * @param {string} recordID a valid element ID - * @param {Object} attrs the new field attrs - * @return {Object} the previous field attrs + * @param {Object} props + * @param {Object} props.fieldNames + * @param {Object} props.fields + * @param {Object} props.fieldAttrs + * @return {Object} + * @return {Object} result.fieldNames the previous field names + * @return {Object} result.fields the previous description fields + * @return {Object} result.fieldattrs the previous field attrs */ - setFieldAttrs: function (recordID, attrs) { + setFieldProps: function (recordID, props) { var record = this.localData[recordID]; - var result = record.fieldAttrs; - record.fieldAttrs = attrs; + var result = { + fieldNames: record.fieldNames, + fields: record.fields, + fieldAttrs: record.fieldAttrs, + }; + record.fieldNames = props.fieldNames; + record.fields = _.defaults(props.fields, record.fields); + record.fieldAttrs = props.fieldAttrs; return result; }, /** @@ -2100,6 +2117,9 @@ var BasicModel = AbstractModel.extend({ if (element.type === 'record') { for (var fieldName in element.data) { var field = element.fields[fieldName]; + if (!field) { + continue; + } if (_.contains(['one2many', 'many2one', 'many2many'], field.type)) { var relationalElement = this.localData[element.data[fieldName]]; diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index 22ec34f714b..e581b222417 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -63,24 +63,28 @@ return AbstractRenderer.extend({ * @returns {Object} */ _getFieldValues: function (record) { - return _.mapObject(record.data, function (value, name) { + var result = {}; + _.each(record.fieldNames, function (name) { var field = record.fields[name]; + var value = record.data[name]; + result[name] = value; if (field.type === 'many2one') { - return value.data ? value.data.id || false : false; + result[name] = value.data ? value.data.id || false : false; } if (field.type === 'one2many' || field.type === 'many2many') { if (value) { - return _.pluck(value.data, 'res_id'); + result[name] = _.pluck(value.data, 'res_id'); } else { - return []; + result[name] = []; } } if (field.type === 'boolean') { // we want an explicit false value, not null if it is unset - return value || false; + result[name] = value || false; } - return value; }); + result.id = record.data.id; + return result; }, /** * Render the view diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index ab9588dc740..5ca2cd5c14e 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -141,7 +141,7 @@ var FormViewDialog = ViewDialog.extend({ */ destroy: function () { if (this.recordID && this.fieldAttrs) { - this.model.setFieldAttrs(this.recordID, this.fieldAttrs); + this.setFieldProps(this.recordID, this.previousFieldProps); } this._super.apply(this, arguments); }, @@ -168,8 +168,11 @@ var FormViewDialog = ViewDialog.extend({ fields_view_def.then(function (fields_view) { if (self.recordID) { - self.fieldAttrs = - self.model.setFieldAttrs(self.recordID, fields_view.fieldAttrs); + self.previousFieldProps = self.model.setFieldProps(self.recordID, { + fieldNames: Object.keys(fields_view.fields), + fields: fields_view.fields, + fieldAttrs: fields_view.fieldAttrs + }); } var formview = new FormView(fields_view.arch, fields_view.fields, { modelName: self.res_model, diff --git a/addons/web/static/test/fields/relational_fields_tests.js b/addons/web/static/test/fields/relational_fields_tests.js index 531dfc01751..dadb388d65b 100644 --- a/addons/web/static/test/fields/relational_fields_tests.js +++ b/addons/web/static/test/fields/relational_fields_tests.js @@ -20,6 +20,7 @@ QUnit.module('relational_fields', { int_field: {string: "int_field", type: "integer", sortable: true}, qux: {string: "Qux", type: "float", digits: [16,1] }, p: {string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu'}, + turtles: {string: "one2many turtle field", type: "one2many", relation: 'turtle'}, trululu: {string: "Trululu", type: "many2one", relation: 'partner'}, timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'}, product_id: {string: "Product", type: "many2one", relation: 'product'}, @@ -39,6 +40,7 @@ QUnit.module('relational_fields', { int_field: 10, qux: 0.44, p: [], + turtles: [2], timmy: [], trululu: 4, }, { @@ -83,6 +85,36 @@ QUnit.module('relational_fields', { {id: 14, display_name: "silver", color: 5}, ] }, + turtle: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + turtle_foo: {string: "Foo", type: "char", default: "My little Foo Value"}, + turtle_bar: {string: "Bar", type: "boolean", default: true}, + turtle_int: {string: "int", type: "integer", sortable: true, required: true}, + turtle_qux: {string: "Qux", type: "float", digits: [16,1], required: true}, + turtle_trululu: {string: "Trululu", type: "many2one", relation: 'partner', required: true, default: 1}, + product_id: {string: "Product", type: "many2one", relation: 'product', required: true}, + }, + records: [{ + id: 1, + display_name: "leonardo", + turtle_bar: true, + turtle_foo: "yop", + }, { + id: 2, + display_name: "donatello", + turtle_bar: true, + turtle_foo: "blip", + turtle_int: 9, + }, { + id: 3, + display_name: "raphael", + turtle_bar: false, + turtle_foo: "kawa", + turtle_int: 21, + turtle_qux: 9.8, + }], + } }; } }, function () { @@ -1573,42 +1605,44 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('one2many with many2many widget', function (assert) { - assert.expect(9); - - this.data.partner.records[0].p = [2]; + QUnit.test('one2many with many2many widget: create', function (assert) { + assert.expect(10); var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'
' + - '' + + '' + '' + - '' + - '' + + '' + + '' + + '' + '' + '' + '' + '' + - '' + - '' + - '' + + '' + + '' + + '' + '' + '' + '' + '' + '', archs: { - 'partner,false,list': '', - 'partner,false,search': '', + 'turtle,false,list': '', + 'turtle,false,search': '', }, session: {}, res_id: 1, mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/turtle/create') { + assert.ok(args.args, "should write on the turtle record"); + } if (route === '/web/dataset/call_kw/partner/write') { assert.strictEqual(args.args[0][0], 1, "should write on the partner record 1"); - assert.strictEqual(args.args[1].p[0][0], 6, "should send only a 'replace with' command"); + assert.strictEqual(args.args[1].turtles[0][0], 6, "should send only a 'replace with' command"); } return this._super.apply(this, arguments); }, @@ -1630,8 +1664,8 @@ QUnit.module('relational_fields', { "sould have 1 record in the select view"); $('.modal-footer button:eq(1)').click(); - $('.modal input.o_form_field[name="foo"]').val('tototo').trigger('input'); - $('.modal input.o_form_field[name="int_field"]').val(50).trigger('input'); + $('.modal input.o_form_field[name="turtle_foo"]').val('tototo').trigger('input'); + $('.modal input.o_form_field[name="turtle_int"]').val(50).trigger('input'); var $many2one = $('.modal [name="product_id"] input').click(); var $dropdown = $many2one.autocomplete('widget'); $dropdown.find('li:first a').mouseenter(); @@ -1643,7 +1677,7 @@ QUnit.module('relational_fields', { assert.strictEqual(form.$('.o_data_row').length, 3, "sould have 3 records in one2many list"); - assert.strictEqual(form.$('.o_data_row').text(), "blip9xphoneyop10tototo50xphone", + assert.strictEqual(form.$('.o_data_row').text(), "blip0.09yop0.0tototo0.050xphone", "sould display the record values in one2many list"); $('.o_form_button_save').click(); @@ -1651,6 +1685,83 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('one2many with many2many widget: edition', function (assert) { + assert.expect(6); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch:'
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '', + archs: { + 'turtle,false,list': '', + 'turtle,false,search': '', + }, + session: {}, + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/turtle/write') { + assert.strictEqual(args.args[0].length, 1, "should write on the turtle record"); + assert.deepEqual(args.args[1], {"product_id":37}, "should write only the product_id on the turtle record"); + } + if (route === '/web/dataset/call_kw/partner/write') { + assert.strictEqual(args.args[0][0], 1, "should write on the partner record 1"); + assert.strictEqual(args.args[1].turtles[0][0], 6, "should send only a 'replace with' command"); + } + return this._super.apply(this, arguments); + }, + }); + + form.$('.o_data_row:first').click(); + $('.modal .o_form_button_cancel').click(); + form.$buttons.find('.o_form_button_edit').click(); + + // edit the first one2many record + form.$('.o_data_row:first').click(); + var $many2one = $('.modal [name="product_id"] input').click(); + var $dropdown = $many2one.autocomplete('widget'); + $dropdown.find('li:first a').mouseenter(); + $dropdown.find('li:first a').click(); + $('.modal-footer button:first').click(); + + $('.o_form_button_save').click(); // don't save anything because the one2many does not change + + // add a one2many record + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_form_field_x2many_list_row_add a').click(); + $('.modal .o_data_row:first .o_list_record_selector input').click(); + $('.modal .o_select_button').click(); + + // edit the second one2many record + form.$('.o_data_row:eq(1)').click(); + $many2one = $('.modal [name="product_id"] input').click(); + $dropdown = $many2one.autocomplete('widget'); + $dropdown.find('li:first a').mouseenter(); + $dropdown.find('li:first a').click(); + $('.modal-footer button:first').click(); + + $('.o_form_button_save').click(); + + form.destroy(); + }); + QUnit.test('new record, the context is properly evaluated and sent', function (assert) { assert.expect(2);