import base64 import logging import time from odoo import _, api, fields, models from odoo.exceptions import AccessError, ValidationError from odoo.tools.safe_eval import safe_eval from odoo.addons.redner.utils.formats import Formats _logger = logging.getLogger(__name__) class IrActionsReport(models.Model): """ Inherit from ir.action.report to allow customizing the template file. """ _inherit = "ir.actions.report" @api.constrains("redner_filetype", "report_type") def _check_redner_filetype(self): for report in self: if report.report_type == "redner" and not report.redner_filetype: raise ValidationError( _("Field 'Output Format' is required for Redner report") ) @api.constrains("redner_filetype", "attachment") def _check_redner_attachment(self): for report in self: if report.report_type == "redner" and report.attachment: ext = ".{ftype}".format(ftype=report.redner_filetype) if ext not in report.attachment: raise ValidationError( _( "The output format cannot be different from the " 'extension defined in "Save as attachment prefix".' ) ) @api.model def _get_redner_filetypes(self): formats = Formats() names = formats.get_known_format_names() selections = [] for name in names: description = name if formats.get_format(name).native: description = description + " " + _("(Native)") selections.append((name, description)) return selections report_type = fields.Selection(selection_add=[("redner", "redner")]) redner_multi_in_one = fields.Boolean( string="Multiple Records in a Single Redner Report", help="If you execute a report on several records, " "by default Odoo will generate a ZIP file that contains as many " "files as selected records. If you enable this option, Odoo will " "generate instead a single report for the selected records.", ) redner_filetype = fields.Selection( selection="_get_redner_filetypes", string="Redner Output Format" ) is_redner_native_format = fields.Boolean( compute="_compute_is_redner_native_format" ) redner_tmpl_id = fields.Many2one( comodel_name="redner.template", string="Redner template", domain=[("active", "=", True)], ) substitution_ids = fields.One2many( comodel_name="redner.substitution", inverse_name="ir_actions_report_id", string="Substitution", ) @api.depends("report_type", "redner_filetype") def _compute_is_redner_native_format(self): fmt = Formats() for rec in self: rec.is_redner_native_format = False if not rec.report_type == "redner" or not rec.redner_filetype: continue filetype = rec.redner_filetype rec.is_redner_native_format = fmt.get_format(filetype).native def action_get_substitutions(self): """Call by: action button `Get Substitutions from Redner Report`""" self.ensure_one() if self.redner_tmpl_id: keywords = self.redner_tmpl_id.get_keywords() # Get current substitutions subs = self.substitution_ids.mapped("keyword") or [] values = [] for key in keywords: # check to avoid duplicate keys if key not in subs: values.append((0, 0, {"keyword": key})) self.write({"substitution_ids": values}) # remove obsolete keywords in substitutions model if len(self.substitution_ids) > len(keywords): deprecated_keys = self.substitution_ids.filtered( lambda s: s.keyword not in keywords ) if len(deprecated_keys) > 0: deprecated_keys.unlink() def get_report_metadata(self): self.ensure_one() metadata_values = {} pformat = self.paperformat_id if not pformat: return metadata_values # Get print-paper-size values if pformat.format and pformat.orientation: metadata_values["print-paper-size"] = "%s %s" % ( pformat.format, pformat.orientation, ) elif pformat.format and not pformat.orientation: metadata_values["print-paper-size"] = "%s" % pformat.format # Get print-paper-margin values: top | right | bottom | left metadata_values["print-paper-margin"] = "%s %s %s %s" % ( pformat.margin_top, pformat.margin_right, pformat.margin_bottom, pformat.margin_left, ) return metadata_values def get_report_data(self, res_id): if not res_id: return {} conv = self.substitution_ids.filtered( lambda r: r.depth == 0 ).build_converter() instance = self.env[self.model].browse(res_id) return conv.odoo_to_message(instance) @api.model def get_from_report_name(self, report_name, report_type): return self.search( [ ("report_name", "=", report_name), ("report_type", "=", report_type), ] ) def render_redner(self, res_ids, data): self.ensure_one() if self.report_type != "redner": raise RuntimeError( "redner rendition is only available on redner report.\n" "(current: '{}', expected 'redner'".format(self.report_type) ) return ( self.env["redner.report"] .create({"ir_actions_report_id": self.id}) .create_report(res_ids, data) ) def gen_report_download_filename(self, res_ids, data): """Override this function to change the name of the downloaded report""" self.ensure_one() report = self.get_from_report_name(self.report_name, self.report_type) if report.print_report_name and not len(res_ids) > 1: obj = self.env[self.model].browse(res_ids) return safe_eval( report.print_report_name, {"object": obj, "time": time} ) return "{}.{}".format(self.name, self.redner_filetype) def _get_attachments(self, res_ids): """Return the report already generated for the given res_ids""" self.ensure_one() save_in_attachment = {} if res_ids: # Dispatch the records by ones having an attachment Model = self.env[self.model] record_ids = Model.browse(res_ids) if self.attachment: for record_id in record_ids: attachment_id = self.retrieve_attachment(record_id) if attachment_id: save_in_attachment[record_id.id] = attachment_id return save_in_attachment def postprocess_redner_report(self, record, buffer): """Handle post processing during the report generation. The basic behavior consists to create a new attachment containing the document base64 encoded. """ self.ensure_one() attachment_name = safe_eval( self.attachment, {"object": record, "time": time} ) if not attachment_name: return None if not attachment_name.endswith( ".{filetype}".format(filetype=self.redner_filetype) ): return None attachment_vals = { "name": attachment_name, "datas": base64.encodestring(buffer.getvalue()), "res_model": self.model, "res_id": record.id, "type": "binary", } try: self.env["ir.attachment"].create(attachment_vals) except AccessError: _logger.info( "Cannot save report %r as attachment", attachment_vals["name"] ) else: _logger.info( "The document %s is now saved in the database", attachment_vals["name"], ) return buffer