[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.
This commit is contained in:
committed by
Géry Debongnie
parent
742476fb20
commit
47395c6e51
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>');
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
});});
|
||||
|
||||
Reference in New Issue
Block a user