diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index 276b29b71eb..dbb1b3e557c 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -90,6 +90,8 @@ var AbstractView = Factory.extend({
* @param {boolean} [params.withControlPanel=true]
*/
init: function (viewInfo, params) {
+ this._super.apply(this, arguments);
+
var action = params.action || {};
params = _.defaults(params, this._extractParamsFromAction(action));
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index a7b71ad4cf2..c96b677e2ea 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -26,6 +26,7 @@ var ListController = BasicController.extend({
activate_next_widget: '_onActivateNextWidget',
add_record: '_onAddRecord',
button_clicked: '_onButtonClicked',
+ group_edit_button_clicked: '_onEditGroupClicked',
edit_line: '_onEditLine',
save_line: '_onSaveLine',
resequence: '_onResequence',
@@ -528,6 +529,22 @@ var ListController = BasicController.extend({
});
new DataExport(this, record, defaultExportFields).open();
},
+ /**
+ * Opens the related form view.
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onEditGroupClicked: function (ev) {
+ ev.stopPropagation();
+ this.do_action({
+ type: 'ir.actions.act_window',
+ views: [[false, 'form']],
+ res_model: ev.data.record.model,
+ res_id: ev.data.record.res_id,
+ flags: {mode: 'edit'},
+ });
+ },
/**
* Force a resequence of the records curently on this page.
*
diff --git a/addons/web/static/src/js/views/list/list_model.js b/addons/web/static/src/js/views/list/list_model.js
new file mode 100644
index 00000000000..0be8f54e4f1
--- /dev/null
+++ b/addons/web/static/src/js/views/list/list_model.js
@@ -0,0 +1,117 @@
+odoo.define('web.ListModel', function (require) {
+ "use strict";
+
+ var BasicModel = require('web.BasicModel');
+
+ var ListModel = BasicModel.extend({
+
+ /**
+ * @override
+ * @param {Object} params.groupbys
+ */
+ init: function (parent, params) {
+ this._super.apply(this, arguments);
+
+ this.groupbys = params.groupbys;
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Overriden to add `groupData` when performing get on list datapoints.
+ *
+ * @override
+ * @see _readGroupExtraFields
+ */
+ get: function () {
+ var result = this._super.apply(this, arguments);
+ var dp = result && this.localData[result.id];
+ if (dp && dp.groupData) {
+ result.groupData = this.get(dp.groupData);
+ }
+ return result;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ *
+ * @override
+ * @private
+ */
+ _readGroup: function (list) {
+ var self = this;
+ return this._super.apply(this, arguments).then(function (result) {
+ var prom;
+ if (!list.parentID) {
+ // groupbys buttons are only displayed on the first level of
+ // groupby so no need to fetch the extra fields for inner
+ // groups
+ prom = self._readGroupExtraFields(list);
+ }
+ return Promise.resolve(prom).then(_.constant(result));
+ });
+ },
+ /**
+ * Fetches group specific fields on the group by relation and stores it
+ * in the column datapoint in a special key `groupData`.
+ * Data for the groups are fetched in batch for all groups, to avoid
+ * doing multiple calls.
+ * Note that the option is only for m2o fields.
+ *
+ * @private
+ * @param {Object} list
+ * @returns {Promise}
+ */
+ _readGroupExtraFields: function (list) {
+ var self = this;
+ var groupByFieldName = list.groupedBy[0].split(':')[0];
+ var groupedByField = list.fields[groupByFieldName];
+ if (groupedByField.type !== 'many2one' || !this.groupbys[groupByFieldName]) {
+ return Promise.resolve();
+ }
+ var groupIds = _.reduce(list.data, function (groupIds, id) {
+ var resId = self.get(id, { raw: true }).res_id;
+ if (resId) { // the field might be undefined when grouping
+ groupIds.push(resId);
+ }
+ return groupIds;
+ }, []);
+ var groupFields = Object.keys(this.groupbys[groupByFieldName].viewFields);
+ var prom;
+ if (groupIds.length && groupFields.length) {
+ prom = this._rpc({
+ model: groupedByField.relation,
+ method: 'read',
+ args: [groupIds, groupFields],
+ context: list.context,
+ });
+ }
+ return Promise.resolve(prom).then(function (result) {
+ var fvg = self.groupbys[groupByFieldName];
+ _.each(list.data, function (id) {
+ var dp = self.localData[id];
+ var groupData = result && _.findWhere(result, {
+ id: dp.res_id,
+ });
+ var groupDp = self._makeDataPoint({
+ context: dp.context,
+ data: groupData,
+ fields: fvg.fields,
+ fieldsInfo: fvg.fieldsInfo,
+ modelName: groupedByField.relation,
+ parentID: dp.id,
+ res_id: dp.res_id,
+ viewType: 'groupby',
+ });
+ dp.groupData = groupDp.id;
+ });
+ });
+ },
+ });
+ return ListModel;
+});
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index e6462d99609..670f311c195 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -31,8 +31,6 @@ var FIELD_CLASSES = {
text: 'o_list_text',
};
-var HEADING_COLUMNS_TO_SKIP_IN_GROUPS = 2;
-
var ListRenderer = BasicRenderer.extend({
events: {
'click tbody tr': '_onRowClicked',
@@ -68,6 +66,7 @@ var ListRenderer = BasicRenderer.extend({
this.pagers = []; // instantiated pagers (only for grouped lists)
this.editable = params.editable;
this.isGrouped = this.state.groupedBy.length > 0;
+ this.groupbys = params.groupbys;
},
//--------------------------------------------------------------------------
@@ -191,7 +190,7 @@ var ListRenderer = BasicRenderer.extend({
var self = this;
self.handleField = null;
this.columns = _.reject(this.arch.children, function (c) {
- if (c.tag === 'control') {
+ if (c.tag === 'control' || c.tag === 'groupby') {
return true;
}
var reject = c.attrs.modifiers.column_invisible;
@@ -212,16 +211,12 @@ var ListRenderer = BasicRenderer.extend({
*
* @private
* @param {any} aggregateValues
- * @param {Boolean} isHeader indicates wheter the groups rendered are on the header or on the footer
* @returns {jQueryElement[]} a list of
with the aggregate values
*/
- _renderAggregateCells: function (aggregateValues, isHeader) {
+ _renderAggregateCells: function (aggregateValues) {
var self = this;
- return _.map(this.columns, function (column, index) {
- if (isHeader && index === 0) {
- return;
- }
+ return _.map(this.columns, function (column) {
var $cell = $('
');
if (config.debug) {
$cell.addClass(column.attrs.name);
@@ -384,12 +379,66 @@ var ListRenderer = BasicRenderer.extend({
aggregates[column.attrs.name] = column.aggregate;
}
});
- var $cells = this._renderAggregateCells(aggregates, false);
+ var $cells = this._renderAggregateCells(aggregates);
if (this.hasSelectors) {
$cells.unshift($('
'));
}
return $('
').append($('
').append($cells));
},
+ /**
+ * Renders the group button element.
+ *
+ * @private
+ * @param {Object} record
+ * @param {Object} group
+ * @returns {jQuery} a