odoo18/addons_extensions/menu_control_center/models/menu.py

282 lines
10 KiB
Python

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()