diff options
| author | Mathieu Duckerts-Antoine <dam@odoo.com> | 2018-04-05 17:08:26 +0200 |
|---|---|---|
| committer | Géry Debongnie <ged@odoo.com> | 2018-04-10 17:41:12 +0200 |
| commit | 47395c6e51ec82283d3cd46e386befc256d8a657 (patch) | |
| tree | ba8f89cdd2e46ccbe16635939ba3afe318922139 /addons | |
| parent | 742476fb20e8183f96c42836552381fff259712c (diff) | |
[IMP] web: allow using many2one as measure in pivot view
With this commit, we add the possibility of using many2ones as measure.
The way a many2one is aggregated is by using the count distinct
operator.
Some care had to be done to gracefully support the situation when a
pivot view is grouped by a m2o field F, and the same field F is used as
a measure.
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/web/static/src/js/views/pivot/pivot_model.js | 54 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/pivot/pivot_renderer.js | 3 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/pivot/pivot_view.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 2 | ||||
| -rw-r--r-- | addons/web/static/tests/helpers/mock_server.js | 16 | ||||
| -rw-r--r-- | addons/web/static/tests/views/pivot_tests.js | 79 |
6 files changed, 144 insertions, 12 deletions
diff --git a/addons/web/static/src/js/views/pivot/pivot_model.js b/addons/web/static/src/js/views/pivot/pivot_model.js index 1c76c41ab71..fafeb8428d9 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -73,7 +73,15 @@ var PivotModel = AbstractModel.extend({ var other_root = header.root.other_root; var other_groupbys = header.root.other_root.groupbys; - var fields = [].concat(field, other_groupbys, this.data.measures); + + var measures = _.map(this.data.measures, function(measure) { + var type = self.fields[measure].type; + return (type === 'many2one') ? measure + ":count_distinct" : measure; + }); + var fields = _.map(other_groupbys, function (field) { + return field.split(':')[0]; + }); + var groupbys = []; for (var i = 0; i <= other_groupbys.length; i++) { @@ -86,7 +94,7 @@ var PivotModel = AbstractModel.extend({ method: 'read_group', context: self.data.context, domain: header.domain.length ? header.domain : self.data.domain, - fields: _.map(fields, function (field) { return field.split(':')[0]; }), + fields: [].concat(field, fields, measures), groupBy: groupBy, lazy: false, }); @@ -113,7 +121,19 @@ var PivotModel = AbstractModel.extend({ continue; } for (cell_value = {}, l=0; l < self.data.measures.length; l++) { - cell_value[self.data.measures[l]] = datapt[self.data.measures[l]]; + var _value = datapt[self.data.measures[l]]; + if (_value instanceof Array) { + // when a many2one field is used as a measure AND as + // a grouped field, bad things happen. The server + // will only return the grouped value and will not + // aggregate it. Since there is a nameclash, we are + // then in the situation where this value is an + // array. Fortunately, if we group by a field, + // then we can say for certain that the group contains + // exactly one distinct value for that field. + _value = 1; + } + cell_value[self.data.measures[l]] =_value; } // cell_value.__count = attrs.length; if (!self.data.cells[row.id]) { @@ -523,7 +543,17 @@ var PivotModel = AbstractModel.extend({ var groupBys = []; var rowGroupBys = this.data.groupedBy.length ? this.data.groupedBy : this.initialRowGroupBys; var colGroupBys = this.data.colGroupBys; - var fields = [].concat(rowGroupBys, colGroupBys, this.data.measures); + var measures = _.map(this.data.measures, function(measure) { + if (self.fields[measure].type === 'many2one') { + return measure + ":count_distinct"; + } + else { + return measure; + } + }); + var fields = _.map(rowGroupBys.concat(colGroupBys), function (field) { + return field.split(':')[0]; + }).concat(measures); for (var i = 0; i < rowGroupBys.length + 1; i++) { for (var j = 0; j < colGroupBys.length + 1; j++) { @@ -537,7 +567,7 @@ var PivotModel = AbstractModel.extend({ method: 'read_group', context: self.data.context, domain: self.data.domain, - fields: _.map(fields, function (field) { return field.split(':')[0]; }), + fields: fields, groupBy: groupBy, lazy: false, }); @@ -633,7 +663,19 @@ var PivotModel = AbstractModel.extend({ } if (!this.data.cells[row.id]) this.data.cells[row.id] = []; for (cell_value = {}, m=0; m < this.data.measures.length; m++) { - cell_value[this.data.measures[m]] = datapt[this.data.measures[m]]; + var _value = datapt[this.data.measures[m]]; + if (_value instanceof Array) { + // when a many2one field is used as a measure AND as + // a grouped field, bad things happen. The server + // will only return the grouped value and will not + // aggregate it. Since there is a nameclash, we are + // then in the situation where this value is an + // array. Fortunately, if we group by a field, + // then we can say for certain that the group contains + // exactly one distinct value for that field. + _value = 1; + } + cell_value[this.data.measures[m]] = _value; } this.data.cells[row.id][col.id] = cell_value; } diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js index 83112277bb3..b5648d358a9 100644 --- a/addons/web/static/src/js/views/pivot/pivot_renderer.js +++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js @@ -124,7 +124,8 @@ var PivotRenderer = AbstractRenderer.extend({ return self.state.fields[gb.split(':')[0]].string; }); var measureTypes = this.state.measures.map(function (name) { - return self.state.fields[name].type; + var type = self.state.fields[name].type; + return type === 'many2one' ? 'integer' : type; }); for (i = 0; i < rows.length; i++) { $row = $('<tr>'); diff --git a/addons/web/static/src/js/views/pivot/pivot_view.js b/addons/web/static/src/js/views/pivot/pivot_view.js index 0d42fc7bd46..199042ec12a 100644 --- a/addons/web/static/src/js/views/pivot/pivot_view.js +++ b/addons/web/static/src/js/views/pivot/pivot_view.js @@ -45,7 +45,7 @@ var PivotView = AbstractView.extend({ this.fields.__count = {string: _t("Count"), type: "integer"}; _.each(this.fields, function (field, name) { if ((name !== 'id') && (field.store === true)) { - if (_.contains(['integer', 'float', 'monetary'], field.type)) { + if (_.contains(['integer', 'float', 'monetary', 'many2one'], field.type)) { measures[name] = field; } if (_.contains(GROUPABLE_TYPES, field.type)) { diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index ccd98c10962..96f80c96eb2 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -527,7 +527,7 @@ </button> <ul class="dropdown-menu o_pivot_measures_list"> <li t-foreach="measures" t-as="measure" t-att-data-field="measure[0]"> - <a href="#"><t t-esc="measure[1].string"/></a> + <a href="#"><t t-esc="measure[1].string"/><t t-if="measure[1].type === 'many2one'"> (count)</t></a> </li> <li class="divider"></li> <li data-field="__count"><a href="#">Count</a></li> diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index 8ae3e48780a..a66f78ff159 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -679,8 +679,18 @@ var MockServer = Class.extend({ } var self = this; var fields = this.data[model].fields; - var aggregatedFields = _.map(kwargs.fields, function (field) { - return field.split(":")[0]; + var aggregatedFields = []; + _.each(kwargs.fields, function (field) { + var split = field.split(":"); + var fieldName = split[0]; + if (kwargs.groupby.indexOf(fieldName) > 0) { + // grouped fields are not aggregated + return; + } + if (fields[fieldName] && (fields[fieldName].type === 'many2one') && split[1] !== 'count_distinct') { + return; + } + aggregatedFields.push(fieldName); }); var groupBy = []; if (kwargs.groupby.length) { @@ -689,7 +699,7 @@ var MockServer = Class.extend({ var records = this._getRecords(model, kwargs.domain); // if no fields have been given, the server picks all stored fields - if (aggregatedFields.length === 0) { + if (kwargs.fields.length === 0) { aggregatedFields = _.keys(this.data[model].fields); } diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js index 1f89c1fbf9c..446ab36e20e 100644 --- a/addons/web/static/tests/views/pivot_tests.js +++ b/addons/web/static/tests/views/pivot_tests.js @@ -17,6 +17,7 @@ QUnit.module('Views', { bar: {string: "bar", type: "boolean"}, date: {string: "Date", type: "date", store: true}, product_id: {string: "Product", type: "many2one", relation: 'product', store: true}, + other_product_id: {string: "Other Product", type: "many2one", relation: 'product', store: true}, non_stored_m2o: {string: "Non Stored M2O", type: "many2one", relation: 'product'}, customer: {string: "Customer", type: "many2one", relation: 'customer', store: true}, }, @@ -965,4 +966,82 @@ QUnit.module('Views', { pivot.destroy(); }); + + QUnit.test('pivot view with many2one field as a measure', function (assert) { + assert.expect(1); + + var pivot = createView({ + View: PivotView, + model: "partner", + data: this.data, + arch: '<pivot>' + + '<field name="product_id" type="measure"/>' + + '<field name="date" interval="month" type="col"/>' + + '</pivot>', + }); + + assert.strictEqual(pivot.$('table tbody tr').text().trim(), "Total2112", + "should display product_id count as measure"); + pivot.destroy(); + }); + + QUnit.test('m2o as measure, drilling down into data', function (assert) { + assert.expect(1); + + var pivot = createView({ + View: PivotView, + model: "partner", + data: this.data, + arch: '<pivot>' + + '<field name="product_id" type="measure"/>' + + '</pivot>', + }); + pivot.$('tbody .o_pivot_header_cell_closed').first().click(); + // click on date by day + pivot.$('ul.o_pivot_field_menu > li[data-field="date"] a[data-interval="month"]').click(); + + assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '2211', + 'should have loaded the proper data'); + pivot.destroy(); + }); + + QUnit.test('pivot view with same many2one field as a measure and grouped by', function (assert) { + assert.expect(1); + + var pivot = createView({ + View: PivotView, + model: "partner", + data: this.data, + arch: '<pivot>' + + '<field name="product_id" type="row"/>' + + '</pivot>', + }); + + pivot.$buttons.find('li[data-field=product_id] a').click(); + assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '421131', + 'should have loaded the proper data'); + pivot.destroy(); + }); + + QUnit.test('pivot view with same many2one field as a measure and grouped by (and drill down)', function (assert) { + assert.expect(1); + + var pivot = createView({ + View: PivotView, + model: "partner", + data: this.data, + arch: '<pivot>' + + '<field name="product_id" type="measure"/>' + + '</pivot>', + }); + + pivot.$('tbody .o_pivot_header_cell_closed').first().click(); + + pivot.$('ul.o_pivot_field_menu > li[data-field="product_id"] a').click(); + + assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '211', + 'should have loaded the proper data'); + pivot.destroy(); + }); + });}); |
