63 lines
3.1 KiB
Python
63 lines
3.1 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import contextlib
|
|
|
|
from odoo import api, models
|
|
from odoo.exceptions import AccessError
|
|
|
|
|
|
class IrUiMenu(models.Model):
|
|
_inherit = 'ir.ui.menu'
|
|
|
|
@api.model
|
|
def _get_best_backend_root_menu_id_for_model(self, res_model):
|
|
"""Get the best menu root id for the given res_model and the access
|
|
rights of the user.
|
|
|
|
When a link to a model was sent to a user it was targeting a page without
|
|
menu, so it was hard for the user to act on it.
|
|
The goal of this method is to find the best suited menu to display on a
|
|
page of a given model.
|
|
|
|
Technically, the method tries to find a menu root which has a sub menu
|
|
visible to the user that has an action linked to the given model.
|
|
If there is more than one possibility, it chooses the preferred one based
|
|
on the following preference function that determine the sub-menu from which
|
|
the root menu is extracted:
|
|
- favor the sub-menu linked to an action having a path as it probably indicates
|
|
a "major" action
|
|
- then favor the sub-menu with the smallest menu id as it probably indicates
|
|
that it belongs to the main module of the model and not a sub-one.
|
|
|
|
:param str res_model: the model name for which we want to find the best
|
|
menu root id
|
|
:return (int): the best menu root id or None if not found
|
|
"""
|
|
with contextlib.suppress(AccessError): # if no access to the menu, return None
|
|
visible_menu_ids = self._visible_menu_ids()
|
|
# Try first to get a menu root from the model implementation (take the less specialized i.e. the first one)
|
|
menu_root_candidates = self.env[res_model]._get_backend_root_menu_ids()
|
|
menu_root_id = next((m_id for m_id in menu_root_candidates if m_id in visible_menu_ids), None)
|
|
if menu_root_id:
|
|
return menu_root_id
|
|
|
|
# No menu root could be found by interrogating the model so fall back to a simple heuristic
|
|
# Prefetch menu fields and all menu's actions of type act_window
|
|
menus = self.env['ir.ui.menu'].browse(visible_menu_ids)
|
|
self.env['ir.actions.act_window'].sudo().browse([
|
|
int(menu['action'].split(',')[1])
|
|
for menu in menus.read(['action', 'parent_path'])
|
|
if menu['action'] and menu['action'].startswith('ir.actions.act_window,')
|
|
]).filtered('res_model')
|
|
|
|
def _menu_sort_key(menu_action):
|
|
menu, action = menu_action
|
|
return 1 if action.path else 0, -menu.id
|
|
|
|
menu_sudo = max((
|
|
(menu, action) for menu in menus.sudo() for action in (menu.action,)
|
|
if action and action.type == 'ir.actions.act_window' and action.res_model == res_model
|
|
and all(int(menu_id) in visible_menu_ids for menu_id in menu.parent_path.split('/') if menu_id)
|
|
), key=_menu_sort_key, default=(None, None))[0]
|
|
return int(menu_sudo.parent_path[:menu_sudo.parent_path.index('/')]) if menu_sudo else None
|