summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
authorOdoo's Mergebot <xmo+roboto@odoo.com>2019-09-20 17:13:47 +0200
committerGitHub <noreply@github.com>2019-09-20 17:13:47 +0200
commitf1aa96b0a8120e7adfb9ef217c70c2b3f4a991bc (patch)
treecf9562a69cb67c5c05a7077c7e31b758b7034521 /addons/web/static/src/js
parent3e21daaf457919087000e430e9ab1ba0ab93902b (diff)
parent5c4544fb2943e63fc1e0f5ae1e7e250a5a2b2cf1 (diff)
[MERGE] web: Re-implement the XLS grouped export
Purpose ======= The commit 2849b5c introduces a new export mechanism of grouped list views to xls files. The issue with this development is mainly that the displayed records are exported, instead of all the records that match the search parameters. To export all the records we cannot rely on the data from the web client. This implies to revert 2849b5c, and implement it in a better way. Functional Spec =============== Add the support of grouped exports. Allow the user to export all records in one click from the listview, without having to go through the export modal, taking into account the domain, groupbys, and visible fields. Technical Spec ============== When exporting (whether it is from the modal or from the shortcut), any groupby(s) set on the listview should be taken into account (all unfolded) - UNLESS the export is import-compatible - each subgroup header has an indentation compared to its parent - the 'group headers' in the exported file should contain the same info (label, field aggregates) as it has in the listview. New secondary button on the tree view with label 'EXPORT' (to be confirmed...) - the export shortcut disregard the selected records, it exports all records according to the domain. - the button is visible even if there is no selected records. - essentially the export shortcut does the same thing as the following: - select all records - hit 'action' then 'export' - hit 'export' Define a boolean attribute on <tree> to specify whether or not the export shortcut should be displayed Task 2072910 closes odoo/odoo#37087 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
Diffstat (limited to 'addons/web/static/src/js')
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js105
-rw-r--r--addons/web/static/src/js/views/list/list_view.js1
-rw-r--r--addons/web/static/src/js/widgets/data_export.js56
3 files changed, 49 insertions, 113 deletions
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 4e0060fa5ff..b90d903739b 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -8,69 +8,14 @@ odoo.define('web.ListController', function (require) {
*/
var core = require('web.core');
-var session = require('web.session');
-var framework = require('web.framework');
var BasicController = require('web.BasicController');
var DataExport = require('web.DataExport');
var Dialog = require('web.Dialog');
-var fieldUtils = require('web.field_utils');
var Sidebar = require('web.Sidebar');
var _t = core._t;
var qweb = core.qweb;
-/**
- * Return data required for exporting the current list to a xls file.
- * Recursive if some groups are nested.
- * Note: this function is also used when the records are not grouped.
- * In this scenario, an artificial single group with every record in
- * the list is built.
- * @param {Array} groups
- * @param {Array} displayedFields
- * @param {Array} allFields
- * @returns {Array} list of groups with the following structure:
- * 'data': list of records or list of sub-groups
- * 'isGrouped': true if data contains sub-groups
- * 'value': title of the group
- * 'count': number of records in the group
- * 'aggregateValues': dict container aggregated values of fields {field: aggregatedValue, ...}
- * 'hideHeader': self explanatory, used when the records are not grouped at all.
- */
-var processGroups = (groups, displayedFields, allFields) => {
- let formatRecord = record => {
- let data = _.pick(record.data, displayedFields)
- let formattedData = {}
-
- Object.entries(data).forEach(([field_name, value]) => {
- let field = allFields[field_name]
- let formattedValue = fieldUtils.format[field.type](value, field, {data: record.data, forceString: true})
- formattedData[field_name] = formattedValue.replace('&nbsp;', '')
- });
- return formattedData
- }
-
- if (groups.length && groups[0].groupedBy && groups[0].groupedBy.length) {
- // Recursively process sub-group
- return groups.map(group => ({
- hideHeader: !!group.hideHeader,
- isGrouped: true,
- value: group.value !== undefined && group.value.toString() || _t("Undefined"),
- count: group.count,
- aggregateValues: _.pick(group.aggregateValues, displayedFields),
- data: processGroups(group.data, displayedFields, allFields),
- }))
- }
- // process records
- return groups.map(group => ({
- hideHeader: !!group.hideHeader,
- isGrouped: false,
- value: group.value !== undefined && group.value.toString() || _t("Undefined"),
- count: group.count,
- aggregateValues: _.pick(group.aggregateValues, displayedFields),
- data: group.data.map(formatRecord)
- }))
-}
-
var ListController = BasicController.extend({
/**
* This key contains the name of the buttons template to render on top of
@@ -78,7 +23,7 @@ var ListController = BasicController.extend({
*/
buttons_template: 'ListView.buttons',
events: _.extend({}, BasicController.prototype.events, {
- 'click .o_list_download': '_onExportRecords',
+ 'click .o_list_export_xlsx': '_onDirectExportData',
}),
custom_events: _.extend({}, BasicController.prototype.custom_events, {
activate_next_widget: '_onActivateNextWidget',
@@ -109,9 +54,6 @@ var ListController = BasicController.extend({
this.selectedRecords = params.selectedRecords || [];
this.multipleRecordsSavingPromise = null;
this.fieldChangedPrevented = false;
- this.activeActions = _.extend({}, this.activeActions, {
- download: this.renderer.arch.attrs.download ? !!JSON.parse(this.renderer.arch.attrs.download) : true,
- });
},
//--------------------------------------------------------------------------
@@ -130,14 +72,11 @@ var ListController = BasicController.extend({
* @returns {Promise<array[]>} a promise that resolve to the active domain
*/
getActiveDomain: function () {
- // TODO: this method should be synchronous...
var self = this;
if (this.$('thead .o_list_record_selector input').prop('checked')) {
var searchQuery = this._controlPanel ? this._controlPanel.getSearchQuery() : {};
var record = self.model.get(self.handle, {raw: true});
- return Promise.all(record.getDomain().concat(searchQuery.domain || []));
- } else {
- return Promise.resolve();
+ return record.getDomain().concat(searchQuery.domain || []);
}
},
/*
@@ -402,29 +341,15 @@ var ListController = BasicController.extend({
});
},
/**
- * Export the current list data in a xls file.
- *
+ * @returns {DataExport} the export dialog widget
* @private
*/
- _downloadList() {
- let groups = this.renderer.state.data
- groups = this.renderer.isGrouped ? groups : [{data: groups, count: groups.length, hideHeader: true}] // Artificial single group
-
- let allFields = this.renderer.state.fields
- let columns = this.renderer.columns.map(column => ({
- field: column.attrs.name,
- aggregateValue: column.aggregate && column.aggregate.value,
- string: allFields[column.attrs.name].string,
- }))
-
- groups = processGroups(groups, columns.map(c => c.field), allFields)
-
- return session.get_file({
- url: '/web/list/export_xls',
- data: {data: JSON.stringify({columns, groups, title: this._title})},
- complete: framework.unblockUI,
- error: (error) => this.call('crash_manager', 'rpc_error', error),
- });
+ _getExportDialogWidget() {
+ let state = this.model.get(this.handle);
+ let defaultExportFields = this.renderer.columns.map(field => field.attrs.name);
+ let groupedBy = this.renderer.state.groupedBy;
+ return new DataExport(this, state, defaultExportFields, groupedBy,
+ this.getActiveDomain(), this.getSelectedIds());
},
/**
* @override
@@ -585,6 +510,8 @@ var ListController = BasicController.extend({
* @returns {Promise}
*/
_update: function () {
+ let visilibityFunction = this.renderer.state.count ? 'show' : 'hide'
+ this.$('.o_list_export_xlsx')[visilibityFunction]()
return this._super.apply(this, arguments)
.then(this._toggleSidebar.bind(this))
.then(this._toggleCreateButton.bind(this))
@@ -726,19 +653,15 @@ var ListController = BasicController.extend({
* @private
*/
_onExportData: function () {
- var record = this.model.get(this.handle);
- var defaultExportFields = _.map(this.renderer.columns, function (field) {
- return field.attrs.name;
- });
- new DataExport(this, record, defaultExportFields).open();
+ this._getExportDialogWidget().open();
},
/**
* Export Records in a xls file
*
* @private
*/
- _onExportRecords() {
- this._downloadList()
+ _onDirectExportData() {
+ this._getExportDialogWidget().export();
},
/**
* Opens the related form view.
diff --git a/addons/web/static/src/js/views/list/list_view.js b/addons/web/static/src/js/views/list/list_view.js
index baba4d4a35f..f3f125ba8e3 100644
--- a/addons/web/static/src/js/views/list/list_view.js
+++ b/addons/web/static/src/js/views/list/list_view.js
@@ -52,6 +52,7 @@ var ListView = BasicView.extend({
}
});
+ this.controllerParams.activeActions.export_xlsx = this.arch.attrs.export_xlsx ? !!JSON.parse(this.arch.attrs.export_xlsx): true;
this.controllerParams.editable = (!this.arch.attrs.edit || !!JSON.parse(this.arch.attrs.edit)) ?
this.arch.attrs.editable : false;
this.controllerParams.hasSidebar = params.hasSidebar;
diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js
index 070b1927c5f..97ec75e6b47 100644
--- a/addons/web/static/src/js/widgets/data_export.js
+++ b/addons/web/static/src/js/widgets/data_export.js
@@ -34,11 +34,11 @@ var DataExport = Dialog.extend({
* @param {Object} record
* @param {string[]} defaultExportFields
*/
- init: function (parent, record, defaultExportFields) {
+ init: function (parent, record, defaultExportFields, groupedBy, activeDomain, idsToExport) {
var options = {
title: _t("Export Data"),
buttons: [
- {text: _t("Export"), click: this._exportData, classes: 'btn-primary'},
+ {text: _t("Export"), click: this._onExportData, classes: 'btn-primary'},
{text: _t("Close"), close: true},
],
};
@@ -46,9 +46,13 @@ var DataExport = Dialog.extend({
this.records = {};
this.record = record;
this.defaultExportFields = defaultExportFields;
+ this.groupby = groupedBy;
this.exports = new data.DataSetSearch(this, 'ir.exports', this.record.getContext());
this.rowIndex = 0;
this.rowIndexLevel = 0;
+ this.isCompatibleMode = false;
+ this.domain = activeDomain || this.record.domain;
+ this.idsToExport = activeDomain ? false: idsToExport;
},
/**
* @override
@@ -73,16 +77,6 @@ var DataExport = Dialog.extend({
});
}));
- proms.push(this.getParent().getActiveDomain().then(function (domain) {
- if (domain === undefined) {
- self.idsToExport = self.getParent().getSelectedIds();
- self.domain = self.record.domain;
- } else {
- self.idsToExport = false;
- self.domain = domain;
- }
- }));
-
proms.push(this._showExportsList());
// Bind sortable events after Dialog is open
@@ -117,6 +111,17 @@ var DataExport = Dialog.extend({
}
},
+ /**
+ * Export all data with default values (fields, domain)
+ */
+ export() {
+ let exportedFields = this.defaultExportFields.map(field => ({
+ name: field,
+ label: this.record.fields[field].string,
+ }));
+ this._exportData(exportedFields, 'xlsx', false);
+ },
+
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -146,13 +151,7 @@ var DataExport = Dialog.extend({
*
* @private
*/
- _exportData: function () {
- var exportedFields = this.$('.o_export_field').map(function () {
- return {
- name: $(this).data('field_id'),
- label: this.textContent
- };
- }).get();
+ _exportData(exportedFields, exportFormat, idsToExport) {
if (_.isEmpty(exportedFields)) {
Dialog.alert(this, _t("Please select fields to export..."));
@@ -162,8 +161,6 @@ var DataExport = Dialog.extend({
exportedFields.unshift({ name: 'id', label: _t('External ID') });
}
- var exportFormat = this.$exportFormatInputs.filter(':checked').val();
-
framework.blockUI();
this.getSession().get_file({
url: '/web/export/' + exportFormat,
@@ -171,8 +168,9 @@ var DataExport = Dialog.extend({
data: JSON.stringify({
model: this.record.model,
fields: exportedFields,
- ids: this.idsToExport,
+ ids: idsToExport,
domain: this.domain,
+ groupby: this.groupby,
context: pyUtils.eval('contexts', [this.record.getContext()]),
import_compat: this.isCompatibleMode,
})
@@ -546,6 +544,20 @@ var DataExport = Dialog.extend({
}
},
/**
+ * Submit the user data and export the file
+ *
+ * @private
+ */
+ _onExportData() {
+ let exportedFields = this.$('.o_export_field').map((i, field) => ({
+ name: $(field).data('field_id'),
+ label: field.textContent,
+ }
+ )).get();
+ let exportFormat = this.$exportFormatInputs.filter(':checked').val();
+ this._exportData(exportedFields, exportFormat, this.idsToExport);
+ },
+ /**
* Add a field to export field list on double click
*
* @private