diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 7f4fa24e5c2..2d746a81aa1 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -1179,7 +1179,7 @@ var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({ }, }); -var KanbanFieldMany2ManyTags = FieldMany2Many.extend({ +var KanbanFieldMany2ManyTags = FieldMany2ManyTags.extend({ _render: function () { var self = this; this.$el.addClass('o_form_field_many2manytags o_kanban_tags'); 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 b3777a05efb..4b39025b35d 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1619,40 +1619,50 @@ var BasicModel = AbstractModel.extend({ if (!ids.length || fieldInfo.__no_fetch) { return $.when(); } - + var def; + var fieldNames = _.keys(fieldInfo.relatedFields); // step 2: fetch data from server - return this._rpc({ + // if we want specific fields + // if not we return an array of objects with the id + // to avoid fetching all the relation fields and an useless rpc + if (fieldNames.length) { + def = this._rpc({ model: field.relation, method: 'read', - args: [ids, _.keys(fieldInfo.relatedFields)], + args: [ids, fieldNames], context: {}, // FIXME - }) - .then(function (results) { - // step 3: assign values to correct datapoints - var dataPoints = _.map(results, function (result) { - return self._makeDataPoint({ - modelName: field.relation, - data: result, - fields: fields, - fieldsInfo: fieldsInfo, - viewType: viewType, - }); - }); - - _.each(list.data, function (dataPoint) { - var record = self.localData[dataPoint]; - var m2mList = self.localData[record.data[fieldName]]; - - m2mList.data = []; - _.each(m2mList.res_ids, function (res_id) { - var dataPoint = _.find(dataPoints, function (d) { - return d.res_id === res_id; - }); - m2mList.data.push(dataPoint.id); - m2mList.count++; - }); + }); + } else { + def = $.when(_.map(ids, function (id) { + return {id:id}; + })); + } + return def.then(function (results) { + // step 3: assign values to correct datapoints + var dataPoints = _.map(results, function (result) { + return self._makeDataPoint({ + modelName: field.relation, + data: result, + fields: fields, + fieldsInfo: fieldsInfo, + viewType: viewType, }); }); + + _.each(list.data, function (dataPoint) { + var record = self.localData[dataPoint]; + var m2mList = self.localData[record.data[fieldName]]; + + m2mList.data = []; + _.each(m2mList.res_ids, function (res_id) { + var dataPoint = _.find(dataPoints, function (d) { + return d.res_id === res_id; + }); + m2mList.data.push(dataPoint.id); + m2mList.count++; + }); + }); + }); }, /** * batch request for x2ms for datapoint of type list @@ -2444,12 +2454,18 @@ var BasicModel = AbstractModel.extend({ } } if (missingIds.length) { - def = this._rpc({ - model: list.model, - method: 'read', - args: [missingIds, fieldNames], - context: {} // FIXME - }); + if (fieldNames.length) { + def = this._rpc({ + model: list.model, + method: 'read', + args: [missingIds, fieldNames], + context: {} // FIXME + }); + } else { + def = $.when(_.map(missingIds, function (id) { + return {id:id}; + })); + } } else { def = $.when(); } diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index 2604dcf0aa1..17dc4b97125 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -66,6 +66,29 @@ var BasicView = AbstractView.extend({ var viewType = this.viewType; var viewFields = Object.keys(record.fieldsInfo[viewType]); var fieldNames = _.difference(viewFields, Object.keys(record.data)); + // Suppose that in a form view, there is an x2many list view with + // an x2many field F of the related record, and that F is also + // displayed in the x2many form view (e.g. as a list or a kanban). + // In this case, F is represented in record.data + // (as it is known by the x2many list view), but its related fields + // (those displayed in the list or kanban) still need to be fetched. + // So when this happens, F is added to the list of fieldNames to fetch. + _.each(viewFields, function (name) { + if (!_.contains(fieldNames, name)) { + var fieldType = record.fields[name].type; + if ((fieldType === 'one2many' || fieldType === 'many2many')) { + if (!('fieldsInfo' in record.data[name])) { + fieldNames.push(name); + } else { + var recordViewTypes = Object.keys(record.data[name].fieldsInfo); + var fieldViewTypes = Object.keys(record.fieldsInfo[viewType][name].views); + if (_.difference(fieldViewTypes, recordViewTypes).length) { + fieldNames.push(name); + } + } + } + } + }); if (fieldNames.length && !this.model.isNew(record.id)) { return this.model.reload(this.recordID, { fieldNames: fieldNames, diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index ac703c0d90b..ec9700b66f3 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -431,7 +431,7 @@ QUnit.module('Views', { }); QUnit.test('fetch x2manys in list, with not too many rpcs', function (assert) { - assert.expect(2); + assert.expect(3); this.data.partner.records[0].category = [12, 15]; this.data.partner.records[1].category = [12, 14]; @@ -441,12 +441,11 @@ QUnit.module('Views', { this.params.groupedBy = []; this.params.res_id = undefined; - var rpcCount = 0; var model = createModel({ Model: BasicModel, data: this.data, mockRPC: function (route, args) { - rpcCount++; + assert.step(route); return this._super(route, args); }, }); @@ -456,7 +455,8 @@ QUnit.module('Views', { assert.strictEqual(record.data[0].data.category.data.length, 2, "first record should have 2 categories loaded"); - assert.strictEqual(rpcCount, 2, "should have done 2 rpc (searchread and read category)"); + assert.verifySteps(["/web/dataset/search_read"], + "should have done 2 rpc (searchread and read category)"); }); model.destroy(); }); @@ -1280,4 +1280,24 @@ QUnit.module('Views', { model.destroy(); }); + QUnit.test('load model with many2many field properly fetched', function (assert) { + assert.expect(2); + + this.params.fieldNames = ['category']; + this.params.res_id = 1; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function(route, args) { + assert.step(args.method); + return this._super(route, args); + }, + }); + + model.load(this.params); + assert.verifySteps(['read'], + "there should be only one read"); + model.destroy(); + }); });}); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 903142ac3a7..eacedbb4331 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -3101,6 +3101,7 @@ QUnit.module('Views', { }); } }); + QUnit.test('render stat button with string inline', function (assert) { assert.expect(1); @@ -3144,5 +3145,65 @@ QUnit.module('Views', { done(); }); }); + + QUnit.test('open one2many form containing one2many', function (assert) { + assert.expect(8); + + this.data.partner.records[0].product_ids = [37]; + this.data.product.fields.partner_type_ids = { + string: "one2many partner", type: "one2many", relation: "partner_type", + }; + this.data.product.records[0].partner_type_ids = [12]; + + var form = createView({ + View: FormView, + model: 'partner', + res_id: 1, + data: this.data, + arch: '
', + archs: { + 'product,false,form': + '', + }, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); + var row = form.$('.o_form_field_one2many .o_list_view .o_data_row'); + assert.strictEqual(row.children()[1].textContent, '1 record', + "the cell should contains the number of record: 1"); + row.click(); + var modal_row = $('.modal-body .o_form_sheet .o_form_field_one2many .o_list_view .o_data_row'); + assert.strictEqual(modal_row.children().length, 2, + "the row should contains the 2 fields defined in the form view"); + assert.strictEqual($(modal_row).text(), "gold2", + "the value of the fields should be fetched and displayed"); + assert.verifySteps(['read', 'read', 'read', 'read'], + "there should be 4 read rpcs"); + form.destroy(); + }); }); }); diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index ae0eda0bba0..e44f49227d2 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -420,13 +420,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('many2manytags are correctly fetched and displayed', function (assert) { - assert.expect(3); + QUnit.test('many2many_tags are correctly fetched and displayed', function (assert) { + assert.expect(5); this.data.partner.records[0].category_ids = [6, 7]; this.data.partner.records[1].category_ids = [7]; - var count = 0; var kanban = createView({ View: KanbanView, model: 'partner', @@ -439,8 +438,8 @@ QUnit.module('Views', { '' + '' + '', - mockRPC: function () { - count++; + mockRPC: function (route) { + assert.step(route); return this._super.apply(this, arguments); }, }); @@ -450,7 +449,7 @@ QUnit.module('Views', { 'first record should contain 2 tags'); assert.ok($first_record.find('.o_tag:first()').hasClass('o_tag_color_2'), 'first tag should have color 2'); - assert.strictEqual(count, 2, + assert.verifySteps(['/web/dataset/search_read', '/web/dataset/call_kw/category/read'], 'two RPC should have been done(one search read and one read for the m2m'); kanban.destroy(); }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 28944aeebfd..3b14f15a775 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -644,9 +644,7 @@ QUnit.module('Views', { }); QUnit.test('can display a list with a many2many field', function (assert) { - assert.expect(2); - - var rpcCount = 0; + assert.expect(3); var list = createView({ View: ListView, @@ -656,11 +654,11 @@ QUnit.module('Views', { '