odoo18/addons_extensions/menu_control_center/models/menu.py

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