mirror of
https://github.com/OCA/knowledge.git
synced 2025-08-28 02:06:13 -06:00
commit
51adfd44ad
@ -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>
|
||||
|
@ -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
|
||||
=======================
|
||||
@ -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
|
||||
@ -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.
|
||||
|
||||
@ -68,7 +64,7 @@ Authors
|
||||
Contributors
|
||||
------------
|
||||
|
||||
- Enric Tobella <etobella@creublanca.es>
|
||||
- Enric Tobella <etobella@creublanca.es>
|
||||
|
||||
Maintainers
|
||||
-----------
|
||||
|
@ -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",
|
||||
|
@ -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)
|
@ -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"
|
||||
@ -35,15 +31,7 @@ class DocumentPage(models.Model):
|
||||
@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):
|
||||
@ -52,42 +40,36 @@ class DocumentPage(models.Model):
|
||||
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):
|
||||
|
@ -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.
|
||||
|
@ -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">
|
||||
|
||||
/*
|
||||
@ -360,21 +360,16 @@ ul.auto-toc {
|
||||
</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&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&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>
|
||||
@ -391,13 +386,13 @@ the link between document pages.</p>
|
||||
</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
|
||||
@ -405,21 +400,21 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
|
||||
<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 <<a class="reference external" href="mailto:etobella@creublanca.es">etobella@creublanca.es</a>></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" />
|
||||
@ -434,6 +429,5 @@ promote its widespread use.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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):
|
||||
@ -28,6 +31,15 @@ class TestDocumentReference(TransactionCase):
|
||||
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")
|
||||
@ -52,7 +64,6 @@ class TestDocumentReference(TransactionCase):
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "document.page",
|
||||
"res_id": self.page1.id,
|
||||
"context": {},
|
||||
"target": "current",
|
||||
"views": [(view_id, "form")],
|
||||
}
|
||||
@ -60,6 +71,5 @@ class TestDocumentReference(TransactionCase):
|
||||
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,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>
|
||||
|
Loading…
Reference in New Issue
Block a user