diff --git a/report_csv/README.rst b/report_csv/README.rst index bec910f9e..50e0916c6 100644 --- a/report_csv/README.rst +++ b/report_csv/README.rst @@ -41,7 +41,7 @@ A python class :: A report XML record :: - +* Laurent Mignon Maintainer ---------- diff --git a/report_csv/__manifest__.py b/report_csv/__manifest__.py index b41c7e238..4758cecc0 100644 --- a/report_csv/__manifest__.py +++ b/report_csv/__manifest__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2019 Creu Blanca # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { @@ -5,19 +6,18 @@ 'summary': "Base module to create csv report", 'author': 'Creu Blanca,' + 'ACSONE SA/NV,' 'Odoo Community Association (OCA)', 'website': "https://github.com/oca/reporting-engine", 'category': 'Reporting', - 'version': '11.0.1.0.0', + 'version': '10.0.1.0.0', 'license': 'AGPL-3', - 'external_dependencies': { - 'python': [ - 'csv', - ], - }, 'depends': [ - 'base', 'web', + 'report', ], + 'external_dependencies': { + 'python': ['unicodecsv'] + }, 'data': [ 'views/webclient_templates.xml', ], diff --git a/report_csv/controllers/main.py b/report_csv/controllers/main.py index f5c10980e..2b44a376f 100644 --- a/report_csv/controllers/main.py +++ b/report_csv/controllers/main.py @@ -1,21 +1,29 @@ +# -*- coding: utf-8 -*- # Copyright (C) 2019 Creu Blanca # License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). - -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 - import json import time +from werkzeug import url_decode + +from odoo.addons.report.controllers import main as report +from odoo.http import route, request +from odoo.tools.safe_eval import safe_eval +from odoo.addons.web.controllers.main import ( + _serialize_exception, + content_disposition +) +from odoo.tools import html_escape + class ReportController(report.ReportController): @route() def report_routes(self, reportname, docids=None, converter=None, **data): if converter == 'csv': - report = request.env['ir.actions.report']._get_report_from_name( + report = request.env['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'): @@ -28,6 +36,7 @@ class ReportController(report.ReportController): if data['context'].get('lang'): del data['context']['lang'] context.update(data['context']) + csv = report.with_context(context).render_csv( docids, data=data )[0] @@ -58,3 +67,43 @@ class ReportController(report.ReportController): return super(ReportController, self).report_routes( reportname, docids, converter, **data ) + + @route() + def report_download(self, data, token): + """This function is used by 'qwebactionmanager.js' in order to trigger + the download of a csv/controller report. + + :param data: a javascript array JSON.stringified containg report + internal url ([0]) and type [1] + :returns: Response with a filetoken cookie and an attachment header + """ + requestcontent = json.loads(data) + url, type = requestcontent[0], requestcontent[1] + if type != 'csv': + return super(ReportController, self).report_download(data, token) + try: + reportname = url.split('/report/csv/')[1].split('?')[0] + docids = None + if '/' in reportname: + reportname, docids = reportname.split('/') + + if docids: + # Generic report: + response = self.report_routes( + reportname, docids=docids, converter='csv') + else: + # Particular report: + # decoding the args represented in JSON + data = url_decode(url.split('?')[1]).items() + response = self.report_routes( + reportname, converter='csv', **dict(data)) + response.set_cookie('fileToken', token) + return response + except Exception, e: + se = _serialize_exception(e) + error = { + 'code': 200, + 'message': "Odoo Server Error", + 'data': se + } + return request.make_response(html_escape(json.dumps(error))) diff --git a/report_csv/models/__init__.py b/report_csv/models/__init__.py index 54dbf3df6..55ad3dff4 100644 --- a/report_csv/models/__init__.py +++ b/report_csv/models/__init__.py @@ -1 +1,2 @@ -from . import ir_report +from . import report +from . import ir_actions_report_xml diff --git a/report_csv/models/ir_report.py b/report_csv/models/ir_actions_report_xml.py similarity index 53% rename from report_csv/models/ir_report.py rename to report_csv/models/ir_actions_report_xml.py index 7259f9dd4..9c989f351 100644 --- a/report_csv/models/ir_report.py +++ b/report_csv/models/ir_actions_report_xml.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2019 Creu Blanca # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). @@ -5,8 +6,8 @@ from odoo import api, fields, models, _ from odoo.exceptions import UserError -class ReportAction(models.Model): - _inherit = 'ir.actions.report' +class IrActionsReportXml(models.Model): + _inherit = 'ir.actions.report.xml' report_type = fields.Selection(selection_add=[("csv", "csv")]) @@ -19,15 +20,3 @@ class ReportAction(models.Model): return report_model.with_context({ 'active_model': self.model }).create_csv_report(docids, data) - - @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 = ['csv'] - 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/report_csv/models/report.py b/report_csv/models/report.py new file mode 100644 index 000000000..fc8832875 --- /dev/null +++ b/report_csv/models/report.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Akretion (http://www.akretion.com/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class Report(models.Model): + + _inherit = 'report' + + @api.model + def _get_report_from_name(self, report_name): + """Get the first record of ir.actions.report.xml having the + ``report_name`` as value for the field report_name. + """ + res = super(Report, self)._get_report_from_name(report_name) + if res: + return res + report_obj = self.env['ir.actions.report.xml'] + qwebtypes = ['csv'] + 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/report_csv/report/report_csv.py b/report_csv/report/report_csv.py index fde1ccbca..c2d3ba738 100644 --- a/report_csv/report/report_csv.py +++ b/report_csv/report/report_csv.py @@ -1,17 +1,18 @@ +# -*- coding: utf-8 -*- # Copyright 2019 Creu Blanca # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from io import StringIO +import logging +from StringIO import StringIO from odoo import models -import logging -_logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) try: - import csv + import unicodecsv as csv except ImportError: - _logger.debug('Can not import csvwriter`.') + logger.debug('Cannot import unicodecsv') class ReportCSVAbstract(models.AbstractModel): diff --git a/report_csv/report/report_partner_csv.py b/report_csv/report/report_partner_csv.py index 1fe897a82..4d27b1212 100644 --- a/report_csv/report/report_partner_csv.py +++ b/report_csv/report/report_partner_csv.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2019 Creu Blanca # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). import csv @@ -17,7 +18,7 @@ class PartnerCSV(models.AbstractModel): }) def csv_report_options(self): - res = super().csv_report_options() + res = super(PartnerCSV, self).csv_report_options() res['fieldnames'].append('name') res['fieldnames'].append('email') res['delimiter'] = ';' diff --git a/report_csv/static/src/js/report/qwebactionmanager.js b/report_csv/static/src/js/report/qwebactionmanager.js index 2f3c294ee..02e1d734f 100644 --- a/report_csv/static/src/js/report/qwebactionmanager.js +++ b/report_csv/static/src/js/report/qwebactionmanager.js @@ -7,41 +7,48 @@ var ActionManager= require('web.ActionManager'); var crash_manager = require('web.crash_manager'); var framework = require('web.framework'); +var trigger_download = function(session, response, c, action, options) { + session.get_file({ + url: '/report/download', + data: {data: JSON.stringify(response)}, + complete: framework.unblockUI, + error: c.rpc_error.bind(c), + success: function(){ + if (action && options && !action.dialog) { + options.on_close(); + } + }, + }); +}; + ActionManager.include({ - ir_actions_report: function (action, options){ + ir_actions_report_xml: function(action, options) { var self = this; - var cloned_action = _.clone(action); - if (cloned_action.report_type === 'csv') { + + // Py3o reports + if ('report_type' in action && action.report_type == 'csv' ) { framework.blockUI(); - var report_csv_url = 'report/csv/' + cloned_action.report_name; - if (_.isUndefined(cloned_action.data) || - _.isNull(cloned_action.data) || - (_.isObject(cloned_action.data) && _.isEmpty(cloned_action.data))) - { - if(cloned_action.context.active_ids) { - report_csv_url += '/' + cloned_action.context.active_ids.join(','); + action = _.clone(action); + var report_url = '/report/csv/' + action.report_name;; + // generic report: no query string + // particular: query string of action.data.form and context + if (!('data' in action) || !(action.data)) { + if ('active_ids' in action.context) { + report_url += "/" + action.context.active_ids.join(','); } } else { - report_csv_url += '?options=' + encodeURIComponent(JSON.stringify(cloned_action.data)); - report_csv_url += '&context=' + encodeURIComponent(JSON.stringify(cloned_action.context)); + report_url += "&options=" + encodeURIComponent(JSON.stringify(action.data)); + report_url += "&context=" + encodeURIComponent(JSON.stringify(action.context)); } - self.getSession().get_file({ - url: report_csv_url, - data: {data: JSON.stringify([ - report_csv_url, - cloned_action.report_type - ])}, - error: crash_manager.rpc_error.bind(crash_manager), - success: function (){ - if(cloned_action && options && !cloned_action.dialog){ - options.on_close(); - } - } - }); - framework.unblockUI(); - return; + + var response = new Array(); + response[0] = report_url; + response[1] = action.report_type; + var c = crash_manager; + return trigger_download(self.session, response, c, action, options); + } else { + return self._super(action, options); } - return self._super(action, options); } }); }); diff --git a/report_csv/tests/test_report.py b/report_csv/tests/test_report.py index 0bd53e8a4..b510b69ed 100644 --- a/report_csv/tests/test_report.py +++ b/report_csv/tests/test_report.py @@ -1,19 +1,15 @@ +# -*- coding: utf-8 -*- # Copyright 2019 Creu Blanca # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from io import StringIO +import csv +from StringIO import StringIO from odoo.tests import common -import logging -_logger = logging.getLogger(__name__) -try: - import csv -except ImportError: - _logger.debug('Can not import csv.') class TestReport(common.TransactionCase): def setUp(self): - super().setUp() - report_object = self.env['ir.actions.report'] + super(TestReport, self,).setUp() + report_object = self.env['report'] self.csv_report = ( self.env['report.report_csv.abstract'] .with_context(active_model='res.partner') @@ -25,7 +21,7 @@ class TestReport(common.TransactionCase): def test_report(self): report = self.report self.assertEqual(report.report_type, 'csv') - rep = report.render(self.docs.ids, {}) + rep = report.render_csv(self.docs.ids, {}) str_io = StringIO(rep[0]) dict_report = list(csv.DictReader(str_io, delimiter=';', quoting=csv.QUOTE_ALL)) diff --git a/requirements.txt b/requirements.txt index c454c21f8..02d8235cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ xlwt xlsxwriter py3o.template py3o.formats +unicodecsv