Skip to content
Snippets Groups Projects

Implement caching and optimization for Redner template handling

Merged oury.balde requested to merge topic/17.0/fsb into branch/17.0
6 files
+ 109
78
Compare changes
  • Side-by-side
  • Inline
Files
6
+ 96
72
@@ -23,6 +23,7 @@
@@ -23,6 +23,7 @@
from odoo import _, api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.exceptions import ValidationError
 
from odoo.tools.cache import ormcache
from ..redner import REDNER_API_PATH, Redner
from ..redner import REDNER_API_PATH, Redner
from ..utils.mimetype import b64_to_extension
from ..utils.mimetype import b64_to_extension
@@ -201,4 +202,51 @@
@@ -201,4 +202,51 @@
logger.error("Failed to get preview of redner template :%s", e)
logger.error("Failed to get preview of redner template :%s", e)
return
return
 
def _to_odoo_template(self, template):
 
"""
 
Convert the external template to the Odoo format.
 
"""
 
language = "{}|{}".format(template.get("produces"), template.get("language"))
 
 
odoo_template = {
 
"name": template.get("name"),
 
"description": template.get("description", ""),
 
"redner_id": template.get("name"),
 
"locale_id": self.env["res.lang"].search(
 
[("code", "=", template.get("locale", "fr_FR"))], limit=1
 
),
 
"language": language,
 
"slug": template.get("slug"),
 
"is_mjml": language == LANGUAGE_MJML_MUSTACHE,
 
"body": "",
 
"template_data": False,
 
}
 
match template.get("body-format"):
 
case "base64":
 
body = base64.b64decode(template.get("body", ""))
 
case _:
 
body = template.get("body", "")
 
if template.get("language") == "od+mustache":
 
odoo_template["template_data"] = body
 
else:
 
odoo_template["body"] = body
 
return odoo_template
 
 
@ormcache("record_id")
 
def _get_cached_template(self, record_id):
 
"""
 
Retrieves and caches the template from Redner for a given record.
 
"""
 
record = self.browse(record_id)
 
if not record.redner_id:
 
return {}
 
try:
 
# Fetch the template from the external system
 
template = self.redner.templates.account_template_read(record.redner_id)
 
# Convert the template to Odoo's format
 
return self._to_odoo_template(template)
 
except Exception as e:
 
logger.error("Failed to read Redner template: %s", e)
 
return {}
 
def _compute_template(self):
def _compute_template(self):
@@ -204,4 +252,7 @@
@@ -204,4 +252,7 @@
def _compute_template(self):
def _compute_template(self):
 
"""
 
Computes the template values for the records and applies cached or fetched data.
 
"""
for record in self:
for record in self:
if not record.id or not record.redner_id:
if not record.id or not record.redner_id:
continue
continue
@@ -205,4 +256,8 @@
@@ -205,4 +256,8 @@
for record in self:
for record in self:
if not record.id or not record.redner_id:
if not record.id or not record.redner_id:
continue
continue
 
 
# Fetch the cached template
 
cached_template = self._get_cached_template(record.id)
 
if not any([getattr(record, f) for f in COMPUTED_FIELDS]):
if not any([getattr(record, f) for f in COMPUTED_FIELDS]):
@@ -208,16 +263,7 @@
@@ -208,16 +263,7 @@
if not any([getattr(record, f) for f in COMPUTED_FIELDS]):
if not any([getattr(record, f) for f in COMPUTED_FIELDS]):
# if all the fields are undefined, get from redner
# If all computed fields are undefined, populate them
try:
# from the cached template.
template = self.redner.templates.account_template_read(
for f in COMPUTED_FIELDS + EDITABLE_FIELDS:
record.redner_id
if f in cached_template:
)
setattr(record, f, cached_template[f])
cached_template = self.to_cache(record.id, template)
for f in COMPUTED_FIELDS + EDITABLE_FIELDS:
if f in cached_template:
new_val = cached_template[f]
setattr(record, f, new_val)
except Exception as e:
logger.error("Failed to read redner template :%s", e)
return
else:
else:
@@ -223,5 +269,3 @@
@@ -223,5 +269,3 @@
else:
else:
# if at least one field is defined, we get from the cache
# If at least one field is defined, populate only undefined fields
# if the field is undefined
cached_template = self.cache[record.id]
for f in COMPUTED_FIELDS:
for f in COMPUTED_FIELDS:
@@ -227,8 +271,6 @@
@@ -227,8 +271,6 @@
for f in COMPUTED_FIELDS:
for f in COMPUTED_FIELDS:
attr = getattr(record, f)
if not getattr(record, f):
if not attr:
setattr(record, f, cached_template.get(f, None))
new_val = cached_template[f]
setattr(record, f, new_val)
@api.depends("template_data")
@api.depends("template_data")
def _compute_template_data(self):
def _compute_template_data(self):
@@ -237,10 +279,7 @@
@@ -237,10 +279,7 @@
continue
continue
if not record.template_data:
if not record.template_data:
try:
try:
template = self.redner.templates.account_template_read(
cached_template = self._get_cached_template(record.id)
record.redner_id
)
cached_template = self.to_cache(record.id, template)
if "template_data" in cached_template:
if "template_data" in cached_template:
new_val = cached_template["template_data"]
new_val = cached_template["template_data"]
encoded = base64.b64encode(new_val).decode("utf-8")
encoded = base64.b64encode(new_val).decode("utf-8")
@@ -270,6 +309,6 @@
@@ -270,6 +309,6 @@
for template in template_list:
for template in template_list:
# Filter out templates that already exist in Odoo
# Filter out templates that already exist in Odoo
if template["name"] not in existing_template_ids:
if template["name"] not in existing_template_ids:
new_templates.append(self.to_odoo_template(template))
new_templates.append(self._to_odoo_template(template))
return new_templates
return new_templates
@@ -274,37 +313,4 @@
@@ -274,37 +313,4 @@
return new_templates
return new_templates
def to_cache(self, record_id, template):
cached_template = self.to_odoo_template(template)
self.cache[record_id] = cached_template
return cached_template
def to_odoo_template(self, template):
# define a odoo_like template to put into the cache
language = "{}|{}".format(template.get("produces"), template.get("language"))
odoo_template = {
"name": template.get("name"),
"description": template.get("description", ""),
"redner_id": template.get("name"),
"locale_id": self.env["res.lang"].search(
[("code", "=", template.get("locale", "fr_FR"))], limit=1
),
"language": language,
"slug": template.get("slug"),
"is_mjml": language == LANGUAGE_MJML_MUSTACHE,
"body": "",
"template_data": False,
}
match template.get("body-format"):
case "base64":
body = base64.b64decode(template.get("body", ""))
case _:
body = template.get("body", "")
if template.get("language") == "od+mustache":
odoo_template["template_data"] = body
else:
odoo_template["body"] = body
return odoo_template
@property
@property
def redner(self):
def redner(self):
@@ -309,9 +315,9 @@
@@ -309,9 +315,9 @@
@property
@property
def redner(self):
def redner(self):
"""Try to avoid Redner instance to be over created"""
"""
global _redner
Returns a Redner instance.
if _redner is None:
Recomputes the instance if any system parameter changes.
# Bypass security rules when reading these configuration params. By
Uses a global variable to cache the instance across sessions.
# default, only some administrators have access to that model.
"""
config_model = self.env["ir.config_parameter"].sudo()
global _redner, _redner_params
@@ -317,2 +323,14 @@
@@ -317,2 +323,14 @@
 
# Fetch current system parameters
 
config_model = self.env["ir.config_parameter"].sudo()
 
current_params = {
 
"api_key": config_model.get_param("redner.api_key"),
 
"server_url": config_model.get_param("redner.server_url"),
 
"account": config_model.get_param("redner.account"),
 
"timeout": int(config_model.get_param("redner.timeout", default="20")),
 
}
 
 
# Check if parameters have changed or if _redner is None
 
if _redner is None or _redner_params != current_params:
 
# Recompute the Redner instance
_redner = Redner(
_redner = Redner(
@@ -318,6 +336,6 @@
@@ -318,6 +336,6 @@
_redner = Redner(
_redner = Redner(
config_model.get_param("redner.api_key"),
current_params["api_key"],
config_model.get_param("redner.server_url"),
current_params["server_url"],
config_model.get_param("redner.account"),
current_params["account"],
int(config_model.get_param("redner.timeout", default="20")),
current_params["timeout"],
)
)
@@ -323,4 +341,5 @@
@@ -323,4 +341,5 @@
)
)
 
_redner_params = current_params # Update the stored parameters
return _redner
return _redner
@@ -443,6 +462,6 @@
@@ -443,6 +462,6 @@
# We do NOT depend on the API for consistency here
# We do NOT depend on the API for consistency here
# So raised error should not result block template deletion
# So raised error should not result block template deletion
for record in self:
for record in self:
if record.redner_id:
if record.redner_id and record.allow_modification_from_odoo:
try:
try:
self.redner.templates.account_template_delete(record.redner_id)
self.redner.templates.account_template_delete(record.redner_id)
@@ -447,7 +466,12 @@
@@ -447,7 +466,12 @@
try:
try:
self.redner.templates.account_template_delete(record.redner_id)
self.redner.templates.account_template_delete(record.redner_id)
except Exception:
except Exception as e:
pass
# Log the exception if needed (for debugging purposes)
 
logger.warning(
 
"Failed to delete redner template for record %s: %s",
 
record.id,
 
{str(e)},
 
)
return super().unlink()
return super().unlink()
Loading