From a66a12c957d8e9078411c26e3d5c364523615c87 Mon Sep 17 00:00:00 2001 From: Kitti U Date: Fri, 9 Aug 2019 12:27:10 +0700 Subject: [PATCH 1/4] [12.0][IMP] excel_import_export, excel_import_export_demo Add report action feature and new examples --- excel_import_export/__init__.py | 1 + excel_import_export/__manifest__.py | 9 +- excel_import_export/controllers/__init__.py | 4 + excel_import_export/controllers/main.py | 53 +++++++++++ excel_import_export/models/__init__.py | 1 + excel_import_export/models/common.py | 42 ++++----- excel_import_export/models/ir_report.py | 39 ++++++++ excel_import_export/readme/HISTORY.rst | 5 + excel_import_export/readme/USAGE.rst | 20 ++++ .../src/js/report/action_manager_report.js | 88 ++++++++++++++++++ .../views/webclient_templates.xml | 11 +++ excel_import_export_demo/__init__.py | 1 + excel_import_export_demo/__manifest__.py | 8 +- .../readme/DESCRIPTION.rst | 2 + excel_import_export_demo/readme/HISTORY.rst | 5 + excel_import_export_demo/readme/USAGE.rst | 14 ++- .../report_action/__init__.py | 4 + .../report_action/partner_list/__init__.py | 4 + .../partner_list/partner_list.xlsx | Bin 0 -> 5259 bytes .../report_action/partner_list/report.xml | 10 ++ .../partner_list/report_partner_list.py | 37 ++++++++ .../partner_list/report_partner_list.xml | 43 +++++++++ .../report_action/partner_list/templates.xml | 29 ++++++ .../report_action/sale_order/report.xml | 13 +++ .../sale_order/sale_order_form.xlsx | Bin 0 -> 5369 bytes .../report_action/sale_order/templates.xml | 36 +++++++ 26 files changed, 450 insertions(+), 29 deletions(-) create mode 100644 excel_import_export/controllers/__init__.py create mode 100644 excel_import_export/controllers/main.py create mode 100644 excel_import_export/models/ir_report.py create mode 100644 excel_import_export/static/src/js/report/action_manager_report.js create mode 100644 excel_import_export/views/webclient_templates.xml create mode 100644 excel_import_export_demo/report_action/__init__.py create mode 100644 excel_import_export_demo/report_action/partner_list/__init__.py create mode 100644 excel_import_export_demo/report_action/partner_list/partner_list.xlsx create mode 100644 excel_import_export_demo/report_action/partner_list/report.xml create mode 100644 excel_import_export_demo/report_action/partner_list/report_partner_list.py create mode 100644 excel_import_export_demo/report_action/partner_list/report_partner_list.xml create mode 100644 excel_import_export_demo/report_action/partner_list/templates.xml create mode 100644 excel_import_export_demo/report_action/sale_order/report.xml create mode 100644 excel_import_export_demo/report_action/sale_order/sale_order_form.xlsx create mode 100644 excel_import_export_demo/report_action/sale_order/templates.xml diff --git a/excel_import_export/__init__.py b/excel_import_export/__init__.py index 673c49964..322f82834 100644 --- a/excel_import_export/__init__.py +++ b/excel_import_export/__init__.py @@ -3,3 +3,4 @@ from . import wizard from . import models +from . import controllers diff --git a/excel_import_export/__manifest__.py b/excel_import_export/__manifest__.py index e82589091..60eb409d4 100644 --- a/excel_import_export/__manifest__.py +++ b/excel_import_export/__manifest__.py @@ -2,9 +2,9 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) { - 'name': 'Excel Import/Export', - 'summary': 'Base module for easy way to develop Excel import/export', - 'version': '12.0.1.0.1', + 'name': 'Excel Import/Export/Report', + 'summary': 'Base module for developing Excel import/export/report', + 'version': '12.0.1.0.2', 'author': 'Ecosoft,Odoo Community Association (OCA)', 'license': 'AGPL-3', 'website': 'https://github.com/OCA/server-tools/', @@ -22,8 +22,9 @@ 'wizard/import_xlsx_wizard.xml', 'views/xlsx_template_view.xml', 'views/xlsx_report.xml', + 'views/webclient_templates.xml', ], 'installable': True, - 'development_status': 'alpha', + 'development_status': 'beta', 'maintainers': ['kittiu'], } diff --git a/excel_import_export/controllers/__init__.py b/excel_import_export/controllers/__init__.py new file mode 100644 index 000000000..21a1de321 --- /dev/null +++ b/excel_import_export/controllers/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from . import main diff --git a/excel_import_export/controllers/main.py b/excel_import_export/controllers/main.py new file mode 100644 index 000000000..761082724 --- /dev/null +++ b/excel_import_export/controllers/main.py @@ -0,0 +1,53 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +import json +import base64 +import time +from odoo.addons.web.controllers import main as report +from odoo.http import content_disposition, route, request +from odoo.tools.safe_eval import safe_eval + + +class ReportController(report.ReportController): + + @route() + def report_routes(self, reportname, docids=None, converter=None, **data): + if converter == 'excel': + report = request.env['ir.actions.report']._get_report_from_name( + reportname) + context = dict(request.env.context) + if docids: + docids = [int(i) for i in docids.split(',')] + if data.get('options'): + data.update(json.loads(data.pop('options'))) + if data.get('context'): + # Ignore 'lang' here, because the context in data is the one + # from the webclient *but* if the user explicitely wants to + # change the lang, this mechanism overwrites it. + data['context'] = json.loads(data['context']) + if data['context'].get('lang'): + del data['context']['lang'] + context.update(data['context']) + excel = report.with_context(context).render_excel( + docids, data=data + )[0] + excel = base64.decodestring(excel) + report_name = report.report_file + if report.print_report_name and not len(docids) > 1: + obj = request.env[report.model].browse(docids[0]) + report_name = safe_eval(report.print_report_name, + {'object': obj, 'time': time}) + excelhttpheaders = [ + ('Content-Type', 'application/vnd.openxmlformats-' + 'officedocument.spreadsheetml.sheet'), + ('Content-Length', len(excel)), + ( + 'Content-Disposition', + content_disposition(report_name + '.xlsx') + ) + ] + return request.make_response(excel, headers=excelhttpheaders) + return super(ReportController, self).report_routes( + reportname, docids, converter, **data + ) diff --git a/excel_import_export/models/__init__.py b/excel_import_export/models/__init__.py index 6f262fca5..52501c936 100644 --- a/excel_import_export/models/__init__.py +++ b/excel_import_export/models/__init__.py @@ -6,3 +6,4 @@ from . import xlsx_export from . import xlsx_import from . import xlsx_template from . import xlsx_report +from . import ir_report diff --git a/excel_import_export/models/common.py b/excel_import_export/models/common.py index 51c2572a3..3f5fc50fe 100644 --- a/excel_import_export/models/common.py +++ b/excel_import_export/models/common.py @@ -180,28 +180,28 @@ def xlrd_get_sheet_by_name(book, name): raise ValidationError(_("'%s' sheet not found") % (name,)) -def isfloat(input): +def isfloat(input_val): try: - float(input) + float(input_val) return True except ValueError: return False -def isinteger(input): +def isinteger(input_val): try: - int(input) + int(input_val) return True except ValueError: return False -def isdatetime(input): +def isdatetime(input_val): try: - if len(input) == 10: - dt.strptime(input, '%Y-%m-%d') - elif len(input) == 19: - dt.strptime(input, '%Y-%m-%d %H:%M:%S') + if len(input_val) == 10: + dt.strptime(input_val, '%Y-%m-%d') + elif len(input_val) == 19: + dt.strptime(input_val, '%Y-%m-%d %H:%M:%S') else: return False return True @@ -209,18 +209,18 @@ def isdatetime(input): return False -def str_to_number(input): - if isinstance(input, str): - if ' ' not in input: - if isdatetime(input): - return parse(input) - elif isinteger(input): - if not (len(input) > 1 and input[:1] == '0'): - return int(input) - elif isfloat(input): - if not (input.find(".") > 2 and input[:1] == '0'): # 00.123 - return float(input) - return input +def str_to_number(input_val): + if isinstance(input_val, str): + if ' ' not in input_val: + if isdatetime(input_val): + return parse(input_val) + elif isinteger(input_val): + if not (len(input_val) > 1 and input_val[:1] == '0'): + return int(input_val) + elif isfloat(input_val): + if not (input_val.find(".") > 2 and input_val[:1] == '0'): + return float(input_val) + return input_val def csv_from_excel(excel_content, delimiter, quote): diff --git a/excel_import_export/models/ir_report.py b/excel_import_export/models/ir_report.py new file mode 100644 index 000000000..656096f9c --- /dev/null +++ b/excel_import_export/models/ir_report.py @@ -0,0 +1,39 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + + +class ReportAction(models.Model): + _inherit = "ir.actions.report" + + report_type = fields.Selection(selection_add=[("excel", "Excel")]) + + @api.model + def render_excel(self, docids, data): + if len(docids) != 1: + raise UserError( + _('Only one id is allowed for excel_import_export')) + xlsx_template = self.env['xlsx.template'].search( + [('fname', '=', self.report_name), ('res_model', '=', self.model)]) + if not xlsx_template or len(xlsx_template) != 1: + raise UserError( + _("Template %s on model %s is not unique!" % + (self.report_name, self.model))) + Export = self.env['xlsx.export'] + return Export.export_xlsx(xlsx_template, self.model, docids[0]) + + @api.model + def _get_report_from_name(self, report_name): + res = super(ReportAction, self)._get_report_from_name(report_name) + if res: + return res + report_obj = self.env['ir.actions.report'] + qwebtypes = ['excel'] + conditions = [ + ('report_type', 'in', qwebtypes), + ('report_name', '=', report_name), + ] + context = self.env['res.users'].context_get() + return report_obj.with_context(context).search(conditions, limit=1) diff --git a/excel_import_export/readme/HISTORY.rst b/excel_import_export/readme/HISTORY.rst index 58b5c00d2..d54c21cdf 100644 --- a/excel_import_export/readme/HISTORY.rst +++ b/excel_import_export/readme/HISTORY.rst @@ -1,3 +1,8 @@ +12.0.1.0.3 (2019-08-09) +~~~~~~~~~~~~~~~~~~~~~~~ + +* Add report action for report_type = 'excel' + 12.0.1.0.2 (2019-08-07) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/excel_import_export/readme/USAGE.rst b/excel_import_export/readme/USAGE.rst index 297f93e3c..ef4a1f291 100644 --- a/excel_import_export/readme/USAGE.rst +++ b/excel_import_export/readme/USAGE.rst @@ -39,3 +39,23 @@ This create report menu with criteria wizard. (example - excel_import_export_dem 3. Create report model as models.Transient, then define search criteria fields, and get reporing data into ``results`` field -- .py 4. Create/Design Excel Template File (.xlsx), in the template, name the underlining tab used for report results -- .xlsx 5. Create instruction dictionary for report in xlsx.template model -- templates.xml + +**Note:** + +Another option for reporting is to use report action (report_type='excel'), I.e., + +.. code-block:: xml + + + +By using report action, Odoo will find template using combination of model and name, then do the export for the underlining record. +Please see example in excel_import_export_demo/report_action, which shows, + +1. Print excel from an active sale.order +2. Run partner list report based on search criteria. diff --git a/excel_import_export/static/src/js/report/action_manager_report.js b/excel_import_export/static/src/js/report/action_manager_report.js new file mode 100644 index 000000000..303df50a0 --- /dev/null +++ b/excel_import_export/static/src/js/report/action_manager_report.js @@ -0,0 +1,88 @@ +// Copyright 2019 Ecosoft Co., Ltd. +// License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). +odoo.define("excel_import_export.report", function (require) { + "use strict"; + + var core = require("web.core"); + var ActionManager = require("web.ActionManager"); + var crash_manager = require("web.crash_manager"); + var framework = require("web.framework"); + var session = require("web.session"); + var _t = core._t; + + ActionManager.include({ + + _downloadReportExcel: function (url, actions) { + framework.blockUI(); + var def = $.Deferred(); + var type = "excel"; + var cloned_action = _.clone(actions); + + if (_.isUndefined(cloned_action.data) || + _.isNull(cloned_action.data) || + (_.isObject(cloned_action.data) && _.isEmpty(cloned_action.data))) + { + if (cloned_action.context.active_ids) { + url += "/" + cloned_action.context.active_ids.join(','); + } + } else { + url += "?options=" + encodeURIComponent(JSON.stringify(cloned_action.data)); + url += "&context=" + encodeURIComponent(JSON.stringify(cloned_action.context)); + } + + var blocked = !session.get_file({ + url: url, + data: { + data: JSON.stringify([url, type]), + }, + success: def.resolve.bind(def), + error: function () { + crash_manager.rpc_error.apply(crash_manager, arguments); + def.reject(); + }, + complete: framework.unblockUI, + }); + if (blocked) { + // AAB: this check should be done in get_file service directly, + // should not be the concern of the caller (and that way, get_file + // could return a deferred) + var message = _t('A popup window with your report was blocked. You ' + + 'may need to change your browser settings to allow ' + + 'popup windows for this page.'); + this.do_warn(_t('Warning'), message, true); + } + return def; + }, + + _triggerDownload: function (action, options, type) { + var self = this; + var reportUrls = this._makeReportUrls(action); + if (type === "excel") { + return this._downloadReportExcel(reportUrls[type], action).then(function () { + if (action.close_on_report_download) { + var closeAction = {type: 'ir.actions.act_window_close'}; + return self.doAction(closeAction, _.pick(options, 'on_close')); + } else { + return options.on_close(); + } + }); + } + return this._super.apply(this, arguments); + }, + + _makeReportUrls: function (action) { + var reportUrls = this._super.apply(this, arguments); + reportUrls.excel = '/report/excel/' + action.report_name; + return reportUrls; + }, + + _executeReportAction: function (action, options) { + var self = this; + if (action.report_type === 'excel') { + return self._triggerDownload(action, options, 'excel'); + } + return this._super.apply(this, arguments); + } + }); + +}); diff --git a/excel_import_export/views/webclient_templates.xml b/excel_import_export/views/webclient_templates.xml new file mode 100644 index 000000000..96cdbdb22 --- /dev/null +++ b/excel_import_export/views/webclient_templates.xml @@ -0,0 +1,11 @@ + + + +