Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion document_page/views/report_document_page.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<t t-call="web.external_layout">
<div class="page">
<h1 t-field="doc.name" />
<div t-raw="doc.content" />
<div t-out="doc.content" />
</div>
</t>
</template>
Expand Down
10 changes: 3 additions & 7 deletions document_page_reference/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

=======================
Document Page Reference
=======================
Expand All @@ -17,7 +13,7 @@ Document Page Reference
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fknowledge-lightgray.png?logo=github
Expand All @@ -43,7 +39,7 @@ the link between document pages.
Usage
=====

When editing a document page add elements like ${XXX} where XXX is the
When editing a document page add elements like {{XXX}} where XXX is the
reference of another page. Now, when viewing the document, it will link
directly to the page. Also, the name will be parsed as the display name.

Expand All @@ -68,7 +64,7 @@ Authors
Contributors
------------

- Enric Tobella <etobella@creublanca.es>
- Enric Tobella <etobella@creublanca.es>

Maintainers
-----------
Expand Down
2 changes: 1 addition & 1 deletion document_page_reference/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"name": "Document Page Reference",
"summary": """
Include references on document pages""",
"version": "18.0.1.0.0",
"version": "18.0.1.1.0",
"license": "AGPL-3",
"author": "Creu Blanca,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/knowledge",
Expand Down
12 changes: 12 additions & 0 deletions document_page_reference/migrations/18.0.1.1.0/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2025 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

import re

from openupgradelib import openupgrade


@openupgrade.migrate()
def migrate(env, version):
for item in env["document.page"].search([("content", "ilike", "${")]):
item.content = re.sub(r"\${(.+)}", r"{{\1}}", item.content)
56 changes: 19 additions & 37 deletions document_page_reference/models/document_page.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
# Copyright 2019 Creu Blanca
# Copyright 2025 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging
import re

from jinja2.sandbox import SandboxedEnvironment
from markupsafe import Markup

from odoo import _, api, fields, models
from odoo import api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import html_escape

_logger = logging.getLogger(__name__)
env = SandboxedEnvironment(autoescape=False)


class DocumentPage(models.Model):
_inherit = "document.page"
Expand All @@ -35,15 +31,7 @@ def get_formview_action(self, access_uid=None):
@api.depends("content")
def _compute_content_parsed(self):
for record in self:
try:
raw = record.content or ""
converted = re.sub(r"\$\{([\w_]+)\}", r"{{ resolve('\1') }}", raw)
template = env.from_string(converted)
rendered = template.render(resolve=record._resolve_reference)
record.content_parsed = rendered
except Exception as e:
_logger.info("Render failed for %s: %s", record.id, e)
record.content_parsed = record.content or ""
record.content_parsed = record.get_content()

@api.constrains("reference")
def _check_reference_validity(self):
Expand All @@ -52,42 +40,36 @@ def _check_reference_validity(self):
continue
regex = r"^[a-zA-Z_][a-zA-Z0-9_]*$"
if not re.match(regex, rec.reference):
raise ValidationError(_("Reference is not valid"))
raise ValidationError(self.env._("Reference is not valid"))
domain = [("reference", "=", rec.reference), ("id", "!=", rec.id)]
if self.search(domain):
raise ValidationError(_("Reference must be unique"))
raise ValidationError(self.env._("Reference must be unique"))

def _get_document(self, code):
return self.search([("reference", "=", code)], limit=1)

def get_content(self):
for record in self:
try:
raw = record.content or ""
converted = re.sub(r"\$\{([\w_]+)\}", r"{{ resolve('\1') }}", raw)
template = env.from_string(converted)
return template.render(resolve=record._resolve_reference)
except Exception:
_logger.error(
"Template from page with id = %s cannot be processed", record.id
)
return record.content
self.ensure_one()
content_parsed = raw = self.content or ""
for text in re.findall(r"\{\{.*?\}\}", raw):
reference = text.replace("{{", "").replace("}}", "")
content_parsed = content_parsed.replace(
text, self._resolve_reference(reference)
)
return content_parsed

def _resolve_reference(self, code):
doc = self._get_document(code)
if self.env.context.get("raw_reference", False):
return html_escape(doc.display_name if doc else code)
sanitized_code = html_escape(code)
if not doc:
return (
f"<i><a href='#' class='oe_direct_line' "
f"data-oe-model='document.page' data-oe-id='' "
f"name='{sanitized_code}'>{sanitized_code}</a></i>"
)
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"<a href='#' class='oe_direct_line' data-oe-model='{doc._name}' "
f"data-oe-id='{doc.id}' name='{sanitized_code}'>"
f"{html_escape(doc.display_name)}</a>"
f"<a href='#' class='oe_direct_line' data-oe-model='{oe_model}' "
f"data-oe-id='{oe_id}' name='{sanitized_code}'>"
f"{name}</a>"
)

def get_raw_content(self):
Expand Down
2 changes: 1 addition & 1 deletion document_page_reference/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
When editing a document page add elements like \${XXX} where XXX is the
When editing a document page add elements like {{XXX}} where XXX is the
reference of another page. Now, when viewing the document, it will link
directly to the page. Also, the name will be parsed as the display name.
28 changes: 11 additions & 17 deletions document_page_reference/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<title>Document Page Reference</title>
<style type="text/css">

/*
Expand Down Expand Up @@ -360,21 +360,16 @@
</style>
</head>
<body>
<div class="document">
<div class="document" id="document-page-reference">
<h1 class="title">Document Page Reference</h1>


<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="document-page-reference">
<h1>Document Page Reference</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f5181a95b68e52c04e1b76ba19f503b5bfc8d365939debe14edbb40413101321
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/knowledge/tree/18.0/document_page_reference"><img alt="OCA/knowledge" src="https://img.shields.io/badge/github-OCA%2Fknowledge-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/knowledge-18-0/knowledge-18-0-document_page_reference"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/knowledge&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/knowledge/tree/18.0/document_page_reference"><img alt="OCA/knowledge" src="https://img.shields.io/badge/github-OCA%2Fknowledge-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/knowledge-18-0/knowledge-18-0-document_page_reference"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/knowledge&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows to add a reference name on documents and simplifies
the link between document pages.</p>
<p><strong>Table of contents</strong></p>
Expand All @@ -391,35 +386,35 @@ <h1>Document Page Reference</h1>
</ul>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
<p>When editing a document page add elements like ${XXX} where XXX is the
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>When editing a document page add elements like {{XXX}} where XXX is the
reference of another page. Now, when viewing the document, it will link
directly to the page. Also, the name will be parsed as the display name.</p>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/knowledge/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/knowledge/issues/new?body=module:%20document_page_reference%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<ul class="simple">
<li>Creu Blanca</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<ul class="simple">
<li>Enric Tobella &lt;<a class="reference external" href="mailto:etobella&#64;creublanca.es">etobella&#64;creublanca.es</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand All @@ -434,6 +429,5 @@ <h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
</div>
</div>
</div>
</div>
</body>
</html>
26 changes: 18 additions & 8 deletions document_page_reference/tests/test_document_reference.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
# Copyright 2019 Creu Blanca
# Copyright 2025 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from markupsafe import Markup

from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase

from odoo.addons.base.tests.common import BaseCommon

class TestDocumentReference(TransactionCase):

class TestDocumentReference(BaseCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.page_obj = cls.env["document.page"]
cls.history_obj = cls.env["document.page.history"]
cls.page1 = cls.page_obj.create(
{"name": "Test Page 1", "content": "${r2}", "reference": "R1"}
{"name": "Test Page 1", "content": Markup("{{r2}}"), "reference": "R1"}
)
cls.page2 = cls.page_obj.create(
{"name": "Test Page 1", "content": "${r1}", "reference": "r2"}
{"name": "Test Page 1", "content": Markup("{{r1}}"), "reference": "r2"}
)

def test_constraints_duplicate_reference(self):
Expand All @@ -28,6 +31,15 @@ def test_constraints_invalid_reference(self):
with self.assertRaises(ValidationError):
self.page2.write({"reference": self.page2.reference + "-02"})

def test_no_contrains(self):
self.page1.write({"reference": False})
self.assertFalse(self.page1.reference)
self.page2.write({"reference": False})
self.assertFalse(self.page2.reference)

def test_check_raw(self):
self.assertEqual(self.page2.display_name, self.page1.get_raw_content())

def test_auto_reference(self):
"""Test if reference is proposed when saving a page without one."""
self.assertEqual(self.page1.reference, "R1")
Expand All @@ -52,14 +64,12 @@ def test_get_formview_action(self):
"type": "ir.actions.act_window",
"res_model": "document.page",
"res_id": self.page1.id,
"context": {},
"target": "current",
"views": [(view_id, "form")],
}
for key, expected_value in expected_keys.items():
self.assertEqual(res.get(key), expected_value, f"Mismatch in key: {key}")

def test_compute_content_parsed(self):
self.page1.content = "<p></p>"
self.page1._compute_content_parsed()
self.assertEqual(str(self.page1.content_parsed), "<p></p>")
self.page1.content = Markup("<p></p>")
self.assertEqual(self.page1.content_parsed, Markup("<p></p>"))
4 changes: 2 additions & 2 deletions document_page_reference/views/report_document_page.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
id="report_documentpage_doc"
inherit_id="document_page.report_documentpage_doc"
>
<xpath expr="//div[@t-raw='doc.content']" position="attributes">
<xpath expr="//div[@t-out='doc.content']" position="attributes">
<attribute name="t-if">1==0</attribute>
</xpath>
<xpath expr="//div[@t-raw='doc.content']" position="after">
<xpath expr="//div[@t-out='doc.content']" position="after">
<t t-out="doc.get_raw_content()" />
</xpath>
</template>
Expand Down