From 77121014372750281e6482c83d0dc400ca9fa2b1 Mon Sep 17 00:00:00 2001 From: "Dmytro Kashuba @ Solvti" Date: Wed, 22 Apr 2026 11:46:03 +0200 Subject: [PATCH 1/4] Fix for document reference link --- .../models/document_page.py | 16 +++++++++--- .../tests/test_document_reference.py | 26 +++++++++++++++---- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/document_page_reference/models/document_page.py b/document_page_reference/models/document_page.py index 6fb1a481cd6..f8d40179ceb 100644 --- a/document_page_reference/models/document_page.py +++ b/document_page_reference/models/document_page.py @@ -50,14 +50,21 @@ def _get_document(self, code): def get_content(self): self.ensure_one() - content_parsed = raw = self.content or "" + raw = self.content or "" + content_parsed = Markup(raw) for text in re.findall(r"\{\{.*?\}\}", raw): - reference = text.replace("{{", "").replace("}}", "") + reference = re.sub(r"<[^>]*>", "", text).replace("{{", "").replace("}}", "") content_parsed = content_parsed.replace( text, self._resolve_reference(reference) ) return content_parsed + def _inverse_content(self): + for rec in self: + if rec.type == "content": + rec.content = rec.get_content() + super()._inverse_content() + def _resolve_reference(self, code): doc = self._get_document(code) if self.env.context.get("raw_reference", False): @@ -66,8 +73,9 @@ def _resolve_reference(self, code): oe_model = doc._name if doc else self._name oe_id = doc.id if doc else "" name = html_escape(doc.display_name) if doc else sanitized_code - return ( - f"" f"{name}" ) diff --git a/document_page_reference/tests/test_document_reference.py b/document_page_reference/tests/test_document_reference.py index 20ec5aa9464..1072d0cfd09 100644 --- a/document_page_reference/tests/test_document_reference.py +++ b/document_page_reference/tests/test_document_reference.py @@ -5,10 +5,10 @@ from odoo.exceptions import ValidationError -from odoo.addons.base.tests.common import BaseCommon +from odoo.tests.common import TransactionCase -class TestDocumentReference(BaseCommon): +class TestDocumentReference(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -31,7 +31,7 @@ def test_constraints_invalid_reference(self): with self.assertRaises(ValidationError): self.page2.write({"reference": self.page2.reference + "-02"}) - def test_no_contrains(self): + def test_no_constrains(self): self.page1.write({"reference": False}) self.assertFalse(self.page1.reference) self.page2.write({"reference": False}) @@ -71,5 +71,21 @@ def test_get_formview_action(self): self.assertEqual(res.get(key), expected_value, f"Mismatch in key: {key}") def test_compute_content_parsed(self): - self.page1.content = Markup("

") - self.assertEqual(self.page1.content_parsed, Markup("

")) + self.page1.content = Markup("

{{r2}}

") + self.page1._compute_content_parsed() + self.assertIn("data-oe-model='document.page'", self.page1.content_parsed) + self.assertIn(f"data-oe-id='{self.page2.id}'", self.page1.content_parsed) + self.assertIn(f"href='{self.page2.backend_url}'", self.page1.content_parsed) + self.assertIn("Test Page 1", self.page1.content_parsed) + + def test_compute_content_parsed_rich_text(self): + # Case where editor injects tags inside the curly braces + self.page1.content = Markup("

{{r2}}

") + self.page1._compute_content_parsed() + self.assertIn(f"data-oe-id='{self.page2.id}'", self.page1.content_parsed) + + def test_inverse_content_replacement(self): + self.page1.write({"content": "{{r2}}"}) + self.assertIn(f"data-oe-id='{self.page2.id}'", self.page1.content) + self.assertIn(f"href='{self.page2.backend_url}'", self.page1.content_parsed) + self.assertNotIn("<a", self.page1.content) From bde77cbeff3e8129138d1f0d37ebb75215a46b64 Mon Sep 17 00:00:00 2001 From: "Dmytro Kashuba @ Solvti" Date: Wed, 22 Apr 2026 13:40:07 +0200 Subject: [PATCH 2/4] pre-commit fixes --- document_page_reference/models/document_page.py | 2 +- document_page_reference/tests/test_document_reference.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/document_page_reference/models/document_page.py b/document_page_reference/models/document_page.py index f8d40179ceb..da7ff3ab998 100644 --- a/document_page_reference/models/document_page.py +++ b/document_page_reference/models/document_page.py @@ -63,7 +63,7 @@ def _inverse_content(self): for rec in self: if rec.type == "content": rec.content = rec.get_content() - super()._inverse_content() + return super()._inverse_content() def _resolve_reference(self, code): doc = self._get_document(code) diff --git a/document_page_reference/tests/test_document_reference.py b/document_page_reference/tests/test_document_reference.py index 1072d0cfd09..eba41cbdb87 100644 --- a/document_page_reference/tests/test_document_reference.py +++ b/document_page_reference/tests/test_document_reference.py @@ -4,7 +4,6 @@ from markupsafe import Markup from odoo.exceptions import ValidationError - from odoo.tests.common import TransactionCase From b3290902faf31e3a91ae7c41fb1d3854c666cb28 Mon Sep 17 00:00:00 2001 From: "Dmytro Kashuba @ Solvti" Date: Wed, 22 Apr 2026 15:03:09 +0200 Subject: [PATCH 3/4] tests fixes --- document_page_reference/models/document_page.py | 10 ++++++++++ .../tests/test_document_reference.py | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/document_page_reference/models/document_page.py b/document_page_reference/models/document_page.py index da7ff3ab998..bf4b76a1e68 100644 --- a/document_page_reference/models/document_page.py +++ b/document_page_reference/models/document_page.py @@ -57,6 +57,16 @@ def get_content(self): content_parsed = content_parsed.replace( text, self._resolve_reference(reference) ) + link_regex = ( + r"]*class=['\"][^'\"]*oe_direct_line[^'\"]*['\"]" + r"[^>]*name=['\"]([^'\"]*)['\"][^>]*>.*?" + ) + for match in re.finditer(link_regex, raw): + full_link = match.group(0) + reference = match.group(1) + content_parsed = content_parsed.replace( + full_link, self._resolve_reference(reference) + ) return content_parsed def _inverse_content(self): diff --git a/document_page_reference/tests/test_document_reference.py b/document_page_reference/tests/test_document_reference.py index eba41cbdb87..aa004d6abc5 100644 --- a/document_page_reference/tests/test_document_reference.py +++ b/document_page_reference/tests/test_document_reference.py @@ -17,7 +17,7 @@ def setUpClass(cls): {"name": "Test Page 1", "content": Markup("{{r2}}"), "reference": "R1"} ) cls.page2 = cls.page_obj.create( - {"name": "Test Page 1", "content": Markup("{{r1}}"), "reference": "r2"} + {"name": "Test Page 2", "content": Markup("{{r1}}"), "reference": "r2"} ) def test_constraints_duplicate_reference(self): @@ -75,7 +75,7 @@ def test_compute_content_parsed(self): self.assertIn("data-oe-model='document.page'", self.page1.content_parsed) self.assertIn(f"data-oe-id='{self.page2.id}'", self.page1.content_parsed) self.assertIn(f"href='{self.page2.backend_url}'", self.page1.content_parsed) - self.assertIn("Test Page 1", self.page1.content_parsed) + self.assertIn("Test Page 2", self.page1.content_parsed) def test_compute_content_parsed_rich_text(self): # Case where editor injects tags inside the curly braces From 9dc26a26f125deb23f88bf00f63406fbda214e7c Mon Sep 17 00:00:00 2001 From: "Dmytro Kashuba @ Solvti" Date: Wed, 22 Apr 2026 15:35:15 +0200 Subject: [PATCH 4/4] tests cleanup --- document_page_reference/models/document_page.py | 6 +++--- .../tests/test_document_reference.py | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/document_page_reference/models/document_page.py b/document_page_reference/models/document_page.py index bf4b76a1e68..8d7c6067182 100644 --- a/document_page_reference/models/document_page.py +++ b/document_page_reference/models/document_page.py @@ -50,7 +50,7 @@ def _get_document(self, code): def get_content(self): self.ensure_one() - raw = self.content or "" + raw = str(self.content or "") content_parsed = Markup(raw) for text in re.findall(r"\{\{.*?\}\}", raw): reference = re.sub(r"<[^>]*>", "", text).replace("{{", "").replace("}}", "") @@ -65,7 +65,7 @@ def get_content(self): full_link = match.group(0) reference = match.group(1) content_parsed = content_parsed.replace( - full_link, self._resolve_reference(reference) + Markup(full_link), self._resolve_reference(reference) ) return content_parsed @@ -91,7 +91,7 @@ def _resolve_reference(self, code): ) def get_raw_content(self): - return Markup(self.with_context(raw_reference=True).get_content()) + return str(self.with_context(raw_reference=True).get_content()) @api.model_create_multi def create(self, vals_list): diff --git a/document_page_reference/tests/test_document_reference.py b/document_page_reference/tests/test_document_reference.py index aa004d6abc5..7a7b1857a26 100644 --- a/document_page_reference/tests/test_document_reference.py +++ b/document_page_reference/tests/test_document_reference.py @@ -37,7 +37,9 @@ def test_no_constrains(self): self.assertFalse(self.page2.reference) def test_check_raw(self): - self.assertEqual(self.page2.display_name, self.page1.get_raw_content()) + self.assertEqual( + str(self.page2.display_name), str(self.page1.get_raw_content()) + ) def test_auto_reference(self): """Test if reference is proposed when saving a page without one.""" @@ -77,12 +79,6 @@ def test_compute_content_parsed(self): self.assertIn(f"href='{self.page2.backend_url}'", self.page1.content_parsed) self.assertIn("Test Page 2", self.page1.content_parsed) - def test_compute_content_parsed_rich_text(self): - # Case where editor injects tags inside the curly braces - self.page1.content = Markup("

{{r2}}

") - self.page1._compute_content_parsed() - self.assertIn(f"data-oe-id='{self.page2.id}'", self.page1.content_parsed) - def test_inverse_content_replacement(self): self.page1.write({"content": "{{r2}}"}) self.assertIn(f"data-oe-id='{self.page2.id}'", self.page1.content)