diff --git a/addons/web/controllers/__init__.py b/addons/web/controllers/__init__.py
index 2113dfa39c9..48cc37266c6 100644
--- a/addons/web/controllers/__init__.py
+++ b/addons/web/controllers/__init__.py
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import main, pivot, list_controller
+from . import main, pivot
diff --git a/addons/web/controllers/list_controller.py b/addons/web/controllers/list_controller.py
deleted file mode 100644
index 7ac98b79220..00000000000
--- a/addons/web/controllers/list_controller.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import json
-from datetime import datetime
-
-from odoo import http
-from odoo.http import request
-from odoo.tools.misc import xlwt
-
-
-HEADER_BOLD = xlwt.easyxf("font: bold on; pattern: pattern solid, fore_colour gray25;")
-
-class TableListExporter(http.Controller):
-
- def _write_group_header(self, worksheet, x, y, group, columns, group_depth=0):
- aggregates = group.get('aggregateValues', {})
- title = group.get('value')
-
- if group.get('hideHeader'):
- return x, y
-
- title = '%s%s (%s)' % ('>' * group_depth, title, group.get('count', 0))
- worksheet.write(x, y, title, HEADER_BOLD)
- for column in columns[1:]: # No aggregates allowed in the first column
- y += 1
- worksheet.write(x, y, aggregates.get(column['field'], ''), HEADER_BOLD)
- return x + 1, 0
-
- def _write_record(self, worksheet, x, y, record, columns):
- for column in columns:
- worksheet.write(x, y, record[column['field']])
- y += 1
- return x + 1, 0
-
- def _write_groups(self, worksheet, x, y, groups, columns, group_depth=0):
- """
- Write groups to the xls worksheet. Recursive if groups are nested (chained groupby)
- :param worksheet:
- :param x: start position where to write the group (vertical axis, pointing downward)
- :param y: start position where to write the group (horizontal axis, pointing to the right)
- :param groups: list of groups. Each group is a dict containing either records' data
- or sub-groups. A group has 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.
- :param columns: list of displayed columns. Each column is a dict with the following keys:
- 'field': field name
- 'aggregateValue': aggregate for all records
- 'string':
- """
- for group in groups:
- x, y = self._write_group_header(worksheet, x, y, group, columns, group_depth)
- if group['isGrouped']:
- # Recursively write sub-groups
- x, y = self._write_groups(worksheet, x, y, group['data'], columns, group_depth + 1)
- else:
- for record in group['data']:
- x, y = self._write_record(worksheet, x, y, record, columns)
- return x, y
-
- def _write_worksheet(self, worksheet, data):
- bold = xlwt.easyxf("font: bold on;")
-
- columns = data['columns']
- groups = data['groups']
-
- # Write main header
- for y, field in enumerate(columns):
- worksheet.write(0, y, field['string'], bold)
-
- # Write data
- x, y = 1, 0
- x, y = self._write_groups(worksheet, x, y, groups, columns)
-
- # Write column aggregates
- for y, field in enumerate(columns):
- worksheet.write(x, y, field.get('aggregateValue', ''), bold)
-
- @http.route('/web/list/export_xls', type='http', auth="user")
- def export_xls(self, data, token):
- data = json.loads(data)
- title = data.get('title', 'export')
- workbook = xlwt.Workbook()
- worksheet = workbook.add_sheet(title)
-
- self._write_worksheet(worksheet, data)
-
- response = request.make_response(None,
- headers=[('Content-Type', 'application/vnd.ms-excel'),
- ('Content-Disposition', 'attachment; filename="%s - %s.xls"' % (title, datetime.now()))],
- cookies={'fileToken': token})
- workbook.save(response.stream)
-
- return response
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..dd2adea5a84 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -8,78 +8,20 @@ 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(' ', '')
- });
- 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
* the list view. It can be overridden to add buttons in specific child views.
*/
buttons_template: 'ListView.buttons',
- events: _.extend({}, BasicController.prototype.events, {
- 'click .o_list_download': '_onExportRecords',
- }),
custom_events: _.extend({}, BasicController.prototype.custom_events, {
activate_next_widget: '_onActivateNextWidget',
add_record: '_onAddRecord',
@@ -109,9 +51,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,
- });
},
//--------------------------------------------------------------------------
@@ -401,31 +340,6 @@ var ListController = BasicController.extend({
self._updateButtons('readonly');
});
},
- /**
- * Export the current list data in a xls file.
- *
- * @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),
- });
- },
/**
* @override
* @private
@@ -732,14 +646,6 @@ var ListController = BasicController.extend({
});
new DataExport(this, record, defaultExportFields).open();
},
- /**
- * Export Records in a xls file
- *
- * @private
- */
- _onExportRecords() {
- this._downloadList()
- },
/**
* Opens the related form view.
*
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index f52818d5ed6..20c76190c7f 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -275,9 +275,6 @@
Create
-
-
-
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 82607c43689..98f45209943 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -3544,193 +3544,6 @@ QUnit.module('Views', {
list.destroy();
});
- QUnit.test('export list view to xls with nested groups', async function (assert) {
- assert.expect(5);
-
- let currencies = {};
- _.each(this.data.res_currency.records, function (currency) {
- currencies[currency.id] = currency;
- });
-
- let list = await createView({
- View: ListView,
- model: 'foo',
- data: this.data,
- arch: `
-
-
-
-
-
-
-
-
-
-
- `,
- groupBy: ['foo', 'bar'],
- session: {
- currencies: currencies,
- get_file(args) {
- let data = JSON.parse(args.data.data);
- assert.strictEqual(args.url, '/web/list/export_xls',
- "should call get_file with the correct url");
- assert.deepEqual(data.columns, [
- {field: "foo", string: "Foo"},
- {field: "bar", string: "Bar"},
- {field: "int_field", aggregateValue: 32, string: "int_field"},
- {field: "datetime", string: "Datetime Field"},
- {field: "amount", string: "Monetary field"},
- {field: "o2m", string: "O2M field"},
- {field: "m2o", string: "M2O field"},
- {field: "m2m", string: "M2M field"},
- ], "columns should be in the correct order with the string and aggregate value")
- assert.deepEqual(data.groups[0], {
- // Group "yop"
- isGrouped: true,
- count: 1,
- aggregateValues: {int_field: 10},
- hideHeader: false,
- value: "yop",
- data: [{
- // Group "yop > true"
- isGrouped: false,
- count: 1,
- aggregateValues: {int_field: 10},
- hideHeader: false,
- value: "true",
- data: [{
- // Record "yop > true"
- foo: "yop",
- bar: "True",
- int_field: "10",
- datetime: "12/12/2016 10:55:05",
- amount: "1200.00€",
- m2m: "2 records",
- m2o: "Value 1",
- o2m: "No records",
- }]
- }]
- });
- assert.deepEqual(data.groups[1], {
- // Group "blip"
- isGrouped: true,
- count: 2,
- aggregateValues: {int_field: 5},
- hideHeader: false,
- value: "blip",
- data: [{
- // Group "blip > true"
- isGrouped: false,
- count: 1,
- aggregateValues: {int_field: 9},
- hideHeader: false,
- value: "true",
- data: [],
- }, {
- // Group "blip > false"
- isGrouped: false,
- count: 1,
- aggregateValues: {int_field: -4},
- hideHeader: false,
- value: "false",
- data: [],
- }]
- });
- assert.deepEqual(data.groups[2], {
- // Group "gnap"
- isGrouped: true,
- count: 1,
- aggregateValues: {int_field: 17},
- hideHeader: false,
- value: "gnap",
- data: [],
- });
- args.complete();
- },
- },
- });
-
- // open the first group
- await testUtils.dom.click(list.$('.o_group_header:first'));
-
- // open subgroup
- let $openGroup = list.$('tbody:nth(1)');
- await testUtils.dom.click($openGroup.find('.o_group_header:first'));
-
- // open the second group
- await testUtils.dom.click(list.$('.o_group_header:nth(2)'));
-
- // Download
- await testUtils.dom.click(list.$buttons.find('.o_list_download'));
-
- list.destroy();
- });
-
- QUnit.test('export list view to xls', async function (assert) {
- assert.expect(3);
-
- let currencies = {};
- _.each(this.data.res_currency.records, function (currency) {
- currencies[currency.id] = currency;
- });
-
- let list = await createView({
- View: ListView,
- model: 'foo',
- data: this.data,
- arch: `
-
-
-
-
-
-
-
-
-
-
- `,
- session: {
- currencies: currencies,
- get_file(args) {
- let data = JSON.parse(args.data.data);
- assert.strictEqual(args.url, '/web/list/export_xls',
- "should call get_file with the correct url");
- assert.deepEqual(data.columns, [
- {field: "foo", string: "Foo"},
- {field: "bar", string: "Bar"},
- {field: "int_field", aggregateValue: 32, string: "int_field"},
- {field: "datetime", string: "Datetime Field"},
- {field: "amount", string: "Monetary field"},
- {field: "o2m", string: "O2M field"},
- {field: "m2o", string: "M2O field"},
- {field: "m2m", string: "M2M field"},
- ], "columns should be in the correct order with the string and aggregate value")
- assert.deepEqual(data.groups[0], {
- isGrouped: false,
- count: 4,
- aggregateValues: {},
- hideHeader: true,
- value: "Undefined",
- data: [
- {foo: "yop", bar: "True", int_field: "10", datetime: "12/12/2016 10:55:05", amount: "1200.00€", m2m: "2 records", m2o: "Value 1", o2m: "No records"},
- {foo: "blip", bar: "True", int_field: "9", datetime: "", amount: "$500.00", m2m: "3 records", m2o: "Value 2", o2m: "No records"},
- {foo: "gnap", bar: "True", int_field: "17", datetime: "", amount: "$300.00", m2m: "No records", m2o: "Value 1", o2m: "No records"},
- {foo: "blip", bar: "False", int_field: "-4", datetime: "", amount: "$0.00", m2m: "1 record", m2o: "Value 1", o2m: "No records"},
- ],
- });
- args.complete();
- },
- },
- });
-
- // Download
- await testUtils.dom.click(list.$buttons.find('.o_list_download'));
-
- list.destroy();
- });
-
QUnit.test('grouped list on selection field at level 2', async function (assert) {
assert.expect(4);
diff --git a/addons/web/tests/__init__.py b/addons/web/tests/__init__.py
index 690fab494d3..1ed329bafe1 100644
--- a/addons/web/tests/__init__.py
+++ b/addons/web/tests/__init__.py
@@ -6,4 +6,3 @@ from . import test_js
from . import test_menu
from . import test_serving_base
from . import test_click_everywhere
-from . import test_list_controller
diff --git a/addons/web/tests/test_list_controller.py b/addons/web/tests/test_list_controller.py
deleted file mode 100644
index 311def2989f..00000000000
--- a/addons/web/tests/test_list_controller.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from ..controllers.list_controller import TableListExporter
-from odoo.tests.common import HttpCase
-
-class WorksheetMock(dict):
-
- def write(self, x, y, value, style=None):
- if (x, y) in self:
- raise Exception("Cannot rewrite cell [{}, {}]".format(x, y))
- self[x, y] = value
-
-class ListController(HttpCase):
-
- def test_list_download(self):
- controller = TableListExporter()
-
- columns = [
- {'field': "foo", 'string': "Foo"},
- {'field': "bar", 'string': "Bar"},
- {'field': "int_field", 'aggregateValue': 19, 'string': "int_field"},
- ]
- groups = [{
- # Group "yop"
- 'isGrouped': True,
- 'count': 1,
- 'aggregateValues': {'int_field': 19},
- 'hideHeader': False,
- 'value': "yop",
- 'data': [{
- # Group "yop > true"
- 'isGrouped': False,
- 'count': 1,
- 'aggregateValues': {'int_field': 10},
- 'hideHeader': False,
- 'value': "true",
- 'data': [{'foo': "yop", 'bar': "True", 'int_field': 10}],
- }, {
- # Group "yop > false"
- 'isGrouped': False,
- 'count': 10,
- 'aggregateValues': {'int_field': 9},
- 'hideHeader': False,
- 'value': "false",
- 'data': [],
- }],
- }]
-
- worksheet = WorksheetMock()
- controller._write_worksheet(worksheet, {'columns': columns, 'groups': groups})
-
- # Header
- self.assertEqual(worksheet[0, 0], 'Foo')
- self.assertEqual(worksheet[0, 1], 'Bar')
- self.assertEqual(worksheet[0, 2], 'int_field')
-
- # Main group
- self.assertEqual(worksheet[1, 0], 'yop (1)', "Group header")
- self.assertEqual(worksheet[1, 2], 19, "int_field aggregate value")
-
- # First sub-group (open)
- self.assertEqual(worksheet[2, 0], '>true (1)', "subgroup header")
- self.assertEqual(worksheet[2, 2], 10, "int_field aggregate value")
-
- # data
- self.assertEqual(worksheet[3, 0], 'yop')
- self.assertEqual(worksheet[3, 1], 'True')
- self.assertEqual(worksheet[3, 2], 10)
-
- # second sub-group (closed)
- self.assertEqual(worksheet[4, 0], '>false (10)', "second sub-group header")
- self.assertEqual(worksheet[4, 2], 9, "int_field aggregate value")
-
- self.assertEqual(worksheet[5, 2], 19, "main list int_field aggregate value")
diff --git a/odoo/addons/base/rng/tree_view.rng b/odoo/addons/base/rng/tree_view.rng
index 6ebf2393da3..6ba2bf53899 100644
--- a/odoo/addons/base/rng/tree_view.rng
+++ b/odoo/addons/base/rng/tree_view.rng
@@ -27,7 +27,6 @@
-