From 0021173e64aa0d02514f3c4ab735a0acc27a08bc Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Wed, 27 Nov 2019 23:20:44 +0100 Subject: [PATCH 01/27] [ADD] document_page_reference --- document_page_reference/README.rst | 81 ++++ document_page_reference/__init__.py | 1 + document_page_reference/__manifest__.py | 23 + document_page_reference/models/__init__.py | 1 + .../models/document_page.py | 108 +++++ .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 2 + document_page_reference/readme/USAGE.rst | 3 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 427 ++++++++++++++++++ .../static/src/js/editor.js | 30 ++ document_page_reference/tests/__init__.py | 1 + .../tests/test_document_reference.py | 49 ++ document_page_reference/views/assets.xml | 8 + .../views/document_page.xml | 62 +++ .../views/report_document_page.xml | 13 + 16 files changed, 810 insertions(+) create mode 100644 document_page_reference/README.rst create mode 100644 document_page_reference/__init__.py create mode 100644 document_page_reference/__manifest__.py create mode 100644 document_page_reference/models/__init__.py create mode 100644 document_page_reference/models/document_page.py create mode 100644 document_page_reference/readme/CONTRIBUTORS.rst create mode 100644 document_page_reference/readme/DESCRIPTION.rst create mode 100644 document_page_reference/readme/USAGE.rst create mode 100644 document_page_reference/static/description/icon.png create mode 100644 document_page_reference/static/description/index.html create mode 100644 document_page_reference/static/src/js/editor.js create mode 100644 document_page_reference/tests/__init__.py create mode 100644 document_page_reference/tests/test_document_reference.py create mode 100644 document_page_reference/views/assets.xml create mode 100644 document_page_reference/views/document_page.xml create mode 100755 document_page_reference/views/report_document_page.xml diff --git a/document_page_reference/README.rst b/document_page_reference/README.rst new file mode 100644 index 00000000000..adc253cb4e5 --- /dev/null +++ b/document_page_reference/README.rst @@ -0,0 +1,81 @@ +======================= +Document Page Reference +======================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/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 + :target: https://github.com/OCA/knowledge/tree/11.0/document_page_reference + :alt: OCA/knowledge +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/knowledge-11-0/knowledge-11-0-document_page_reference + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/118/11.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to add a reference name on documents and simplifies the link +between document pages. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +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. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Creu Blanca + +Contributors +~~~~~~~~~~~~ + +* Enric Tobella + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/knowledge `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/document_page_reference/__init__.py b/document_page_reference/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/document_page_reference/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/document_page_reference/__manifest__.py b/document_page_reference/__manifest__.py new file mode 100644 index 00000000000..c3a6a9814f1 --- /dev/null +++ b/document_page_reference/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2019 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Document Page Reference', + 'summary': """ + Include references on document pages""", + 'version': '11.0.1.0.0', + 'license': 'AGPL-3', + 'author': 'Creu Blanca,Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/knowledge', + 'depends': [ + 'document_page', + 'web_editor', + ], + 'data': [ + 'views/assets.xml', + 'views/document_page.xml', + 'views/report_document_page.xml', + ], + 'maintainers': ['etobella'], + 'development_status': 'Alpha', +} diff --git a/document_page_reference/models/__init__.py b/document_page_reference/models/__init__.py new file mode 100644 index 00000000000..427be24e72a --- /dev/null +++ b/document_page_reference/models/__init__.py @@ -0,0 +1 @@ +from . import document_page diff --git a/document_page_reference/models/document_page.py b/document_page_reference/models/document_page.py new file mode 100644 index 00000000000..1f5a67096d2 --- /dev/null +++ b/document_page_reference/models/document_page.py @@ -0,0 +1,108 @@ +# Copyright 2019 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, tools, _ +from odoo.exceptions import ValidationError +from odoo.tools.misc import html_escape + +import logging +_logger = logging.getLogger(__name__) + +try: + from jinja2.sandbox import SandboxedEnvironment + from jinja2 import Undefined + from jinja2.lexer import name_re as old_name_re + import re + + name_re = re.compile(u'^%s$' % old_name_re.pattern) + + class Context(SandboxedEnvironment.context_class): + def resolve(self, key): + res = super().resolve(key) + if not isinstance(res, Undefined): + return res + return self.parent['ref'](key) + + class Environment(SandboxedEnvironment): + context_class = Context + + mako_template_env = Environment( + block_start_string="<%", + block_end_string="%>", + variable_start_string="${", + variable_end_string="}", + comment_start_string="<%doc>", + comment_end_string="", + line_statement_prefix="%", + line_comment_prefix="##", + trim_blocks=True, # do not output newline after blocks + autoescape=False, + ) +except Exception: + _logger.error("Jinja2 is not available") + + +class DocumentPage(models.Model): + + _inherit = 'document.page' + + reference = fields.Char( + help="Used to find the document, it can contain letters, numbers and _" + ) + content_parsed = fields.Html(compute='_compute_content_parsed') + + @api.depends('history_head') + def _compute_content_parsed(self): + for record in self: + record.content_parsed = record.get_content() + + @api.constrains('reference') + def _check_reference(self): + for record in self: + if not record.reference: + continue + if not name_re.match(record.reference): + raise ValidationError(_('Reference is not valid')) + if self.search([ + ('reference', '=', record.reference), + ('id', '!=', record.id)] + ): + raise ValidationError(_('Reference must be unique')) + + def _get_document(self, code): + # Hook created in order to add check on other models + document = self.search([('reference', '=', code)]) + if document: + return document + return False + + def get_reference(self, code): + element = self._get_document(code) + if not element: + return code + if self.env.context.get('raw_reference', False): + return html_escape(element.display_name) + text = '%s' + return text % ( + element._name, + element.id, + html_escape(element.display_name) + ) + + def _get_template_variables(self): + return {'ref': self.get_reference} + + def get_content(self): + try: + content = self.content + mako_env = mako_template_env + template = mako_env.from_string(tools.ustr(content)) + return template.render(self._get_template_variables()) + except Exception: + _logger.error( + 'Template from page %s cannot be processed' % self.id) + return self.content + + def get_raw_content(self): + return self.with_context(raw_reference=True).get_content() diff --git a/document_page_reference/readme/CONTRIBUTORS.rst b/document_page_reference/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..93ec993e044 --- /dev/null +++ b/document_page_reference/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Enric Tobella diff --git a/document_page_reference/readme/DESCRIPTION.rst b/document_page_reference/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..dfddc47b4ff --- /dev/null +++ b/document_page_reference/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module allows to add a reference name on documents and simplifies the link +between document pages. diff --git a/document_page_reference/readme/USAGE.rst b/document_page_reference/readme/USAGE.rst new file mode 100644 index 00000000000..5f341f02648 --- /dev/null +++ b/document_page_reference/readme/USAGE.rst @@ -0,0 +1,3 @@ +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. diff --git a/document_page_reference/static/description/icon.png b/document_page_reference/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/document_page_reference/static/description/index.html b/document_page_reference/static/description/index.html new file mode 100644 index 00000000000..87f2d10fa1d --- /dev/null +++ b/document_page_reference/static/description/index.html @@ -0,0 +1,427 @@ + + + + + + +Document Page Reference + + + +
+

Document Page Reference

+ + +

Beta License: AGPL-3 OCA/knowledge Translate me on Weblate Try me on Runbot

+

This module allows to add a reference name on documents and simplifies the link +between document pages.

+

Table of contents

+ +
+

Usage

+

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.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Creu Blanca
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/knowledge project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/document_page_reference/static/src/js/editor.js b/document_page_reference/static/src/js/editor.js new file mode 100644 index 00000000000..b3533a8721a --- /dev/null +++ b/document_page_reference/static/src/js/editor.js @@ -0,0 +1,30 @@ +odoo.define('document_page_reference.backend', function (require) { + 'use strict'; + + var field_registry = require('web.field_registry'); + var backend = require('web_editor.backend'); + var FieldTextHtmlSimple = backend.FieldTextHtmlSimple; + + var FieldDocumentPage = FieldTextHtmlSimple.extend({ + events: _.extend({}, FieldTextHtmlSimple.prototype.events, { + 'click .oe_direct_line': '_onClickDirectLink', + }), + _onClickDirectLink: function (event) { + var self = this; + event.preventDefault(); + event.stopPropagation(); + var element = $(event.target).closest('.oe_direct_line')[0]; + this._rpc({ + model: element.name, + method: 'get_formview_action', + args: [[parseInt(element.dataset.id)]], + context: this.record.getContext(this.recordParams), + }) + .then(function (action) { + self.trigger_up('do_action', {action: action}); + }); + }, + }); + field_registry.add('document_page_reference', FieldDocumentPage); + return FieldDocumentPage; +}); diff --git a/document_page_reference/tests/__init__.py b/document_page_reference/tests/__init__.py new file mode 100644 index 00000000000..ca802a6bb24 --- /dev/null +++ b/document_page_reference/tests/__init__.py @@ -0,0 +1 @@ +from . import test_document_reference diff --git a/document_page_reference/tests/test_document_reference.py b/document_page_reference/tests/test_document_reference.py new file mode 100644 index 00000000000..4be351b4bdd --- /dev/null +++ b/document_page_reference/tests/test_document_reference.py @@ -0,0 +1,49 @@ +# Copyright 2019 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError + + +class TestDocumentReference(TransactionCase): + + def setUp(self): + super().setUp() + self.page_obj = self.env['document.page'] + self.history_obj = self.env['document.page.history'] + self.page1 = self.page_obj.create({ + 'name': 'Test Page 1', + 'content': '${r2}', + 'reference': 'R1' + }) + self.page2 = self.page_obj.create({ + 'name': 'Test Page 1', + 'content': '${r1}', + 'reference': 'r2' + }) + + def test_constrains_01(self): + with self.assertRaises(ValidationError): + self.page2.write({'reference': self.page1.reference}) + + def test_constrains_02(self): + with self.assertRaises(ValidationError): + self.page2.write({'reference': self.page2.reference + '-02'}) + + def test_no_contrains(self): + self.page1.write({'reference': False}) + self.page2.write({'reference': False}) + self.assertEqual(self.page1.reference, self.page2.reference) + + def test_check_raw(self): + self.assertEqual(self.page2.display_name, self.page1.get_raw_content()) + + def test_check_reference(self): + self.assertRegex( + self.page1.content_parsed, + '.*%s.*' % self.page2.display_name + ) + + def test_no_reference(self): + self.page2.reference = 'r3' + self.assertRegex(self.page1.content_parsed, '.*r2.*') diff --git a/document_page_reference/views/assets.xml b/document_page_reference/views/assets.xml new file mode 100644 index 00000000000..e292a0aad7e --- /dev/null +++ b/document_page_reference/views/assets.xml @@ -0,0 +1,8 @@ + + + + diff --git a/document_page_reference/views/document_page.xml b/document_page_reference/views/document_page.xml new file mode 100644 index 00000000000..f4bf348688f --- /dev/null +++ b/document_page_reference/views/document_page.xml @@ -0,0 +1,62 @@ + + + + + + + document.page.form (in knowledge_reference) + document.page + + + +

+ +

+
+ + oe_edit_only + + + + + +
+ + + document.page.menu.form + document.page + + + + 1 + + + + + + + + + document.page.search (in knowledge_reference) + document.page + + + + + + + + + + document.page.tree (in knowledge_reference) + document.page + + + + + + + + +
diff --git a/document_page_reference/views/report_document_page.xml b/document_page_reference/views/report_document_page.xml new file mode 100755 index 00000000000..779465694c0 --- /dev/null +++ b/document_page_reference/views/report_document_page.xml @@ -0,0 +1,13 @@ + + + + + + From 2af3d4db329b97301d851ac99be7de5a6f8f2745 Mon Sep 17 00:00:00 2001 From: Jordi Ballester Alomar Date: Fri, 29 Nov 2019 14:31:58 +0100 Subject: [PATCH 02/27] [IMP] document_page_reference: add option to create a new page from a non-existing reference --- document_page_reference/README.rst | 17 +++++- .../i18n/document_page_reference.pot | 52 +++++++++++++++++++ .../models/document_page.py | 20 ++++--- .../static/description/index.html | 12 ++++- .../static/src/js/editor.js | 13 +++-- 5 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 document_page_reference/i18n/document_page_reference.pot diff --git a/document_page_reference/README.rst b/document_page_reference/README.rst index adc253cb4e5..34ad39975e8 100644 --- a/document_page_reference/README.rst +++ b/document_page_reference/README.rst @@ -7,9 +7,9 @@ Document Page Reference !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png :target: https://odoo-community.org/page/development-status - :alt: Beta + :alt: Alpha .. |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 @@ -28,6 +28,11 @@ Document Page Reference This module allows to add a reference name on documents and simplifies the link between document pages. +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + **Table of contents** .. contents:: @@ -76,6 +81,14 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. +.. |maintainer-etobella| image:: https://github.com/etobella.png?size=40px + :target: https://github.com/etobella + :alt: etobella + +Current `maintainer `__: + +|maintainer-etobella| + This module is part of the `OCA/knowledge `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/document_page_reference/i18n/document_page_reference.pot b/document_page_reference/i18n/document_page_reference.pot new file mode 100644 index 00000000000..b3ce20b2abd --- /dev/null +++ b/document_page_reference/i18n/document_page_reference.pot @@ -0,0 +1,52 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * document_page_reference +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: document_page_reference +#: model:ir.model.fields,field_description:document_page_reference.field_document_page_content_parsed +msgid "Content Parsed" +msgstr "" + +#. module: document_page_reference +#: model:ir.model,name:document_page_reference.model_document_page +msgid "Document Page" +msgstr "" + +#. module: document_page_reference +#: model:ir.model.fields,field_description:document_page_reference.field_document_page_reference +msgid "Reference" +msgstr "" + +#. module: document_page_reference +#: code:addons/document_page_reference/models/document_page.py:65 +#, python-format +msgid "Reference is not valid" +msgstr "" + +#. module: document_page_reference +#: code:addons/document_page_reference/models/document_page.py:70 +#, python-format +msgid "Reference must be unique" +msgstr "" + +#. module: document_page_reference +#: model:ir.model.fields,help:document_page_reference.field_document_page_reference +msgid "Used to find the document, it can contain letters, numbers and _" +msgstr "" + +#. module: document_page_reference +#: model:ir.ui.view,arch_db:document_page_reference.document_page_form_view +msgid "internal_reference" +msgstr "" + diff --git a/document_page_reference/models/document_page.py b/document_page_reference/models/document_page.py index 1f5a67096d2..f75fa66616a 100644 --- a/document_page_reference/models/document_page.py +++ b/document_page_reference/models/document_page.py @@ -74,21 +74,25 @@ def _get_document(self, code): document = self.search([('reference', '=', code)]) if document: return document - return False + else: + return self.env[self._name] def get_reference(self, code): element = self._get_document(code) - if not element: - return code if self.env.context.get('raw_reference', False): return html_escape(element.display_name) - text = '%s' - return text % ( + text = """%s + """ + if not element: + text = '%s' % text + res = text % ( element._name, - element.id, - html_escape(element.display_name) + element and element.id or '', + code, + html_escape(element.display_name or code), ) + return res def _get_template_variables(self): return {'ref': self.get_reference} diff --git a/document_page_reference/static/description/index.html b/document_page_reference/static/description/index.html index 87f2d10fa1d..92644b72617 100644 --- a/document_page_reference/static/description/index.html +++ b/document_page_reference/static/description/index.html @@ -3,7 +3,7 @@ - + Document Page Reference -
-

Document Page Reference

+
+ + +Odoo Community Association + +
+

Document Page Reference

-

Beta License: AGPL-3 OCA/knowledge Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/knowledge Translate me on Weblate Try me on Runboat

This module allows to add a reference name on documents and simplifies the link between document pages.

Table of contents

@@ -386,13 +391,13 @@

Document Page Reference

-

Usage

+

Usage

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.

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. 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 @@ -400,21 +405,21 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

+
diff --git a/document_page_reference/static/src/js/editor.esm.js b/document_page_reference/static/src/js/editor.esm.js new file mode 100644 index 00000000000..416bfb851f4 --- /dev/null +++ b/document_page_reference/static/src/js/editor.esm.js @@ -0,0 +1,32 @@ +import {HtmlField, htmlField} from "@web/views/fields/html/html_field"; +import {onMounted} from "@odoo/owl"; +import {registry} from "@web/core/registry"; +import {useService} from "@web/core/utils/hooks"; + +class DocumentPageReferenceField extends HtmlField { + setup() { + super.setup(); + this.orm = useService("orm"); + this.action = useService("action"); + onMounted(() => { + // eslint-disable-next-line no-undef + const links = document.querySelectorAll(".oe_direct_line"); + links.forEach((link) => { + link.addEventListener("click", (event) => + this._onClickDirectLink(event) + ); + }); + }); + } + _onClickDirectLink(event) { + const {oeModel: model, oeId} = event.target.dataset; + const id = parseInt(oeId, 10); + this.orm.call(model, "get_formview_action", [[id]], {}).then((action) => { + this.action.doAction(action); + }); + } +} +registry.category("fields").add("document_page_reference", { + ...htmlField, + component: DocumentPageReferenceField, +}); diff --git a/document_page_reference/static/src/js/editor.js b/document_page_reference/static/src/js/editor.js deleted file mode 100644 index 852df084ca9..00000000000 --- a/document_page_reference/static/src/js/editor.js +++ /dev/null @@ -1,34 +0,0 @@ -odoo.define("document_page_reference.backend", function (require) { - "use strict"; - - var field_registry = require("web.field_registry"); - var FieldTextHtmlSimple = require("web_editor.field.html"); - var FieldDocumentPage = FieldTextHtmlSimple.extend({ - events: _.extend({}, FieldTextHtmlSimple.prototype.events, { - "click .oe_direct_line": "_onClickDirectLink", - }), - _onClickDirectLink: function (event) { - var self = this; - event.preventDefault(); - event.stopPropagation(); - var element = $(event.target).closest(".oe_direct_line")[0]; - var default_reference = element.name; - var model = $(event.target).data("oe-model"); - var id = $(event.target).data("oe-id"); - var context = this.record.getContext(this.recordParams); - if (default_reference) { - context.default_reference = default_reference; - } - this._rpc({ - model: model, - method: "get_formview_action", - args: [[parseInt(id, 10)]], - context: context, - }).then(function (action) { - self.trigger_up("do_action", {action: action}); - }); - }, - }); - field_registry.add("document_page_reference", FieldDocumentPage); - return FieldDocumentPage; -}); diff --git a/document_page_reference/tests/test_document_reference.py b/document_page_reference/tests/test_document_reference.py index 1a2ba268021..73581b8b9dc 100644 --- a/document_page_reference/tests/test_document_reference.py +++ b/document_page_reference/tests/test_document_reference.py @@ -18,61 +18,48 @@ def setUpClass(cls): {"name": "Test Page 1", "content": "${r1}", "reference": "r2"} ) - def test_constrains_01(self): + def test_constraints_duplicate_reference(self): + """Should raise if reference is not unique (same as another).""" with self.assertRaises(ValidationError): self.page2.write({"reference": self.page1.reference}) - def test_constrains_02(self): + def test_constraints_invalid_reference(self): + """Should raise if reference does not match the required pattern.""" with self.assertRaises(ValidationError): self.page2.write({"reference": self.page2.reference + "-02"}) - def test_no_contrains(self): - self.page1.write({"reference": False}) - self.page2.write({"reference": False}) - self.assertEqual(self.page1.reference, self.page2.reference) - - def test_check_raw(self): - self.assertEqual(self.page2.display_name, self.page1.get_raw_content()) - - def test_check_reference(self): - self.assertRegex(self.page1.content_parsed, ".*%s.*" % self.page2.display_name) - - def test_no_reference(self): - self.page2.reference = "r3" - self.assertRegex(self.page1.content_parsed, ".*r2.*") - def test_auto_reference(self): """Test if reference is proposed when saving a page without one.""" self.assertEqual(self.page1.reference, "R1") new_page = self.page_obj.create( - {"name": "Test Page with no rEfErenCe", "content": "some content"} + {"name": "Test Page with no reference", "content": "some content"} ) self.assertEqual(new_page.reference, "test_page_with_no_reference") - new_page_duplicated_name = self.page_obj.create( - { - "name": "test page with no reference", - "content": "this should have an empty reference " - "because reference must be unique", - } - ) - self.assertFalse(new_page_duplicated_name.reference) + with self.assertRaises(ValidationError): + new_page_duplicated_name = self.page_obj.create( + { + "name": "test page with no reference", + "content": "this should have an empty reference " + "because reference must be unique", + } + ) + self.assertFalse(new_page_duplicated_name.reference) def test_get_formview_action(self): res = self.page1.get_formview_action() view_id = self.env.ref("document_page.view_wiki_form").id - expected_result = { + expected_keys = { "type": "ir.actions.act_window", - "context": {}, "res_model": "document.page", "res_id": self.page1.id, - "view_mode": "form", - "view_type": "form", + "context": {}, "target": "current", "views": [(view_id, "form")], } - self.assertEqual(res, expected_result) + 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 = "

" + self.page1.content = "

" self.page1._compute_content_parsed() self.assertEqual(str(self.page1.content_parsed), "

") diff --git a/document_page_reference/views/document_page.xml b/document_page_reference/views/document_page.xml index a9459fcceb2..9f4c7ce6364 100644 --- a/document_page_reference/views/document_page.xml +++ b/document_page_reference/views/document_page.xml @@ -27,6 +27,7 @@ + document.page.menu.form document.page @@ -44,6 +45,7 @@ + document.page.search (in knowledge_reference) document.page @@ -54,6 +56,7 @@ + document.page.tree (in knowledge_reference) document.page diff --git a/document_page_reference/views/report_document_page.xml b/document_page_reference/views/report_document_page.xml index 5ecaa1509ee..47ff1298d90 100644 --- a/document_page_reference/views/report_document_page.xml +++ b/document_page_reference/views/report_document_page.xml @@ -8,7 +8,7 @@ 1==0 -
+ From 8fd2d656a83e9b5b2bfd76604faee26d61cb56fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Thu, 21 Aug 2025 12:26:49 +0200 Subject: [PATCH 23/27] [IMP+FIX] document_page_reference: Refactor code related to https://github.com/OCA/knowledge/pull/550#issuecomment-3108953008 TT55514 --- document_page_reference/README.rst | 4 +- document_page_reference/__manifest__.py | 2 +- .../migrations/18.0.1.1.0/post-migration.py | 12 ++++ .../models/document_page.py | 56 +++++++------------ document_page_reference/readme/USAGE.md | 2 +- .../static/description/index.html | 4 +- .../tests/test_document_reference.py | 26 ++++++--- .../views/report_document_page.xml | 4 +- 8 files changed, 57 insertions(+), 53 deletions(-) create mode 100644 document_page_reference/migrations/18.0.1.1.0/post-migration.py diff --git a/document_page_reference/README.rst b/document_page_reference/README.rst index 6b7a0bfe783..791713790ef 100644 --- a/document_page_reference/README.rst +++ b/document_page_reference/README.rst @@ -11,7 +11,7 @@ Document Page Reference !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:f5181a95b68e52c04e1b76ba19f503b5bfc8d365939debe14edbb40413101321 + !! source digest: sha256:3081d597b92f6ce09c2259ce73d935f1215e5c8a1b171983c8d2c727c133a38e !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -43,7 +43,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. diff --git a/document_page_reference/__manifest__.py b/document_page_reference/__manifest__.py index 988fc2235d9..90da7ccb981 100644 --- a/document_page_reference/__manifest__.py +++ b/document_page_reference/__manifest__.py @@ -5,7 +5,7 @@ "name": "Document Page Reference", "summary": """ Include references on document pages""", - "version": "18.0.1.0.0", + "version": "18.0.2.0.0", "license": "AGPL-3", "author": "Creu Blanca,Odoo Community Association (OCA)", "website": "https://github.com/OCA/knowledge", diff --git a/document_page_reference/migrations/18.0.1.1.0/post-migration.py b/document_page_reference/migrations/18.0.1.1.0/post-migration.py new file mode 100644 index 00000000000..0f15f1ebb2e --- /dev/null +++ b/document_page_reference/migrations/18.0.1.1.0/post-migration.py @@ -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) diff --git a/document_page_reference/models/document_page.py b/document_page_reference/models/document_page.py index f745ee41b6f..6fb1a481cd6 100644 --- a/document_page_reference/models/document_page.py +++ b/document_page_reference/models/document_page.py @@ -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 @@ 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): @@ -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"{sanitized_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"{html_escape(doc.display_name)}" + f"" + f"{name}" ) def get_raw_content(self): diff --git a/document_page_reference/readme/USAGE.md b/document_page_reference/readme/USAGE.md index 6fa5061fe50..6a9530ff58f 100644 --- a/document_page_reference/readme/USAGE.md +++ b/document_page_reference/readme/USAGE.md @@ -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. diff --git a/document_page_reference/static/description/index.html b/document_page_reference/static/description/index.html index a2cd632afe4..085d95237ed 100644 --- a/document_page_reference/static/description/index.html +++ b/document_page_reference/static/description/index.html @@ -372,7 +372,7 @@

Document Page Reference

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:f5181a95b68e52c04e1b76ba19f503b5bfc8d365939debe14edbb40413101321 +!! source digest: sha256:3081d597b92f6ce09c2259ce73d935f1215e5c8a1b171983c8d2c727c133a38e !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/knowledge Translate me on Weblate Try me on Runboat

This module allows to add a reference name on documents and simplifies @@ -392,7 +392,7 @@

Document Page Reference

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.

diff --git a/document_page_reference/tests/test_document_reference.py b/document_page_reference/tests/test_document_reference.py index 73581b8b9dc..7ef9b644c03 100644 --- a/document_page_reference/tests/test_document_reference.py +++ b/document_page_reference/tests/test_document_reference.py @@ -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 @@ 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") @@ -52,7 +64,6 @@ 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")], } @@ -60,6 +71,5 @@ 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 = "

" - self.page1._compute_content_parsed() - self.assertEqual(str(self.page1.content_parsed), "

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

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

")) diff --git a/document_page_reference/views/report_document_page.xml b/document_page_reference/views/report_document_page.xml index 47ff1298d90..de2e43795fa 100644 --- a/document_page_reference/views/report_document_page.xml +++ b/document_page_reference/views/report_document_page.xml @@ -4,10 +4,10 @@ id="report_documentpage_doc" inherit_id="document_page.report_documentpage_doc" > - + 1==0 - + From 7c083da31c9896590727dff169610085ced9c45c Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Wed, 17 Dec 2025 09:55:54 +0100 Subject: [PATCH 24/27] [FIX] document_page_reference: Fix tests to use HTML --- document_page_reference/README.rst | 2 +- document_page_reference/__manifest__.py | 2 +- document_page_reference/static/description/index.html | 2 +- document_page_reference/tests/test_document_reference.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/document_page_reference/README.rst b/document_page_reference/README.rst index 791713790ef..83c3b5aaf60 100644 --- a/document_page_reference/README.rst +++ b/document_page_reference/README.rst @@ -11,7 +11,7 @@ Document Page Reference !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:3081d597b92f6ce09c2259ce73d935f1215e5c8a1b171983c8d2c727c133a38e + !! source digest: sha256:654977301f8ef4677d564b8e8401b7bf91b49393ee291bcee15012eaa42d7a8f !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/document_page_reference/__manifest__.py b/document_page_reference/__manifest__.py index 90da7ccb981..01a3fe116cb 100644 --- a/document_page_reference/__manifest__.py +++ b/document_page_reference/__manifest__.py @@ -5,7 +5,7 @@ "name": "Document Page Reference", "summary": """ Include references on document pages""", - "version": "18.0.2.0.0", + "version": "18.0.2.1.0", "license": "AGPL-3", "author": "Creu Blanca,Odoo Community Association (OCA)", "website": "https://github.com/OCA/knowledge", diff --git a/document_page_reference/static/description/index.html b/document_page_reference/static/description/index.html index 085d95237ed..579f5fc48a7 100644 --- a/document_page_reference/static/description/index.html +++ b/document_page_reference/static/description/index.html @@ -372,7 +372,7 @@

Document Page Reference

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:3081d597b92f6ce09c2259ce73d935f1215e5c8a1b171983c8d2c727c133a38e +!! source digest: sha256:654977301f8ef4677d564b8e8401b7bf91b49393ee291bcee15012eaa42d7a8f !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/knowledge Translate me on Weblate Try me on Runboat

This module allows to add a reference name on documents and simplifies diff --git a/document_page_reference/tests/test_document_reference.py b/document_page_reference/tests/test_document_reference.py index 7ef9b644c03..20ec5aa9464 100644 --- a/document_page_reference/tests/test_document_reference.py +++ b/document_page_reference/tests/test_document_reference.py @@ -51,8 +51,8 @@ def test_auto_reference(self): new_page_duplicated_name = self.page_obj.create( { "name": "test page with no reference", - "content": "this should have an empty reference " - "because reference must be unique", + "content": "

this should have an empty reference " + "because reference must be unique

", } ) self.assertFalse(new_page_duplicated_name.reference) From 58b9240af87ac80af08a4c52c7c15d99aed4f6ea Mon Sep 17 00:00:00 2001 From: Grzegorz Rutecki Date: Mon, 16 Mar 2026 11:33:11 +0100 Subject: [PATCH 25/27] [MIG] document_page_reference: Migration to 19.0 --- document_page_reference/__manifest__.py | 2 +- document_page_reference/static/src/js/editor.esm.js | 1 - setup/document_page_reference/pyproject.toml | 7 +++++++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 setup/document_page_reference/pyproject.toml diff --git a/document_page_reference/__manifest__.py b/document_page_reference/__manifest__.py index 01a3fe116cb..e1f8b1f838a 100644 --- a/document_page_reference/__manifest__.py +++ b/document_page_reference/__manifest__.py @@ -5,7 +5,7 @@ "name": "Document Page Reference", "summary": """ Include references on document pages""", - "version": "18.0.2.1.0", + "version": "19.0.2.1.0", "license": "AGPL-3", "author": "Creu Blanca,Odoo Community Association (OCA)", "website": "https://github.com/OCA/knowledge", diff --git a/document_page_reference/static/src/js/editor.esm.js b/document_page_reference/static/src/js/editor.esm.js index 416bfb851f4..0bbfa7bbdbb 100644 --- a/document_page_reference/static/src/js/editor.esm.js +++ b/document_page_reference/static/src/js/editor.esm.js @@ -9,7 +9,6 @@ class DocumentPageReferenceField extends HtmlField { this.orm = useService("orm"); this.action = useService("action"); onMounted(() => { - // eslint-disable-next-line no-undef const links = document.querySelectorAll(".oe_direct_line"); links.forEach((link) => { link.addEventListener("click", (event) => diff --git a/setup/document_page_reference/pyproject.toml b/setup/document_page_reference/pyproject.toml new file mode 100644 index 00000000000..e2346ba6966 --- /dev/null +++ b/setup/document_page_reference/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "odoo-addon-document_page_reference" +dynamic = ["version"] + +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" From add72becfbbb14935debcd7fd0fba9935fe68be4 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 19 Jun 2026 07:55:48 -0400 Subject: [PATCH 26/27] [FIX] document_page_reference: render reference links on 19.0 19.0 Html fields return Markup; Markup.replace(token, plain_str) escapes the injected anchor, so content_parsed stored the as literal text and no .oe_direct_line element rendered. Restore 18.0's get_content shape: seed with Markup(raw), return Markup() from _resolve_reference (+ backend_url href and the second pass that re-resolves already-rendered links). Bind the click handler via one delegated useExternalListener instead of per-node onMounted listeners. --- .../models/document_page.py | 20 ++++++++++++---- .../static/src/js/editor.esm.js | 24 +++++++++++-------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/document_page_reference/models/document_page.py b/document_page_reference/models/document_page.py index 6fb1a481cd6..d3ba55fab9b 100644 --- a/document_page_reference/models/document_page.py +++ b/document_page_reference/models/document_page.py @@ -50,12 +50,23 @@ def _get_document(self, code): def get_content(self): self.ensure_one() - content_parsed = raw = self.content or "" + raw = str(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) ) + 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( + Markup(full_link), self._resolve_reference(reference) + ) return content_parsed def _resolve_reference(self, code): @@ -66,8 +77,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/static/src/js/editor.esm.js b/document_page_reference/static/src/js/editor.esm.js index 0bbfa7bbdbb..e8ff07dbf47 100644 --- a/document_page_reference/static/src/js/editor.esm.js +++ b/document_page_reference/static/src/js/editor.esm.js @@ -1,6 +1,6 @@ import {HtmlField, htmlField} from "@web/views/fields/html/html_field"; -import {onMounted} from "@odoo/owl"; import {registry} from "@web/core/registry"; +import {useExternalListener} from "@odoo/owl"; import {useService} from "@web/core/utils/hooks"; class DocumentPageReferenceField extends HtmlField { @@ -8,18 +8,22 @@ class DocumentPageReferenceField extends HtmlField { super.setup(); this.orm = useService("orm"); this.action = useService("action"); - onMounted(() => { - const links = document.querySelectorAll(".oe_direct_line"); - links.forEach((link) => { - link.addEventListener("click", (event) => - this._onClickDirectLink(event) - ); - }); + // Delegate: one listener that resolves the link at click time, so it + // survives the html field re-rendering and is auto-removed on unmount. + useExternalListener(document, "click", (event) => { + const link = event.target.closest?.(".oe_direct_line"); + if (link) { + this._onClickDirectLink(event, link); + } }); } - _onClickDirectLink(event) { - const {oeModel: model, oeId} = event.target.dataset; + _onClickDirectLink(event, link) { + const {oeModel: model, oeId} = link.dataset; const id = parseInt(oeId, 10); + if (!model || !id) { + return; + } + event.preventDefault(); this.orm.call(model, "get_formview_action", [[id]], {}).then((action) => { this.action.doAction(action); }); From e0ba6b4e82922df587ae71401bd38e58d61511e9 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 19 Jun 2026 11:28:35 -0400 Subject: [PATCH 27/27] [FIX] document_page_reference: render content_parsed in OWL form Replace the dead oe_read_only/oe_edit_only CSS toggle (an open record is always o_form_editable in OWL, so content_parsed never showed) with readonly content_parsed + invisible/required content. Companion to the Markup-render fix. --- document_page_reference/README.rst | 10 +++++----- .../static/description/index.html | 6 +++--- document_page_reference/views/document_page.xml | 17 +++++++++-------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/document_page_reference/README.rst b/document_page_reference/README.rst index 83c3b5aaf60..169f919762e 100644 --- a/document_page_reference/README.rst +++ b/document_page_reference/README.rst @@ -21,13 +21,13 @@ Document Page Reference :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 - :target: https://github.com/OCA/knowledge/tree/18.0/document_page_reference + :target: https://github.com/OCA/knowledge/tree/19.0/document_page_reference :alt: OCA/knowledge .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/knowledge-18-0/knowledge-18-0-document_page_reference + :target: https://translation.odoo-community.org/projects/knowledge-19-0/knowledge-19-0-document_page_reference :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/knowledge&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/knowledge&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -53,7 +53,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -91,6 +91,6 @@ Current `maintainer `__: |maintainer-etobella| -This module is part of the `OCA/knowledge `_ project on GitHub. +This module is part of the `OCA/knowledge `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/document_page_reference/static/description/index.html b/document_page_reference/static/description/index.html index 579f5fc48a7..8dd6cf7cce4 100644 --- a/document_page_reference/static/description/index.html +++ b/document_page_reference/static/description/index.html @@ -374,7 +374,7 @@

Document Page Reference

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:654977301f8ef4677d564b8e8401b7bf91b49393ee291bcee15012eaa42d7a8f !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/knowledge Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/knowledge Translate me on Weblate Try me on Runboat

This module allows to add a reference name on documents and simplifies the link between document pages.

Table of contents

@@ -401,7 +401,7 @@

Bug Tracker

Bugs are tracked on GitHub Issues. 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 -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -429,7 +429,7 @@

Maintainers

promote its widespread use.

Current maintainer:

etobella

-

This module is part of the OCA/knowledge project on GitHub.

+

This module is part of the OCA/knowledge project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/document_page_reference/views/document_page.xml b/document_page_reference/views/document_page.xml index 9f4c7ce6364..7b9f7cb0953 100644 --- a/document_page_reference/views/document_page.xml +++ b/document_page_reference/views/document_page.xml @@ -15,16 +15,17 @@ />
- - oe_edit_only - + + type == 'category' + type != 'category' +
@@ -33,16 +34,16 @@ document.page - - oe_edit_only - + + 1 +