# 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