diff --git a/hide_portal_module_by_user/.gitignore b/hide_portal_module_by_user/.gitignore new file mode 100644 index 0000000..6da5887 --- /dev/null +++ b/hide_portal_module_by_user/.gitignore @@ -0,0 +1,2 @@ +*.*~ +*pyc diff --git a/hide_portal_module_by_user/README.rst b/hide_portal_module_by_user/README.rst new file mode 100644 index 0000000..42d4eaf --- /dev/null +++ b/hide_portal_module_by_user/README.rst @@ -0,0 +1,64 @@ +========================== +Hide Portal Module By User +========================== + +Show / Hide Specific Portal Docs on res.users + + +IMPORTANT +============= +!! IMPORTANT !! + +This module doesnt modify the access rights of the users, it only hides the documents. + +This Module will Allow to enter a specific document via URL even when the user does not have the group assigned to it. + + +REVIEW YOUR USE CASES + +Example: + +if you dont have the group assigned to it. + +- you can access to the document via URL: /my/orders/6?access_token=0f13c269-0f10-46dc-81f8-4db9682f2267 +- but not to my/orders + + +PLEASE MAKE A PR IF YOU I MISSED TO WHITELIST A ROUTE URL + +Usage +===== + +To use this module, you need to: + +#. Install and Configure the Groups and Users + +.. image:: static/description/img.png + :alt: hide_portal_module_by_user 1 + :width: 50% + +.. image:: static/description/img_1.png + :alt: hide_portal_module_by_user 2 + :width: 50% + +.. image:: static/description/img_2.png + :alt: hide_portal_module_by_user 3 + :width: 50% + +.. image:: static/description/img_3.png + :alt: hide_portal_module_by_user 24 + :width: 50% + +.. image:: static/description/img_4.png + :alt: hide_portal_module_by_user 24 + :width: 50% + +How it Works +===== +- At Installation, the module will search for all the views that inherits portal.portal_my_home and create a group based on the URL that its linked to in the anchor tag. +- Post Installation, the module will detect if a new view is created inheriting the portal home and create the group. +- We Add a validation at view level to hide the group and a validation when trying to list the portal documents. + + +Changelog +========= diff --git a/hide_portal_module_by_user/__init__.py b/hide_portal_module_by_user/__init__.py new file mode 100644 index 0000000..3d05e19 --- /dev/null +++ b/hide_portal_module_by_user/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import controllers +from .hooks import post_init_hook diff --git a/hide_portal_module_by_user/__manifest__.py b/hide_portal_module_by_user/__manifest__.py new file mode 100644 index 0000000..8dc68b5 --- /dev/null +++ b/hide_portal_module_by_user/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2026 Munin +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + 'name': 'Hide Portal Module By User', + 'description': """ + Show / Hide Specific Portal Docs on res.users""", + 'version': '16.0.1.0.0', + 'license': 'AGPL-3', + 'author': 'Munin', + 'website': 'https://github.com/AxeldelosReyes/odoo_web_modules', + 'depends': [ + 'base', 'portal','base_portal_type' + ], + 'data': [ + 'views/portal_home.xml', + 'views/res_groups.xml', + ], + 'demo': [ + ], + 'post_init_hook': 'post_init_hook', +} diff --git a/hide_portal_module_by_user/controllers/__init__.py b/hide_portal_module_by_user/controllers/__init__.py new file mode 100644 index 0000000..12a7e52 --- /dev/null +++ b/hide_portal_module_by_user/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/hide_portal_module_by_user/controllers/main.py b/hide_portal_module_by_user/controllers/main.py new file mode 100644 index 0000000..4c22add --- /dev/null +++ b/hide_portal_module_by_user/controllers/main.py @@ -0,0 +1,19 @@ +# Copyright 2026 Munin +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.http import request +from odoo.addons.portal.controllers.portal import CustomerPortal +from werkzeug.exceptions import NotFound + +WHITELISTED_ROUTES = ['/my/home', '/my', '/my/account', '/my/security', '/my/payment_method'] + + +class CustomerPortalPolicy(CustomerPortal): + def _prepare_portal_layout_values(self): + vals = super()._prepare_portal_layout_values() + current_path = request.httprequest.path + if request.env.user.validate_portal_url(current_path) or current_path in WHITELISTED_ROUTES: + return vals + if '/my/' not in current_path: + return vals + raise NotFound() diff --git a/hide_portal_module_by_user/hooks.py b/hide_portal_module_by_user/hooks.py new file mode 100644 index 0000000..4d453b2 --- /dev/null +++ b/hide_portal_module_by_user/hooks.py @@ -0,0 +1,17 @@ +from odoo import api, SUPERUSER_ID + + +def post_init_hook(cr, registry): + """Loaded after installing the module. + This module's DB modifications will be available. + :param odoo.sql_db.Cursor cr: + Database cursor. + :param odoo.modules.registry.RegistryManager registry: + Database registry, using v7 api. + """ + env = api.Environment(cr, SUPERUSER_ID, {}) + portal_views = env['ir.ui.view'].search([('inherit_id.xml_id', '=', 'portal.portal_my_home')]) + if portal_views: + for p in portal_views: + p.create_group_from_view() + return True diff --git a/hide_portal_module_by_user/models/__init__.py b/hide_portal_module_by_user/models/__init__.py new file mode 100644 index 0000000..2482d64 --- /dev/null +++ b/hide_portal_module_by_user/models/__init__.py @@ -0,0 +1,2 @@ +from . import res_users +from . import ir_ui_view diff --git a/hide_portal_module_by_user/models/ir_ui_view.py b/hide_portal_module_by_user/models/ir_ui_view.py new file mode 100644 index 0000000..f055486 --- /dev/null +++ b/hide_portal_module_by_user/models/ir_ui_view.py @@ -0,0 +1,68 @@ +# Copyright 2026 Munin +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from lxml import etree +from odoo import _, api, fields, models + + +class ResGroups(models.Model): + _inherit = "res.groups" + + portal_url = fields.Char(string='Portal URL', index=True) + + +class IrUiView(models.Model): + _inherit = "ir.ui.view" + + custom_portal_group = fields.Boolean(string='Custom Portal Group', default=False) + portal_group = fields.Many2one('res.groups', string='Portal Group') + + @api.model_create_multi + def create(self, vals_list): + res = super(IrUiView, self).create(vals_list) + for view in res: + if view.inherit_id.xml_id == 'portal.portal_my_home': + view.create_group_from_view() + return res + + def create_group_from_view(self): + self.ensure_one() + + def _generate_name(name): + return f"show_portal_{name}".lower().replace(' ', '_') + + view_xml = etree.fromstring(self.arch) + groups_from_view = {} + for option in view_xml.xpath("//t[@t-call='portal.portal_docs_entry']"): + has_title = option.find("t[@t-set='title']") + if has_title is not None: + title = has_title.text + else: + title = self.name + + k = _generate_name(title) + target_url = option.find("t[@t-set='url']") + if target_url is not None: + target_url = target_url.get('t-value').replace("'", '') + else: + raise ValueError(_("No target url found")) + groups_from_view[k] = target_url + + search_groups = self.env['res.groups'].sudo().search([('portal_url', 'in', list(groups_from_view.values()))]) + category_portal_type = self.env.ref('base_portal_type.category_portal_type') + base_user_group = self.env.ref('base.group_user') + + for group in groups_from_view.keys(): + portal_group = search_groups.filtered(lambda g: g.portal_url == groups_from_view[group]) + if not portal_group: + portal_group = self.env['res.groups'].sudo().create( + { + 'name': group, + 'portal_url': groups_from_view[group], + 'category_id': category_portal_type.id, + } + ) + if base_user_group and portal_group: + implied_ids = base_user_group.sudo().mapped('implied_ids.id') + if portal_group.id not in implied_ids: + base_user_group.sudo().write({'implied_ids': [(4, portal_group.id)]}) + return True diff --git a/hide_portal_module_by_user/models/res_users.py b/hide_portal_module_by_user/models/res_users.py new file mode 100644 index 0000000..70b5316 --- /dev/null +++ b/hide_portal_module_by_user/models/res_users.py @@ -0,0 +1,16 @@ +# Copyright 2026 Munin +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + portal_url = fields.Char(string='Portal URL') + + def validate_portal_url(self, url): + group = self.sudo().env['res.groups'].search([('portal_url', '=', url)]) + if self.env.user in group.users: + return True + return False diff --git a/hide_portal_module_by_user/static/description/icon.png b/hide_portal_module_by_user/static/description/icon.png new file mode 100644 index 0000000..06c0af4 Binary files /dev/null and b/hide_portal_module_by_user/static/description/icon.png differ diff --git a/hide_portal_module_by_user/static/description/img.png b/hide_portal_module_by_user/static/description/img.png new file mode 100644 index 0000000..0443a9a Binary files /dev/null and b/hide_portal_module_by_user/static/description/img.png differ diff --git a/hide_portal_module_by_user/static/description/img_1.png b/hide_portal_module_by_user/static/description/img_1.png new file mode 100644 index 0000000..7d8bae8 Binary files /dev/null and b/hide_portal_module_by_user/static/description/img_1.png differ diff --git a/hide_portal_module_by_user/static/description/img_2.png b/hide_portal_module_by_user/static/description/img_2.png new file mode 100644 index 0000000..716547f Binary files /dev/null and b/hide_portal_module_by_user/static/description/img_2.png differ diff --git a/hide_portal_module_by_user/static/description/img_3.png b/hide_portal_module_by_user/static/description/img_3.png new file mode 100644 index 0000000..4c5121a Binary files /dev/null and b/hide_portal_module_by_user/static/description/img_3.png differ diff --git a/hide_portal_module_by_user/static/description/img_4.png b/hide_portal_module_by_user/static/description/img_4.png new file mode 100644 index 0000000..e6983ec Binary files /dev/null and b/hide_portal_module_by_user/static/description/img_4.png differ diff --git a/hide_portal_module_by_user/static/description/index.html b/hide_portal_module_by_user/static/description/index.html new file mode 100644 index 0000000..bbba931 --- /dev/null +++ b/hide_portal_module_by_user/static/description/index.html @@ -0,0 +1,406 @@ + + + + + + +Hide Portal Module By User + + + +
+

Hide Portal Module By User

+ +

Show / Hide Specific Portal Docs on res.users

+
+

IMPORTANT

+

!! IMPORTANT !!

+

This module doesnt modify the access rights of the users, it only hides the documents.

+

This Module will Allow to enter a specific document via URL even when the user does not have the group assigned to it.

+

REVIEW YOUR USE CASES

+

Example:

+

if you dont have the group assigned to it.

+ +

PLEASE MAKE A PR IF YOU I MISSED TO WHITELIST A ROUTE URL

+
+
+

Usage

+

To use this module, you need to:

+
    +
  1. Install and Configure the Groups and Users
  2. +
+hide_portal_module_by_user 1 +hide_portal_module_by_user 2 +hide_portal_module_by_user 3 +hide_portal_module_by_user 24 +hide_portal_module_by_user 24 +
+
+

How it Works

+ +
+
+

Changelog

+
+
+ + diff --git a/hide_portal_module_by_user/views/portal_home.xml b/hide_portal_module_by_user/views/portal_home.xml new file mode 100644 index 0000000..8e74a28 --- /dev/null +++ b/hide_portal_module_by_user/views/portal_home.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/hide_portal_module_by_user/views/res_groups.xml b/hide_portal_module_by_user/views/res_groups.xml new file mode 100644 index 0000000..886168c --- /dev/null +++ b/hide_portal_module_by_user/views/res_groups.xml @@ -0,0 +1,28 @@ + + + + + + res.groups.form (in hide_portal_module_by_user) + res.groups + + + + + + + + + res.groups.search (in hide_portal_module_by_user) + res.groups + + + + + + + +