132 lines
5.3 KiB
Python
132 lines
5.3 KiB
Python
from passlib.apps import master_context
|
|
|
|
from odoo import models, fields, api, tools, _
|
|
from collections import defaultdict
|
|
from odoo.http import request
|
|
|
|
class IrUiMenu(models.Model):
|
|
_inherit = 'ir.ui.menu'
|
|
|
|
@api.model
|
|
@tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'debug')
|
|
def _visible_menu_ids(self, debug=False):
|
|
"""Return the IDs of menu items visible to the current user based on permissions and active master."""
|
|
# Clear existing cache to ensure fresh menu visibility calculation
|
|
self.env['ir.ui.menu'].sudo().clear_caches()
|
|
|
|
# Retrieve all menus with required fields
|
|
context = {'ir.ui.menu.full_list': True}
|
|
menus = self.with_context(context).search_fetch([], ['action', 'parent_id']).sudo()
|
|
|
|
# Get active master and control configuration
|
|
active_master = request.session.get('active_master')
|
|
master_control = False
|
|
control_unit = False
|
|
|
|
if active_master:
|
|
master_control = self.env['master.control'].sudo().search(
|
|
[('code', '=', active_master)], limit=1
|
|
)
|
|
if master_control:
|
|
control_unit = self.env['menu.access.control'].sudo().search([
|
|
('user_ids', 'ilike', self.env.user.id),
|
|
('master_control', '=', master_control.id)
|
|
])
|
|
|
|
# Get user groups and exclude technical group in non-debug mode
|
|
group_ids = set(self.env.user._get_group_ids())
|
|
if not debug:
|
|
group_ids -= {
|
|
self.env['ir.model.data']._xmlid_to_res_id(
|
|
'base.group_no_one', raise_if_not_found=False
|
|
)
|
|
}
|
|
|
|
# Filter menus by group permissions
|
|
menus = menus.filtered(
|
|
lambda menu: not (menu.groups_id and group_ids.isdisjoint(menu.groups_id._ids))
|
|
)
|
|
|
|
# Determine menus to hide based on access control
|
|
hide_menus_list = self._get_hidden_menu_ids(control_unit, master_control, debug)
|
|
menus = menus.filtered(lambda menu: menu.id not in hide_menus_list)
|
|
|
|
# Process menus with actions
|
|
visible = self._process_action_menus(menus)
|
|
|
|
return set(visible.ids)
|
|
|
|
def _get_hidden_menu_ids(self, control_unit, master_control, debug):
|
|
"""Helper method to determine menu IDs that should be hidden from the user."""
|
|
if debug and control_unit:
|
|
# In debug mode with control unit, use its specific menu restrictions
|
|
parent_menus = control_unit.access_menu_line_ids.filtered(
|
|
lambda menu: not menu.is_main_menu
|
|
).menu_id.ids
|
|
sub_menus = control_unit.access_sub_menu_line_ids.filtered(
|
|
lambda menu: not menu.is_main_menu
|
|
).menu_id.ids
|
|
elif not debug:
|
|
# In non-debug mode, determine menus to hide based on control configuration
|
|
domain = [('user_ids', 'ilike', self.env.user.id)]
|
|
if master_control:
|
|
domain.append(('master_control', '=', master_control.id))
|
|
|
|
access_controls = self.env['menu.access.control'].sudo().search(domain)
|
|
parent_menus = access_controls.access_menu_line_ids.filtered(
|
|
lambda menu: not menu.is_main_menu
|
|
).menu_id.ids
|
|
sub_menus = access_controls.access_sub_menu_line_ids.filtered(
|
|
lambda menu: not menu.is_main_menu
|
|
).menu_id.ids
|
|
else:
|
|
# Default case: no menus to hide
|
|
return []
|
|
|
|
return list(set(parent_menus + sub_menus))
|
|
|
|
def _process_action_menus(self, menus):
|
|
"""Process menus with actions and determine visibility based on model access."""
|
|
# Separate menus with actions from folder menus
|
|
actions_by_model = defaultdict(set)
|
|
for action in menus.mapped('action'):
|
|
if action:
|
|
actions_by_model[action._name].add(action.id)
|
|
|
|
existing_actions = {
|
|
action
|
|
for model_name, action_ids in actions_by_model.items()
|
|
for action in self.env[model_name].browse(action_ids).exists()
|
|
}
|
|
|
|
action_menus = menus.filtered(lambda m: m.action and m.action in existing_actions)
|
|
folder_menus = menus - action_menus
|
|
visible = self.browse()
|
|
|
|
# Model access check configuration
|
|
access = self.env['ir.model.access']
|
|
MODEL_BY_TYPE = {
|
|
'ir.actions.act_window': 'res_model',
|
|
'ir.actions.report': 'model',
|
|
'ir.actions.server': 'model_name',
|
|
}
|
|
|
|
# Prefetch action data for performance
|
|
prefetch_ids = defaultdict(list)
|
|
for action in action_menus.mapped('action'):
|
|
prefetch_ids[action._name].append(action.id)
|
|
|
|
# Check access for each action menu
|
|
for menu in action_menus:
|
|
action = menu.action.with_prefetch(prefetch_ids[action._name])
|
|
model_name = action._name in MODEL_BY_TYPE and action[MODEL_BY_TYPE[action._name]]
|
|
|
|
if not model_name or access.check(model_name, 'read', False):
|
|
# Make menu visible and its folder ancestors
|
|
visible += menu
|
|
parent = menu.parent_id
|
|
while parent and parent in folder_menus and parent not in visible:
|
|
visible += parent
|
|
parent = parent.parent_id
|
|
|
|
return visible |