From ee3e28987eb1ec23b8bacc7c4043d9690aada144 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 6 Feb 2023 15:36:20 +0000 Subject: [PATCH] [REM] web,project: remove legacy data export This commit remove the legacy implementation of the data export dialog, which wasn't used since the removal of the legacy list view. This also allowed us to finally remove the data.js file, which contained the "legacy legacy" implementation of the model (DataSetStatic, DataSetSearch...). This was deprecated since v11. Part of task 3168640 Part-of: odoo/odoo#111809 --- addons/project/__manifest__.py | 2 - addons/web/__manifest__.py | 2 - .../static/src/legacy/js/_deprecated/data.js | 852 ------------------ .../src/legacy/js/fields/relational_fields.js | 3 +- .../src/legacy/js/widgets/data_export.js | 694 -------------- .../static/src/legacy/scss/data_export.scss | 18 - addons/web/static/src/legacy/xml/base.xml | 76 -- 7 files changed, 1 insertion(+), 1646 deletions(-) delete mode 100644 addons/web/static/src/legacy/js/_deprecated/data.js delete mode 100644 addons/web/static/src/legacy/js/widgets/data_export.js delete mode 100644 addons/web/static/src/legacy/scss/data_export.scss diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index c950c348b27..129d5684a55 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -153,7 +153,6 @@ 'web/static/src/legacy/backend_utils.js', 'web/static/src/legacy/utils.js', 'web/static/src/legacy/web_client.js', - 'web/static/src/legacy/js/_deprecated/data.js', 'web/static/src/legacy/js/chrome/*', 'web/static/src/legacy/js/components/*', 'web/static/src/legacy/js/control_panel/*', @@ -177,7 +176,6 @@ 'web/static/src/legacy/js/services/session.js', 'web/static/src/legacy/js/tools/tools.js', 'web/static/src/legacy/js/views/**/*', - 'web/static/src/legacy/js/widgets/data_export.js', 'web/static/src/legacy/js/widgets/date_picker.js', 'web/static/src/legacy/js/widgets/domain_selector_dialog.js', 'web/static/src/legacy/js/widgets/domain_selector.js', diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index 45253951b0c..60f19ca9a89 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -216,7 +216,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/scss/ace.scss', 'web/static/src/legacy/scss/fields.scss', 'web/static/src/legacy/scss/views.scss', - 'web/static/src/legacy/scss/data_export.scss', 'base/static/src/scss/onboarding.scss', 'web/static/src/legacy/scss/attachment_preview.scss', 'web/static/src/legacy/scss/base_document_layout.scss', @@ -256,7 +255,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/js/services/session.js', 'web/static/src/legacy/js/tools/tools.js', 'web/static/src/legacy/js/views/**/*', - 'web/static/src/legacy/js/widgets/data_export.js', 'web/static/src/legacy/js/widgets/date_picker.js', 'web/static/src/legacy/js/widgets/domain_selector_dialog.js', 'web/static/src/legacy/js/widgets/domain_selector.js', diff --git a/addons/web/static/src/legacy/js/_deprecated/data.js b/addons/web/static/src/legacy/js/_deprecated/data.js deleted file mode 100644 index 71ae101da7a..00000000000 --- a/addons/web/static/src/legacy/js/_deprecated/data.js +++ /dev/null @@ -1,852 +0,0 @@ -odoo.define('web.data', function (require) { -"use strict"; - -var Class = require('web.Class'); -var Context = require('web.Context'); -var concurrency = require('web.concurrency'); -var mixins = require('web.mixins'); -var session = require('web.session'); -var translation = require('web.translation'); -var pyUtils = require('web.py_utils'); - -var _t = translation._t; - -/** - * Serializes the sort criterion array of a dataset into a form which can be - * consumed by OpenERP's RPC APIs. - * - * @param {Array} criterion array of fields, from first to last criteria, prefixed with '-' for reverse sorting - * @returns {String} SQL-like sorting string (``ORDER BY``) clause - */ -function serialize_sort(criterion) { - return _.map(criterion, - function (criteria) { - if (criteria[0] === '-') { - return criteria.slice(1) + ' DESC'; - } - return criteria + ' ASC'; - }).join(', '); -} - -/** - * Reverse of the serialize_sort function: convert an array of SQL-like sort - * descriptors into a list of fields prefixed with '-' if necessary. - */ -function deserialize_sort(criterion) { - return _.map(criterion, function (criteria) { - var split = _.without(criteria.split(' '), ''); - return (split[1] && split[1].toLowerCase() === 'desc' ? '-' : '') + split[0]; - }); -} - -var Query = Class.extend({ - init: function (model, fields) { - this._model = model; - this._fields = fields; - this._filter = []; - this._context = {}; - this._lazy = true; - this._limit = false; - this._offset = 0; - this._order_by = []; - }, - clone: function (to_set) { - to_set = to_set || {}; - var q = new Query(this._model, this._fields); - q._context = this._context; - q._filter = this._filter; - q._lazy = this._lazy; - q._limit = this._limit; - q._offset = this._offset; - q._order_by = this._order_by; - - for(var key in to_set) { - if (!to_set.hasOwnProperty(key)) { continue; } - switch(key) { - case 'filter': - q._filter = (q._filter || []).concat(to_set.filter || []); - break; - case 'context': - q._context = new Context( - q._context, to_set.context); - break; - case 'lazy': - case 'limit': - case 'offset': - case 'order_by': - q['_' + key] = to_set[key]; - } - } - return q; - }, - _execute: function (options) { - var self = this; - options = options || {}; - return session.rpc('/web/dataset/search_read', { - model: this._model.name, - fields: this._fields || false, - domain: pyUtils.eval('domains', - [this._model.domain(this._filter)]), - context: pyUtils.eval('contexts', - [this._model.context(this._context)]), - offset: this._offset, - limit: this._limit, - sort: serialize_sort(this._order_by) - }, options).then(function (results) { - self._count = results.length; - return results.records; - }, null); - }, - /** - * Fetches the first record matching the query, or null - * - * @param {Object} [options] additional options for the rpc() method - * @returns {Promise} - */ - first: function (options) { - var self = this; - return this.clone({limit: 1})._execute(options).then(function (records) { - delete self._count; - if (records.length) { return records[0]; } - return null; - }); - }, - /** - * Fetches all records matching the query - * - * @param {Object} [options] additional options for the rpc() method - * @returns {Promise>} - */ - all: function (options) { - return this._execute(options); - }, - /** - * Fetches the number of records matching the query in the database - * - * @returns {Promise} - */ - count: function () { - if (this._count !== undefined) { return Promise.resolve(this._count); } - return this._model.call( - 'search_count', [this._filter], { - context: this._model.context(this._context)}); - }, - /** - * Performs a groups read according to the provided grouping criterion - * - * @param {String|Array} grouping - * @returns {jQuery.Deferred> | null} - */ - group_by: function (grouping) { - var ctx = pyUtils.eval( - 'context', this._model.context(this._context)); - - // undefined passed in explicitly (!) - if (_.isUndefined(grouping)) { - grouping = []; - } - - if (!(grouping instanceof Array)) { - grouping = _.toArray(arguments); - } - if (_.isEmpty(grouping) && !ctx.group_by_no_leaf) { - return null; - } - var raw_fields = _.map(grouping.concat(this._fields || []), function (field) { - return field.split(':')[0]; - }); - - var self = this; - return this._model.call('read_group', { - groupby: grouping, - fields: _.uniq(raw_fields), - domain: this._model.domain(this._filter), - context: ctx, - offset: this._offset, - lazy: this._lazy, - limit: this._limit, - orderby: serialize_sort(this._order_by) || false - }).then(function (results) { - return _(results).map(function (result) { - // FIX: querygroup initialization - result.__context = result.__context || {}; - result.__context.group_by = result.__context.group_by || []; - _.defaults(result.__context, ctx); - var grouping_fields = self._lazy ? [grouping[0]] : grouping; - return new QueryGroup( - self._model.name, grouping_fields, result); - }); - }); - }, - /** - * Creates a new query with the union of the current query's context and - * the new context. - * - * @param context context data to add to the query - * @returns {openerp.web.Query} - */ - context: function (context) { - if (!context) { return this; } - return this.clone({context: context}); - }, - /** - * Creates a new query with the union of the current query's filter and - * the new domain. - * - * @param domain domain data to AND with the current query filter - * @returns {openerp.web.Query} - */ - filter: function (domain) { - if (!domain) { return this; } - return this.clone({filter: domain}); - }, - /** - * Creates a new query with the provided parameter lazy replacing the current - * query's own. - * - * @param {Boolean} lazy indicates if the read_group should return only the - * first level of groupby records, or should return the records grouped by - * all levels at once (so, it makes only 1 db request). - * @returns {openerp.web.Query} - */ - lazy: function (lazy) { - return this.clone({lazy: lazy}); - }, - /** - * Creates a new query with the provided limit replacing the current - * query's own limit - * - * @param {Number} limit maximum number of records the query should retrieve - * @returns {openerp.web.Query} - */ - limit: function (limit) { - return this.clone({limit: limit}); - }, - /** - * Creates a new query with the provided offset replacing the current - * query's own offset - * - * @param {Number} offset number of records the query should skip before starting its retrieval - * @returns {openerp.web.Query} - */ - offset: function (offset) { - return this.clone({offset: offset}); - }, - /** - * Creates a new query with the provided ordering parameters replacing - * those of the current query - * - * @param {String...} fields ordering clauses - * @returns {openerp.web.Query} - */ - order_by: function (fields) { - if (fields === undefined) { return this; } - if (!(fields instanceof Array)) { - fields = _.toArray(arguments); - } - if (_.isEmpty(fields)) { return this; } - return this.clone({order_by: fields}); - } -}); - -var QueryGroup = Class.extend({ - init: function (model, grouping_fields, read_group_group) { - // In cases where group_by_no_leaf and no group_by, the result of - // read_group has aggregate fields but no __context or __domain. - // Create default (empty) values for those so that things don't break - var fixed_group = _.extend( - {__context: {group_by: []}, __domain: []}, - read_group_group); - - var count_key = (grouping_fields[0] && grouping_fields[0].split(':')[0]) + '_count'; - var aggregates = {}; - for (var key in fixed_group) { - if (fixed_group.hasOwnProperty(key)) { - if (!(key.indexOf('__') === 0 - || _.contains(grouping_fields, key) - || (key === count_key))) { - aggregates[key] = fixed_group[key] || 0; - } - } - } - - this.model = new Model( - model, fixed_group.__context, fixed_group.__domain); - - var group_size = fixed_group[count_key] || fixed_group.__count || 0; - var leaf_group = fixed_group.__context.group_by.length === 0; - - var value = (grouping_fields.length === 1) - ? fixed_group[grouping_fields[0]] - : _.map(grouping_fields, function (field) { return fixed_group[field]; }); - var grouped_on = (grouping_fields.length === 1) - ? grouping_fields[0] - : grouping_fields; - this.attributes = { - folded: !!(fixed_group.__fold), - grouped_on: grouped_on, - // if terminal group (or no group) and group_by_no_leaf => use group.__count - length: group_size, - value: value, - // A group is open-able if it's not a leaf in group_by_no_leaf mode - has_children: !(leaf_group && fixed_group.__context.group_by_no_leaf), - - aggregates: aggregates - }; - }, - get: function (key) { - return this.attributes[key]; - }, - subgroups: function () { - return this.model.query().group_by(this.model.context().group_by); - }, - query: function () { - return this.model.query.apply(this.model, arguments); - } -}); - -var DataSet = Class.extend(mixins.PropertiesMixin, { - /** - * Collection of OpenERP records, used to share records and the current selection between views. - * - * @constructs instance.web.DataSet - * - * @param {String} model the OpenERP model this dataset will manage - */ - init: function (parent, model, context) { - mixins.PropertiesMixin.init.call(this); - this.setParent(parent); - this.model = model; - this.context = context || {}; - this.index = null; - this._sort = []; - this._model = new Model(model, context); - this.orderer = new concurrency.DropMisordered(); - }, - previous: function () { - this.index -= 1; - if (!this.ids.length) { - this.index = null; - } else if (this.index < 0) { - this.index = this.ids.length - 1; - } - return this; - }, - next: function () { - this.index += 1; - if (!this.ids.length) { - this.index = null; - } else if (this.index >= this.ids.length) { - this.index = 0; - } - return this; - }, - select_id: function (id) { - var idx = this.get_id_index(id); - if (idx === null) { - return false; - } else { - this.index = idx; - return true; - } - }, - get_id_index: function (id) { - for (var i=0, ii=this.ids.length; i= ids.length - 1) { - this.index = ids.length - 1; - } - }, - unlink: function (ids) { - this.set_ids(_.without.apply(null, [this.ids].concat(ids))); - this.trigger('unlink', ids); - return Promise.resolve({result: true}); - }, -}); - -var DataSetSearch = DataSet.extend({ - /** - * @constructs instance.web.DataSetSearch - * @extends instance.web.DataSet - * - * @param {Object} parent - * @param {String} model - * @param {Object} context - * @param {Array} domain - */ - init: function (parent, model, context, domain) { - this._super(parent, model, context); - this.domain = domain || []; - this._length = null; - this.ids = []; - this._model = new Model(model, context, domain); - }, - /** - * Read a slice of the records represented by this DataSet, based on its - * domain and context. - * - * @params {Object} options - * @param {Array} [options.fields] fields to read and return, by default all fields are returned - * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context - * @param {Array} [options.domain] domain data to add to the request payload, ANDed with the dataset's domain - * @param {Number} [options.offset=0] The index from which selected records should be returned - * @param {Number} [options.limit=null] The maximum number of records to return - * @returns {Promise} - */ - read_slice: function (fields, options) { - options = options || {}; - var self = this; - var q = this._model.query(fields || false) - .filter(options.domain) - .context(options.context) - .offset(options.offset || 0) - .limit(options.limit || false); - q = q.order_by.apply(q, this._sort); - - var prom = this.orderer.add(q.all()); - prom.then(function (records) { - // FIXME: not sure about that one, *could* have discarded count - q.count().then(function (count) { self._length = count; }); - self.ids = _(records).pluck('id'); - }); - return prom; - }, - get_domain: function (other_domain) { - return this._model.domain(other_domain); - }, - alter_ids: function (ids) { - this._super(ids); - if (this.index !== null && this.index >= this.ids.length) { - this.index = this.ids.length > 0 ? this.ids.length - 1 : 0; - } - }, - remove_ids: function (ids) { - var before = this.ids.length; - this._super(ids); - if (this._length) { - this._length -= (before - this.ids.length); - } - }, - add_ids: function (ids, at) { - var before = this.ids.length; - this._super(ids, at); - if(this._length){ - this._length += (this.ids.length - before); - } - }, - unlink: function (ids, callback, error_callback) { - var self = this; - var prom = this._super(ids); - prom.then(function () { - self.remove_ids( ids); - self.trigger("dataset_changed", ids, callback, error_callback); - }); - return prom; - }, - size: function () { - if (this._length !== null) { - return this._length; - } - return this._super(); - } -}); - -var data = { - Query: Query, - DataSet: DataSet, - DataSetStatic: DataSetStatic, - DataSetSearch: DataSetSearch, - /** @type String */ - noDisplayContent: "" + _t("Unnamed") + "", -}; - - -var Model = Class.extend({ - /** - new openerp.web.Model(name[, context[, domain]]) - - @constructs instance.web.Model - @extends instance.web.Class - - @param {String} name name of the OpenERP model this object is bound to - @param {Object} [context] - @param {Array} [domain] - */ - init: function(name, context, domain) { - this.name = name; - this._context = context || {}; - this._domain = domain || []; - }, - /** - * @deprecated does not allow to specify kwargs, directly use call() instead - */ - get_func: function (method_name) { - var self = this; - return function () { - return self.call(method_name, _.toArray(arguments)); - }; - }, - /** - * Fetches a Query instance bound to this model, for searching - * - * @param {Array} [fields] fields to ultimately fetch during the search - * @returns {instance.web.Query} - */ - query: function (fields) { - return new data.Query(this, fields); - }, - /** - * Fetches the model's domain, combined with the provided domain if any - * - * @param {Array} [domain] to combine with the model's internal domain - * @returns {Array} The model's internal domain, or the AND-ed union of the model's internal domain and the provided domain - */ - domain: function (domain) { - if (!domain) { return this._domain; } - return this._domain.concat(domain); - }, - /** - * Fetches the combination of the user's context and the domain context, - * combined with the provided context if any - * - * @param {Object} [context] to combine with the model's internal context - * @returns {web.Context} The union of the user's context and the model's internal context, as well as the provided context if any. In that order. - */ - context: function (context) { - return new Context(session.user_context, this._context, context || {}); - }, - /** - * Call a method (over RPC) on the bound OpenERP model. - * - * @param {String} method name of the method to call - * @param {Array} [args] positipyEvalonal arguments - * @param {Object} [kwargs] keyword arguments - * @param {Object} [options] additional options for the rpc() method - * @returns {Promise<>} call result - */ - call: function (method, args, kwargs, options) { - args = args || []; - kwargs = kwargs || {}; - if (!_.isArray(args)) { - // call(method, kwargs) - kwargs = args; - args = []; - } - pyUtils.ensure_evaluated(args, kwargs); - var call_kw = '/web/dataset/call_kw/' + this.name + '/' + method; - return session.rpc(call_kw, { - model: this.name, - method: method, - args: args, - kwargs: kwargs - }, options); - }, - call_button: function (method, args) { - pyUtils.ensure_evaluated(args, {}); - // context should be the last argument - var context = (args || []).length > 0 ? args.pop() : {}; - return session.rpc('/web/dataset/call_button', { - model: this.name, - method: method, - args: args || [], - kwargs: {context: context}, - }); - }, -}); - - -return data; - -}); diff --git a/addons/web/static/src/legacy/js/fields/relational_fields.js b/addons/web/static/src/legacy/js/fields/relational_fields.js index 3e13931f3df..dc0c82ac8e9 100644 --- a/addons/web/static/src/legacy/js/fields/relational_fields.js +++ b/addons/web/static/src/legacy/js/fields/relational_fields.js @@ -16,7 +16,6 @@ odoo.define('web.relational_fields', function (require) { var AbstractField = require('web.AbstractField'); var concurrency = require('web.concurrency'); var core = require('web.core'); -var data = require('web.data'); var Dialog = require('web.Dialog'); var dom = require('web.dom'); const { FormViewDialog } = require("@web/views/view_dialogs/form_view_dialog"); @@ -668,7 +667,7 @@ var FieldMany2One = AbstractField.extend({ result[1] = displayName; return { id, - label: escape(displayName) || data.noDisplayContent, + label: escape(displayName) || `${escape(_t("Unnamed"))}`, value: displayName, name: displayName, }; diff --git a/addons/web/static/src/legacy/js/widgets/data_export.js b/addons/web/static/src/legacy/js/widgets/data_export.js deleted file mode 100644 index 8f651933025..00000000000 --- a/addons/web/static/src/legacy/js/widgets/data_export.js +++ /dev/null @@ -1,694 +0,0 @@ -odoo.define('web.DataExport', function (require) { -"use strict"; - -var config = require('web.config'); -var core = require('web.core'); -var Dialog = require('web.Dialog'); -var data = require('web.data'); -var framework = require('web.framework'); -var pyUtils = require('web.py_utils'); - -var QWeb = core.qweb; -var _t = core._t; - -var DataExport = Dialog.extend({ - template: 'ExportDialog', - events: { - 'change .o_exported_lists_select': '_onChangeExportList', - 'change .o_import_compat input': '_onChangeCompatibleInput', - 'click .o_add_field': '_onClickAddField', - 'click .o_delete_exported_list': '_onClickDeleteExportListBtn', - 'click .o_expand': '_onClickExpand', - 'click .o_remove_field': '_onClickRemoveField', - 'click .o_save_list .o_save_list_btn': '_onClickSaveListBtn', - 'click .o_save_list .o_cancel_list_btn': '_resetTemplateField', - 'click .o_export_tree_item': '_onClickTreeItem', - 'dblclick .o_export_tree_item:not(.haschild)': '_onDblclickTreeItem', - 'keydown .o_export_tree_item': '_onKeydownTreeItem', - 'keydown .o_save_list_name': '_onKeydownSaveList', - 'input .o_export_search_input': '_onSearchInput', - }, - /** - * @constructor - * @param {Widget} parent - * @param {Object} record - * @param {string[]} defaultExportFields - */ - init: function (parent, record, defaultExportFields, groupedBy, activeDomain, idsToExport) { - var options = { - title: _t("Export Data"), - buttons: [ - {text: _t("Export"), click: this._onExportData, classes: 'btn-primary'}, - {text: _t("Close"), close: true}, - ], - fullscreen: config.device.isMobile, - }; - this._super(parent, options); - 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 - */ - start: function () { - var self = this; - var proms = [this._super.apply(this, arguments)]; - - // The default for the ".modal_content" element is "max-height: 100%;" - // but we want it to always expand to "height: 100%;" for this modal. - // This can be achieved thanks to CSS modification without touching - // the ".modal-content" rules... but not with Internet explorer (11). - this.$modal.find('.modal-content').css('height', '100%'); - - this.$fieldsList = this.$('.o_fields_list'); - - proms.push(this._rpc({route: '/web/export/formats'}).then(doSetupExportFormats)); - proms.push(this._onChangeCompatibleInput().then(function () { - _.each(self.defaultExportFields, function (field) { - var record = self.records[field]; - self._addField(record.id, record.string); - }); - })); - - proms.push(this._showExportsList()); - - // Bind sortable events after Dialog is open - this.opened().then(function () { - self.$('.o_fields_list').sortable({ - axis: 'y', - cursor: 'grabbing', - handle: '.o_short_field', - forcePlaceholderSize: true, - placeholder: 'o-field-placeholder bg-light border border-primary', - update: self.proxy('_resetTemplateField'), - }); - }); - return Promise.all(proms); - - function doSetupExportFormats(formats) { - var $fmts = self.$('.o_export_format'); - - _.each(formats, function (format) { - var $radio = $('', {type: 'radio', value: format.tag, name: 'o_export_format_name', class: 'form-check-input', id: 'o_radio' + format.label}); - var $label = $('