[ADD] report_carbone, jsonifier, export_json : carbone is an alternative to Py3o
This commit is contained in:
134
report_carbone/controllers/main.py
Normal file
134
report_carbone/controllers/main.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
|
||||
import requests
|
||||
import werkzeug.exceptions
|
||||
from werkzeug.urls import url_decode
|
||||
|
||||
from odoo import _, api, exceptions, http
|
||||
from odoo.http import content_disposition, request
|
||||
from odoo.tools import html_escape
|
||||
|
||||
from odoo.addons.web.controllers.report import ReportController
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_headers(extension, content, filename):
|
||||
if extension == "zip":
|
||||
mime_type = "application/zip"
|
||||
else:
|
||||
mime_type, _ = mimetypes.guess_type(f"file.{extension}")
|
||||
if not mime_type:
|
||||
mime_type = "application/octet-stream" # generic fallback
|
||||
header = [
|
||||
("Content-Type", mime_type),
|
||||
("Content-Length", len(content)),
|
||||
("X-Content-Type-Options", "nosniff"),
|
||||
]
|
||||
if filename:
|
||||
header.append(("Content-Disposition", content_disposition(filename)))
|
||||
return header
|
||||
|
||||
|
||||
class CarboneReportController(ReportController):
|
||||
@classmethod
|
||||
def handle_response(cls, response: requests.Response, raise_exception=True) -> str | dict:
|
||||
try:
|
||||
response_json = response.json()
|
||||
except ValueError:
|
||||
response_json = {}
|
||||
try:
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
response_error_message = response_json.get("message", response.text) if response_json else response.text
|
||||
error_message = f"Carbone API Error: {response.status_code}: {response_error_message}"
|
||||
_logger.error("Carbone API Error %s: ", response.status_code, exc_info=e)
|
||||
if raise_exception:
|
||||
raise exceptions.UserError(error_message) from e
|
||||
return error_message
|
||||
return response_json
|
||||
|
||||
@api.model
|
||||
def check_carbone_report(self, carbone_report: "odoo.model.ir_actions_report"):
|
||||
if not carbone_report:
|
||||
raise exceptions.UserError(_("No report with this name and Carbone type was found."))
|
||||
if not carbone_report.name:
|
||||
raise exceptions.UserError(_("Please add a name to the report in order to export it."))
|
||||
|
||||
@api.model
|
||||
def handle_exception_error(self, e, reportname: str):
|
||||
_logger.exception("Error while generating report %s", reportname)
|
||||
se = http.serialize_exception(e)
|
||||
error = {"code": 200, "message": "Odoo Server Error", "data": se}
|
||||
res = request.make_response(html_escape(json.dumps(error)))
|
||||
raise werkzeug.exceptions.InternalServerError(response=res) from e
|
||||
|
||||
@http.route(
|
||||
[
|
||||
"/report/<converter>/<reportname>",
|
||||
"/report/<converter>/<reportname>/<docids>",
|
||||
],
|
||||
type="http",
|
||||
auth="user",
|
||||
website=True,
|
||||
readonly=True,
|
||||
)
|
||||
def report_routes(self, reportname: str, docids: str | None = None, converter=None, **data):
|
||||
if converter != "carbone":
|
||||
return super().report_routes(reportname, docids, converter, **data)
|
||||
context = dict(request.env.context)
|
||||
context.update({"from_ir_report_controller": True})
|
||||
if data.get("options"):
|
||||
data.update(json.loads(data.pop("options")))
|
||||
if data.get("context"):
|
||||
context.update(json.loads(data["context"]))
|
||||
request.update_context(**context)
|
||||
|
||||
# Retrieval of ir.actions.report
|
||||
carbone_report = request.env["ir.actions.report"]._get_report_from_name(reportname)
|
||||
self.check_carbone_report(carbone_report)
|
||||
|
||||
report_content, extension = request.env["ir.actions.report"]._render_carbone(carbone_report, docids)
|
||||
filename = f"{carbone_report.report_name}.{extension}"
|
||||
headers = _get_headers(extension, report_content, filename)
|
||||
return request.make_response(report_content, headers)
|
||||
|
||||
def _call_carbone_converter(self, docids: str, reportname: str, context: str, url: "str"):
|
||||
if docids:
|
||||
# Generic report:
|
||||
response = self.report_routes(reportname, docids=docids, converter="carbone", 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="carbone", context=context, **data)
|
||||
return response
|
||||
|
||||
@http.route(["/report/download"], type="http", auth="user")
|
||||
def report_download(self, data, context=None):
|
||||
"""
|
||||
Overload of the function to handle ir.actions.reports of "Carbone" type
|
||||
:param data: data: a javascript array JSON.stringified contain report internal url ([0]) and
|
||||
type [1]
|
||||
:param context:
|
||||
:return: Response with an attachment header
|
||||
"""
|
||||
requestcontent = json.loads(data)
|
||||
url, report_type = requestcontent[0], requestcontent[1]
|
||||
if report_type != "carbone":
|
||||
return super().report_download(data, context)
|
||||
reportname = url
|
||||
try:
|
||||
reportname = url.split("/report/carbone/")[1].split("?")[0]
|
||||
docids = None
|
||||
if "/" in reportname:
|
||||
reportname, docids = reportname.split("/")
|
||||
|
||||
response = self._call_carbone_converter(docids, reportname, context, url)
|
||||
return response
|
||||
except Exception as e:
|
||||
self.handle_exception_error(e, reportname)
|
||||
Reference in New Issue
Block a user