diff --git a/web_debranding/README.rst b/web_debranding/README.rst new file mode 100644 index 00000000..bea58c79 --- /dev/null +++ b/web_debranding/README.rst @@ -0,0 +1,32 @@ +================ +Web Debranding +================ + +This addon removes selected Odoo branding elements in the web backend. + +- Removes the About block from General Settings. +- Blanks the default browser tab title fallback (no "Odoo"). + +Usage +----- + +Install the module and open Settings to verify the About section is gone. +Open the web client and check the browser tab title no longer defaults to "Odoo". + +Bug Tracker +----------- + +If you encounter problems, please open an issue in the repository. + +Credits +------- + +Authors +~~~~~~~ + +- Odoo Community Association (OCA) + +Maintainers +~~~~~~~~~~~ + +This module is part of the OCA/server-brand project. diff --git a/web_debranding/__init__.py b/web_debranding/__init__.py new file mode 100644 index 00000000..b02b8b5f --- /dev/null +++ b/web_debranding/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2025 Milan Topuzov (https://milantopuzov.dev) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). diff --git a/web_debranding/__manifest__.py b/web_debranding/__manifest__.py new file mode 100644 index 00000000..8cdd6604 --- /dev/null +++ b/web_debranding/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2025 Milan Topuzov (https://milantopuzov.dev) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +{ + "name": "Web Debranding", + "summary": "Hide Odoo branding elements in web backend", + "version": "19.0.1.0.0", + "category": "Hidden", + "website": "https://github.com/OCA/server-brand", + "author": "Odoo Community Association (OCA)", + "license": "LGPL-3", + "depends": ["web", "base_setup"], + "data": [ + "views/res_config_settings_debrand.xml", + "views/web_layout_debrand.xml", + ], + "assets": {}, + "installable": True, +} diff --git a/web_debranding/pyproject.toml b/web_debranding/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/web_debranding/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/web_debranding/tests/__init__.py b/web_debranding/tests/__init__.py new file mode 100644 index 00000000..2ff9942f --- /dev/null +++ b/web_debranding/tests/__init__.py @@ -0,0 +1,11 @@ +"""Tests package for web_debranding. + +This package follows the same pattern as other modules in this repo, +importing local test modules so Odoo test discovery can load them. +""" + +# Copyright 2025 Milan Topuzov (https://milantopuzov.dev) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from . import test_web_debranding +from . import test_title_debranding diff --git a/web_debranding/tests/test_title_debranding.py b/web_debranding/tests/test_title_debranding.py new file mode 100644 index 00000000..28719100 --- /dev/null +++ b/web_debranding/tests/test_title_debranding.py @@ -0,0 +1,57 @@ +# Copyright 2025 Milan Topuzov (https://milantopuzov.dev) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from lxml import html + +from odoo import http +from odoo.tests import common + + +class _DummyRequest: + class _Session: + def __init__(self, debug=""): + self.debug = debug + + def __init__(self, debug=""): + # Minimal session object with a 'debug' attribute + self.session = self._Session(debug=debug) + + def csrf_token(self, _): + # Minimal CSRF call used by web.layout + return "" + + +class TestWebDebrandingTitle(common.TransactionCase): + def setUp(self): + super().setUp() + # Bind a dummy request into the http local stack so that the + # `odoo.http.request` LocalProxy resolves during QWeb evaluation. + self._dummy_request = _DummyRequest() + http._request_stack.push(self._dummy_request) + self.addCleanup(lambda: http._request_stack.pop()) + + def test_web_layout_title_no_fallback(self): + """web.layout should not fallback to 'Odoo' when no title is provided. + + Our view inherit replaces the fallback with an empty string. + Validate that the rendered document title is empty and contains no 'Odoo'. + """ + content = self.env["ir.ui.view"]._render_template("web.layout", values={}) + doc = html.fromstring(content) + titles = doc.xpath("//title") + self.assertTrue(titles, "Rendered web.layout should contain a <title> tag") + title_text = (titles[0].text or "").strip() + self.assertEqual(title_text, "") + self.assertNotIn("Odoo", title_text) + + def test_web_layout_title_with_value(self): + """When a title value is provided, it should be rendered as-is.""" + expected = "My Custom Title" + content = self.env["ir.ui.view"]._render_template( + "web.layout", values={"title": expected} + ) + doc = html.fromstring(content) + titles = doc.xpath("//title") + self.assertTrue(titles, "Rendered web.layout should contain a <title> tag") + title_text = (titles[0].text or "").strip() + self.assertEqual(title_text, expected) diff --git a/web_debranding/tests/test_web_debranding.py b/web_debranding/tests/test_web_debranding.py new file mode 100644 index 00000000..ee09676c --- /dev/null +++ b/web_debranding/tests/test_web_debranding.py @@ -0,0 +1,22 @@ +# Copyright 2025 Milan Topuzov (https://milantopuzov.dev) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from lxml import etree + +from odoo.tests import common + + +class TestWebDebranding(common.TransactionCase): + def test_settings_about_section_hidden(self): + """The About block in Settings form should be hidden.""" + conf = self.env["res.config.settings"].create({}) + view = conf.get_views([[False, "form"]])["views"]["form"] + doc = etree.XML(view["arch"]) + + about_nodes = doc.xpath("//div[@id='about']") + self.assertTrue(about_nodes, "About section container should exist in the view") + self.assertIn( + "invisible", + about_nodes[0].attrib, + "About section should be hidden via invisible='1'", + ) diff --git a/web_debranding/views/res_config_settings_debrand.xml b/web_debranding/views/res_config_settings_debrand.xml new file mode 100644 index 00000000..aebfbbda --- /dev/null +++ b/web_debranding/views/res_config_settings_debrand.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<odoo> + <!-- Hide the About section in General Settings --> + <record id="view_res_config_settings_debrand_about" model="ir.ui.view"> + <field name="name">res.config.settings.form.debrand.about</field> + <field name="model">res.config.settings</field> + <field name="priority" eval="100" /> + <field name="inherit_id" ref="base_setup.res_config_settings_view_form" /> + <field name="arch" type="xml"> + <xpath expr="//div[@id='about']" position="attributes"> + <attribute name="invisible">1</attribute> + </xpath> + </field> + </record> +</odoo> diff --git a/web_debranding/views/web_layout_debrand.xml b/web_debranding/views/web_layout_debrand.xml new file mode 100644 index 00000000..d9975445 --- /dev/null +++ b/web_debranding/views/web_layout_debrand.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8" ?> +<odoo> + <!-- Remove default 'Odoo' fallback from page title --> + <template + id="web_layout_debrand_title" + inherit_id="web.layout" + name="Web layout debrand title" + > + <xpath expr="//title" position="attributes"> + <attribute name="t-esc">title or ''</attribute> + </xpath> + </template> +</odoo>