import logging import re from odoo import _, api, fields, models from odoo.exceptions import ValidationError from .redner import Redner logger = logging.getLogger(__name__) PREFIX = "tmpl_" expression_pattern = re.compile( r""" (?P<open_bracket>\{\{) # Match opening brackets {{ (?P<content>.*) # Match anything (?P<close_bracket>\}\}) # Match closing brackets }} """, re.VERBOSE, ) # Match every words which starts with PREFIX variable_pattern = re.compile(r"(?P<variable>%s[\w]+)" % PREFIX, re.VERBOSE) def get_redner_tmpl_keys(data) -> list: # noqa """Retrieve every substitution template variables, should be prefixed by ``tmpl_``. Args: data(string): xml body i.e:: <mjml><mj-body>{{tmpl_replace_me}}</mj-body></mjml> Returns: list: of keywords """ if not data: return [] return list( set( [ v.group("variable") for e in expression_pattern.finditer(data) for v in variable_pattern.finditer(e.group("content")) ] ) ) class RednerTemplate(models.Model): _name = "redner.template" name = fields.Char( string="Name", required=True, help="This is a name of template mjml redner", ) body = fields.Text( string="Template remote Id", translate=True, required=True, help="Code for the mjml redner template must be added here", ) slug = fields.Char(string="Slug") active = fields.Boolean( string="Active", default=True, help=( "If unchecked, it will allow you to hide the " "template without removing it." ), ) is_mjml = fields.Boolean( string="Is MJML", default=True, help="set to false if your template doesn't contain MJML", ) detected_keywords = fields.Text( string="Keywords", readonly=True, compute="_compute_keywords" ) language = fields.Selection( string=_("Language"), selection=[("mustache", "mustache"), ("handlebar", "handlebar")], default="mustache", required=True, help="templating language", ) redner_id = fields.Char(string="Redner ID", readonly=True) produces = fields.Selection( selection=[("text/mjml", "MJML"), ("text/html", "HTML")], string="Produces", default="text/mjml", ) locale_id = fields.Many2one( comodel_name="res.lang", string="Locale", help="Optional translation language (ISO code).", ) _redner = None @property def redner(self): """Try to avoid Redner instance to be over created""" if self._redner is None: # Bypass security rules when reading these configuration params. By # default, only some administrators have access to that model. config_model = self.env["ir.config_parameter"].sudo() self._redner = Redner( config_model.get_param("redner.api_key"), config_model.get_param("redner.server_url"), config_model.get_param("redner.account"), ) return self._redner @api.model def create(self, vals): """Overwrite create to create redner template""" # We depend on the API for consistency here # So raised error should not result with a created template vals["redner_id"] = self.redner.templates.account_template_add( vals.get("language"), vals.get("body"), vals.get("name"), produces=vals.get("produces"), version=fields.Datetime.now(), ) return super(RednerTemplate, self).create(vals) def write(self, vals): """Overwrite write to update redner template""" # Similar to the "send_to_rednerd_server" method; not worth factoring # out. # We depend on the API for consistency here # So raised error should not result with an updated template if "name" in vals: self.ensure_one() redner_id = self.redner_id vals["redner_id"] = vals["name"] ret = super(RednerTemplate, self).write(vals) for record in self: try: if "name" not in vals: redner_id = record.redner_id record.redner.templates.account_template_update( redner_id, vals.get("language", record.language), vals.get("body", record.body), name=vals.get("name", ""), produces=vals.get("produces", record.produces), version=record.write_date, ) except Exception as e: logger.error("Failed to update redner template :%s" % e) raise ValidationError( _("Failed to update render template, %s" % e) ) return ret def unlink(self): """Overwrite unlink to delete redner template""" # We do NOT depend on the API for consistency here # So raised error should not result block template deletion try: self.redner.templates.account_template_delete(self.redner_id) except Exception: pass return super(RednerTemplate, self).unlink() def copy(self, default=None): self.ensure_one() default = dict(default or {}, name=_("%s (copy)") % self.name) return super(RednerTemplate, self).copy(default) @api.depends("body") def _compute_keywords(self): for record in self: record.detected_keywords = get_redner_tmpl_keys(record.body) @api.model def get_keywords(self): """Return mjml redner keywords""" return get_redner_tmpl_keys(self.body) def send_to_rednerd_server(self): """Send templates to the rednerd server. Useful when you have existing templates you want to register onto a new rednerd server (or with a new user). """ for record in self: # Similar to the "write" method override; not worth factoring out. templates = record.redner.templates try: templates.account_template_update( record.redner_id, record.language, record.body, record.name ) except ValidationError: record.redner_id = templates.account_template_add( record.language, record.body, record.name )