# HG changeset patch # User Balde Oury <oury.balde@xcg-consulting.fr> # Date 1733822287 0 # Tue Dec 10 09:18:07 2024 +0000 # Branch 17.0 # Node ID 33b299c7ed28cc3d0f203c15578b5028d413ae43 # Parent 63b711b6e63cd8260cd777289d09e25841c8b9e5 Implement caching and optimization for Redner template handling - Added `ormcache` to improve performance by caching template retrieval in `_get_cached_template`. - Refactored template processing logic: - Introduced `_to_odoo_template` to centralize transformation of external templates into Odoo-compatible format. - Simplified template computation in `_compute_template` and `_compute_template_data` using cached templates. - Removed redundant methods (`to_cache`) and consolidated logic for clarity. diff --git a/NEWS.rst b/NEWS.rst --- a/NEWS.rst +++ b/NEWS.rst @@ -2,6 +2,11 @@ Changelog ========= +17.0.1.2.0 +---------- + +- Implement caching and optimization for Redner template handling. + 17.0.1.1.1 ---------- diff --git a/__manifest__.py b/__manifest__.py --- a/__manifest__.py +++ b/__manifest__.py @@ -21,7 +21,7 @@ { "name": "Redner", "license": "AGPL-3", - "version": "17.0.1.1.1", + "version": "17.0.1.2.0", "category": "Reporting", "author": "XCG Consulting", "website": "https://orbeet.io/", diff --git a/models/redner_template.py b/models/redner_template.py --- a/models/redner_template.py +++ b/models/redner_template.py @@ -23,6 +23,7 @@ from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from odoo.tools.cache import ormcache from ..redner import REDNER_API_PATH, Redner from ..utils.mimetype import b64_to_extension @@ -201,34 +202,75 @@ logger.error("Failed to get preview of redner template :%s", e) 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): + """ + Computes the template values for the records and applies cached or fetched data. + """ for record in self: if not record.id or not record.redner_id: 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 all the fields are undefined, get from redner - try: - template = self.redner.templates.account_template_read( - record.redner_id - ) - 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 - + # If all computed fields are undefined, populate them + # from the cached template. + for f in COMPUTED_FIELDS + EDITABLE_FIELDS: + if f in cached_template: + setattr(record, f, cached_template[f]) else: - # if at least one field is defined, we get from the cache - # if the field is undefined - cached_template = self.cache[record.id] + # If at least one field is defined, populate only undefined fields for f in COMPUTED_FIELDS: - attr = getattr(record, f) - if not attr: - new_val = cached_template[f] - setattr(record, f, new_val) + if not getattr(record, f): + setattr(record, f, cached_template.get(f, None)) @api.depends("template_data") def _compute_template_data(self): @@ -237,10 +279,7 @@ continue if not record.template_data: try: - template = self.redner.templates.account_template_read( - record.redner_id - ) - cached_template = self.to_cache(record.id, template) + cached_template = self._get_cached_template(record.id) if "template_data" in cached_template: new_val = cached_template["template_data"] encoded = base64.b64encode(new_val).decode("utf-8") @@ -270,42 +309,9 @@ for template in template_list: # Filter out templates that already exist in Odoo 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 - 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 def redner(self): """Try to avoid Redner instance to be over created""" diff --git a/views/redner_template.xml b/views/redner_template.xml --- a/views/redner_template.xml +++ b/views/redner_template.xml @@ -80,6 +80,7 @@ <group> <field name="description" + required="allow_modification_from_odoo" readonly="not allow_modification_from_odoo" /> </group> # HG changeset patch # User Balde Oury <oury.balde@xcg-consulting.fr> # Date 1733830228 0 # Tue Dec 10 11:30:28 2024 +0000 # Branch 17.0 # Node ID 85f702e851199da41e4d2c57e9b9448a8dc2ce2c # Parent 33b299c7ed28cc3d0f203c15578b5028d413ae43 Fix unlink func: log errors and refine template deletion condition diff --git a/NEWS.rst b/NEWS.rst --- a/NEWS.rst +++ b/NEWS.rst @@ -6,6 +6,7 @@ ---------- - Implement caching and optimization for Redner template handling. +- Fix unlink func: log errors and refine template deletion condition. 17.0.1.1.1 ---------- diff --git a/models/redner_template.py b/models/redner_template.py --- a/models/redner_template.py +++ b/models/redner_template.py @@ -449,11 +449,16 @@ # We do NOT depend on the API for consistency here # So raised error should not result block template deletion for record in self: - if record.redner_id: + if record.redner_id and record.allow_modification_from_odoo: try: self.redner.templates.account_template_delete(record.redner_id) - except Exception: - pass + except Exception as e: + # 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() # HG changeset patch # User Balde Oury <oury.balde@xcg-consulting.fr> # Date 1733822900 0 # Tue Dec 10 09:28:20 2024 +0000 # Branch 17.0 # Node ID d6aded082ef1a4d341fd55b05bc7fa3f24bcc895 # Parent 85f702e851199da41e4d2c57e9b9448a8dc2ce2c test: fix timing discrepancy in Redner template version field during tests diff --git a/NEWS.rst b/NEWS.rst --- a/NEWS.rst +++ b/NEWS.rst @@ -7,6 +7,7 @@ - Implement caching and optimization for Redner template handling. - Fix unlink func: log errors and refine template deletion condition. +- test: Fix timing discrepancy in Redner template version field during tests. 17.0.1.1.1 ---------- diff --git a/tests/test_redner_template.py b/tests/test_redner_template.py --- a/tests/test_redner_template.py +++ b/tests/test_redner_template.py @@ -19,8 +19,7 @@ ############################################################################## from unittest import mock - -from odoo import fields +from unittest.mock import ANY from .common import TestCommon @@ -89,9 +88,7 @@ } ) redner_template = self.env["redner.template"].create(values) - base_template["version"] = fields.Datetime.to_string( - redner_template.create_date - ) + base_template["version"] = ANY updated_template["version"] = base_template["version"] requests_put_mock.assert_not_called() self.assertEqual(redner_template.redner_id, base_name) # HG changeset patch # User Balde Oury <oury.balde@xcg-consulting.fr> # Date 1733850356 0 # Tue Dec 10 17:05:56 2024 +0000 # Branch 17.0 # Node ID 07224c0905af4f9c9c1d47d4acf369ce55c5c859 # Parent d6aded082ef1a4d341fd55b05bc7fa3f24bcc895 Fix redner property caching and param detection diff --git a/NEWS.rst b/NEWS.rst --- a/NEWS.rst +++ b/NEWS.rst @@ -8,6 +8,7 @@ - Implement caching and optimization for Redner template handling. - Fix unlink func: log errors and refine template deletion condition. - test: Fix timing discrepancy in Redner template version field during tests. +- Fix redner property caching and param detection. 17.0.1.1.1 ---------- diff --git a/models/redner_template.py b/models/redner_template.py --- a/models/redner_template.py +++ b/models/redner_template.py @@ -314,19 +314,32 @@ @property def redner(self): - """Try to avoid Redner instance to be over created""" - global _redner - if _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() + """ + Returns a Redner instance. + Recomputes the instance if any system parameter changes. + Uses a global variable to cache the instance across sessions. + """ + global _redner, _redner_params + # 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( - config_model.get_param("redner.api_key"), - config_model.get_param("redner.server_url"), - config_model.get_param("redner.account"), - int(config_model.get_param("redner.timeout", default="20")), + current_params["api_key"], + current_params["server_url"], + current_params["account"], + current_params["timeout"], ) + _redner_params = current_params # Update the stored parameters return _redner # HG changeset patch # User Balde Oury <oury.balde@xcg-consulting.fr> # Date 1733851221 0 # Tue Dec 10 17:20:21 2024 +0000 # Branch 17.0 # Node ID 93b8d613e36abdc467a2b3a8137ad7ecc1c67390 # Parent 07224c0905af4f9c9c1d47d4acf369ce55c5c859 Added tag 17.0.1.2.0 for changeset 07224c0905af diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -30,3 +30,4 @@ c422922e6c47634df921532fa26f73a77792cebc 17.0.1.0.0 244d816f3ee9c11634b29bcb601594c86e2846eb 17.0.1.1.0 37bbac9b58d9d074f0bf7574ae6b4a57bad8abcd 17.0.1.1.1 +07224c0905af4f9c9c1d47d4acf369ce55c5c859 17.0.1.2.0