from collections import defaultdict from odoo import api, models, tools from odoo.http import request class IrUiMenu(models.Model): _inherit = 'ir.ui.menu' def _get_active_master_code(self): return (request.session.get('active_master') if request else False) or self.env.context.get('active_master') def _get_active_master_control(self): active_master_code = self._get_active_master_code() return self.env['master.control'].sudo().search([('code', '=', active_master_code)], limit=1) if active_master_code else False @api.model @tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'debug', 'self.env.uid', 'self._get_active_master_code() or ""') def _visible_menu_ids(self, debug=False): context = {'ir.ui.menu.full_list': True} menus = self.with_context(context).search_fetch([], ['action', 'parent_id']).sudo() master_control = self._get_active_master_control() 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) } menus = menus.filtered( lambda menu: not (menu.groups_id and group_ids.isdisjoint(menu.groups_id._ids)) ) if master_control: if master_control.user_ids and self.env.user not in master_control.user_ids: menus = self.browse() else: hidden_menu_ids = self._get_master_hidden_menu_ids(master_control) | self._get_hidden_menu_ids(master_control) if hidden_menu_ids: menus = menus.filtered(lambda menu: menu.id not in hidden_menu_ids) visible = self._process_action_menus(menus) return set(visible.ids) def _get_master_menu_order_map(self, master_control): if not master_control or not master_control.custom_menu_order: return {} visible_lines = master_control.order_menu_line_ids.filtered('show_menu').sorted( key=lambda line: ( line.custom_order_sequence, line.menu_id.sequence, line.menu_id.name or '', line.menu_id.id, ) ) return {line.menu_id.id: index for index, line in enumerate(visible_lines)} def _get_master_menu_icon_map(self, master_control): if not master_control or not master_control.custom_menu_icons: return {} visible_lines = ( master_control.order_menu_line_ids ).filtered( lambda l: l.show_menu and l.menu_icon ) return { line.menu_id.id: ( line.menu_icon.decode('utf-8') if isinstance(line.menu_icon, bytes) else line.menu_icon ) for line in visible_lines } def _get_master_menu_rename_map(self, master_control): if not master_control or not master_control.enable_rename_option: return {} visible_lines = ( master_control.rename_menu_line_ids + master_control.rename_submenu_line_ids ).filtered( lambda l: l.show_menu and l.menu_custom_name ) return { line.menu_id.id: line.menu_custom_name for line in visible_lines } def _sort_root_menu_ids(self, menu_ids, master_control): order_map = self._get_master_menu_order_map(master_control) if not order_map: return menu_ids default_positions = {menu_id: index for index, menu_id in enumerate(menu_ids)} return sorted( menu_ids, key=lambda menu_id: ( order_map.get(menu_id, len(order_map)), default_positions[menu_id], ) ) def _sort_root_menu_dicts(self, menus, master_control): order_map = self._get_master_menu_order_map(master_control) if not order_map: return menus default_positions = {menu['id']: index for index, menu in enumerate(menus)} return sorted( menus, key=lambda menu: ( order_map.get(menu['id'], len(order_map)), default_positions[menu['id']], ) ) @api.model def load_menus_root(self): root = super().load_menus_root() visible_ids = self._visible_menu_ids(request.session.debug if request else False) master_control = self._get_active_master_control() rename_map = self._get_master_menu_rename_map(master_control) icon_map = self._get_master_menu_icon_map(master_control) root['children'] = [ child for child in root.get('children', []) if child['id'] in visible_ids ] for child in root['children']: if child['id'] in rename_map: child['name'] = rename_map[child['id']] if child['id'] in icon_map: child['web_icon_data'] = icon_map[child['id']] root['children'] = self._sort_root_menu_dicts(root['children'], master_control) root['all_menu_ids'] = [ menu_id for menu_id in root.get('all_menu_ids', []) if menu_id in visible_ids ] return root @api.model def load_menus(self, debug): all_menus = super().load_menus(debug) visible_ids = self._visible_menu_ids(debug) master_control = self._get_active_master_control() rename_map = self._get_master_menu_rename_map(master_control) icon_map = self._get_master_menu_icon_map(master_control) filtered_menus = {'root': dict(all_menus['root'])} for menu_id, menu_data in all_menus.items(): if menu_id == 'root': continue if menu_id in visible_ids: filtered_menus[menu_id] = dict(menu_data) filtered_menus['root']['children'] = [ child_id for child_id in filtered_menus['root'].get('children', []) if child_id in filtered_menus ] filtered_menus['root']['children'] = self._sort_root_menu_ids( filtered_menus['root']['children'], master_control, ) for menu_id, menu_data in list(filtered_menus.items()): if menu_id == 'root': continue menu_data['children'] = [ child_id for child_id in menu_data.get('children', []) if child_id in filtered_menus ] for menu_id, menu_data in filtered_menus.items(): if menu_id == 'root': continue if menu_id in rename_map: menu_data['name'] = rename_map[menu_id] if menu_id in icon_map: menu_data['web_icon_data'] = icon_map[menu_id] return filtered_menus def _get_hidden_menu_ids(self, master_control): access_controls = self.env['menu.access.control'].sudo().search([ ('master_control_id', '=', master_control.id), ('user_ids', 'in', self.env.user.id), ]) hidden_lines = (access_controls.access_menu_line_ids | access_controls.access_sub_menu_line_ids).filtered( lambda line: not line.show_menu ) hidden_menu_ids = hidden_lines.mapped('menu_id').ids if not hidden_menu_ids: return set() return set( self.env['ir.ui.menu'] .sudo() .with_context({'ir.ui.menu.full_list': True}) .search([('id', 'child_of', hidden_menu_ids)]).ids ) def _get_master_hidden_menu_ids(self, master_control): hidden_lines = (master_control.menu_line_ids | master_control.submenu_line_ids).filtered( lambda line: not line.show_menu ) hidden_menu_ids = hidden_lines.mapped('menu_id').ids if not hidden_menu_ids: return set() return set( self.env['ir.ui.menu'] .sudo() .with_context({'ir.ui.menu.full_list': True}) .search([('id', 'child_of', hidden_menu_ids)]).ids ) def _process_action_menus(self, 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 menu: menu.action and menu.action in existing_actions) folder_menus = menus - action_menus visible = self.browse() access_model = self.env['ir.model.access'] model_by_type = { 'ir.actions.act_window': 'res_model', 'ir.actions.report': 'model', 'ir.actions.server': 'model_name', } prefetch_ids = defaultdict(list) for action in action_menus.mapped('action'): prefetch_ids[action._name].append(action.id) 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_model.check(model_name, 'read', False): 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 def unlink(self): # Clean up references before deleting menus menu_ids = self.ids # Delete master control menu lines self.env['master.control.menu.line'].sudo().search([ ('menu_id', 'in', menu_ids) ]).unlink() # Delete menu access lines self.env['menu.access.line'].sudo().search([ ('menu_id', 'in', menu_ids) ]).unlink() # Clear caches self.clear_caches() return super().unlink()