[FIX] report_xlsx: refactor controller and report hamdler

pull/690/head
Christopher Ormaza 2021-12-07 14:07:53 -05:00 committed by Rodrigo
parent aec96ed82c
commit 087cfe0021
6 changed files with 128 additions and 121 deletions

View File

@ -109,6 +109,7 @@ Contributors
* Cristian Salamea <cs@prisehub.com> * Cristian Salamea <cs@prisehub.com>
* Rod Schouteden <rod.schouteden@dynapps.be> * Rod Schouteden <rod.schouteden@dynapps.be>
* Eugene Molotov <molotov@it-projects.info> * Eugene Molotov <molotov@it-projects.info>
* Christopher Ormaza <chris.ormaza@forgeflow.com>
Maintainers Maintainers
~~~~~~~~~~~ ~~~~~~~~~~~

View File

@ -1,26 +1,29 @@
# Copyright (C) 2017 Creu Blanca # Copyright (C) 2017 Creu Blanca
# Copyright 2021 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
import json import json
import logging
from odoo.http import content_disposition, request, route, serialize_exception from werkzeug.urls import url_decode
from odoo.http import (
content_disposition,
request,
route,
serialize_exception as _serialize_exception,
)
from odoo.tools import html_escape from odoo.tools import html_escape
from odoo.tools.safe_eval import safe_eval from odoo.tools.safe_eval import safe_eval, time
from odoo.addons.web.controllers import main as report from odoo.addons.web.controllers import main as report
_logger = logging.getLogger(__name__)
class ReportController(report.ReportController): class ReportController(report.ReportController):
@route() @route()
def report_routes(self, reportname, docids=None, converter=None, **data): def report_routes(self, reportname, docids=None, converter=None, **data):
if converter == "xlsx":
return self._report_routes_xlsx(reportname, docids, converter, **data)
return super(ReportController, self).report_routes(
reportname, docids, converter, **data
)
def _report_routes_xlsx(self, reportname, docids=None, converter=None, **data):
try:
report = request.env["ir.actions.report"]._get_report_from_name(reportname) report = request.env["ir.actions.report"]._get_report_from_name(reportname)
context = dict(request.env.context) context = dict(request.env.context)
if docids: if docids:
@ -28,18 +31,10 @@ class ReportController(report.ReportController):
if data.get("options"): if data.get("options"):
data.update(json.loads(data.pop("options"))) data.update(json.loads(data.pop("options")))
if data.get("context"): 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"]) data["context"] = json.loads(data["context"])
if data["context"].get("lang"):
del data["context"]["lang"]
context.update(data["context"]) context.update(data["context"])
if converter == "xlsx":
xlsx = report.with_context(**context)._render_xlsx(docids, data=data)[0] xlsx = report.with_context(**context)._render_xlsx(docids, data=data)[0]
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})
xlsxhttpheaders = [ xlsxhttpheaders = [
( (
"Content-Type", "Content-Type",
@ -47,10 +42,62 @@ class ReportController(report.ReportController):
"officedocument.spreadsheetml.sheet", "officedocument.spreadsheetml.sheet",
), ),
("Content-Length", len(xlsx)), ("Content-Length", len(xlsx)),
("Content-Disposition", content_disposition(report_name + ".xlsx")),
] ]
return request.make_response(xlsx, headers=xlsxhttpheaders) return request.make_response(xlsx, headers=xlsxhttpheaders)
return super(ReportController, self).report_routes(
reportname, docids, converter, **data
)
@route()
def report_download(self, data, context=None):
requestcontent = json.loads(data)
url, report_type = requestcontent[0], requestcontent[1]
try:
if report_type == "xlsx":
reportname = url.split("/report/xlsx/")[1].split("?")[0]
docids = None
if "/" in reportname:
reportname, docids = reportname.split("/")
if docids:
# Generic report:
response = self.report_routes(
reportname, docids=docids, converter="xlsx", context=context
)
else:
# Particular report:
data = dict(
url_decode(url.split("?")[1]).items()
) # decoding the args represented in JSON
if "context" in data:
context, data_context = json.loads(context or "{}"), json.loads(
data.pop("context")
)
context = json.dumps({**context, **data_context})
response = self.report_routes(
reportname, converter="xlsx", context=context, **data
)
report = request.env["ir.actions.report"]._get_report_from_name(
reportname
)
filename = "%s.%s" % (report.name, "xlsx")
if docids:
ids = [int(x) for x in docids.split(",")]
obj = request.env[report.model].browse(ids)
if report.print_report_name and not len(obj) > 1:
report_name = safe_eval(
report.print_report_name, {"object": obj, "time": time}
)
filename = "%s.%s" % (report_name, "xlsx")
response.headers.add(
"Content-Disposition", content_disposition(filename)
)
return response
else:
return super(ReportController, self).report_download(data, context)
except Exception as e: except Exception as e:
se = serialize_exception(e) _logger.exception("Error while generating report %s", reportname)
se = _serialize_exception(e)
error = {"code": 200, "message": "Odoo Server Error", "data": se} error = {"code": 200, "message": "Odoo Server Error", "data": se}
return request.make_response(html_escape(json.dumps(error))) return request.make_response(html_escape(json.dumps(error)))

View File

@ -6,3 +6,4 @@
* Cristian Salamea <cs@prisehub.com> * Cristian Salamea <cs@prisehub.com>
* Rod Schouteden <rod.schouteden@dynapps.be> * Rod Schouteden <rod.schouteden@dynapps.be>
* Eugene Molotov <molotov@it-projects.info> * Eugene Molotov <molotov@it-projects.info>
* Christopher Ormaza <chris.ormaza@forgeflow.com>

View File

@ -47,7 +47,7 @@ try:
# Only up to 100 duplicates # Only up to 100 duplicates
deduplicated_secuence = "~{:02d}".format(duplicated_secuence + 1) deduplicated_secuence = "~{:02d}".format(duplicated_secuence + 1)
if duplicated_secuence > 99: if duplicated_secuence > 99:
raise xlsxwriter.exceptions.DuplicateWorksheetName raise xlsxwriter.exceptions.DuplicateWorksheetName # noqa: B904
if duplicated_secuence: if duplicated_secuence:
sheetname = re.sub(pattern, deduplicated_secuence, sheetname) sheetname = re.sub(pattern, deduplicated_secuence, sheetname)
elif len(sheetname) <= 28: elif len(sheetname) <= 28:

View File

@ -1,95 +1,53 @@
/** @odoo-module **/ /** @odoo-module **/
import {download} from "@web/core/network/download"; import {download} from "@web/core/network/download";
import {registry} from "@web/core/registry"; import {registry} from "@web/core/registry";
import core from "web.core"; registry
import framework from "web.framework"; .category("ir.actions.report handlers")
import session from "web.session"; .add("xlsx_handler", async function (action, options, env) {
var _t = core._t;
async function _downloadReportXLSX(url, actions) {
var self = this;
framework.blockUI();
var type = "xlsx";
var cloned_action = _.clone(actions);
var new_url = url;
if (
_.isUndefined(cloned_action.data) ||
_.isNull(cloned_action.data) ||
(_.isObject(cloned_action.data) && _.isEmpty(cloned_action.data))
) {
if (cloned_action.context.active_ids) {
new_url += "/" + cloned_action.context.active_ids.join(",");
}
} else {
new_url +=
"?options=" +
encodeURIComponent(JSON.stringify(cloned_action.data));
new_url +=
"&context=" +
encodeURIComponent(JSON.stringify(cloned_action.context));
}
return new Promise(function (resolve, reject) {
var blocked = !session.get_file({
url: new_url,
data: {
data: JSON.stringify([new_url, type]),
},
success: resolve,
error: (error) => {
self.call("crash_manager", "rpc_error", error);
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);
}
});
}
async function _triggerDownload (action, options, type) {
var self = this;
var reportUrls = this._makeReportUrls(action);
if (type === "xlsx") {
return this._downloadReportXLSX(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")
);
}
return options.on_close();
}
);
}
return this._super.apply(this, arguments);
}
async function _makeReportUrls (action) {
var reportUrls = this._super.apply(this, arguments);
reportUrls.xlsx = "/report/xlsx/" + action.report_name;
return reportUrls;
}
async function _executeReportAction (action, options) {
var self = this;
if (action.report_type === "xlsx") { if (action.report_type === "xlsx") {
return self._triggerDownload(action, options, "xlsx"); const type = action.report_type;
let url = `/report/${type}/${action.report_name}`;
const actionContext = action.context || {};
if (action.data && JSON.stringify(action.data) !== "{}") {
// Build a query string with `action.data` (it's the place where reports
// using a wizard to customize the output traditionally put their options)
const action_options = encodeURIComponent(JSON.stringify(action.data));
const context = encodeURIComponent(JSON.stringify(actionContext));
url += `?options=${action_options}&context=${context}`;
} else {
if (actionContext.active_ids) {
url += `/${actionContext.active_ids.join(",")}`;
} }
return this._super.apply(this, arguments); if (type === "xlsx") {
const context = encodeURIComponent(
JSON.stringify(env.services.user.context)
);
url += `?context=${context}`;
} }
}
env.services.ui.block();
try {
await download({
url: "/report/download",
data: {
data: JSON.stringify([url, action.report_type]),
context: JSON.stringify(env.services.user.context),
},
});
} finally {
env.services.ui.unblock();
}
const onClose = options.onClose;
if (action.close_on_report_download) {
return env.services.action.doAction(
{type: "ir.actions.act_window_close"},
{onClose}
);
} else if (onClose) {
onClose();
}
}
return Promise.resolve(true);
});