[IMP] base: batch access rules for visible_menu

Visible_menu check per_read model per model leading to a lot of queries
We can get all readable fields with one query

Computation time goes from ~400 total to less than ~50 total
The different would be even bigger on a server with a distant database.
(higher ping)

Part-of: odoo/odoo#99176
This commit is contained in:
Xavier-Do
2022-09-12 13:48:59 +02:00
parent 93a9eb0a32
commit 31b04aa44b
2 changed files with 31 additions and 26 deletions
+30 -25
View File
@@ -1752,42 +1752,47 @@ class IrModelAccess(models.Model):
# But as the method raises an exception in that case, the key 'lang' might
# not be really necessary as a cache key, unless the `ormcache_context`
# decorator catches the exception (it does not at the moment.)
@tools.ormcache('self.env.uid', 'mode')
def _get_allowed_models(self, mode='read'):
assert mode in ('read', 'write', 'create', 'unlink'), 'Invalid access mode'
self.flush_model()
self.env.cr.execute(f"""
SELECT m.model
FROM ir_model_access a
JOIN ir_model m ON (m.id = a.model_id)
WHERE a.perm_{mode}
AND a.active
AND (
a.group_id IS NULL OR
-- use subselect fo force a better query plan. See #99695 --
a.group_id IN (
SELECT gu.gid
FROM res_groups_users_rel gu
WHERE gu.uid = %s
)
)
GROUP BY m.model
""", (self.env.uid,))
return frozenset(v[0] for v in self.env.cr.fetchall())
@api.model
@tools.ormcache_context('self.env.uid', 'self.env.su', 'model', 'mode', 'raise_exception', keys=('lang',))
def check(self, model, mode='read', raise_exception=True):
if self.env.su:
# User root have all accesses
return True
assert isinstance(model, str), 'Not a model name: %s' % (model,)
assert mode in ('read', 'write', 'create', 'unlink'), 'Invalid access mode'
# TransientModel records have no access rights, only an implicit access rule
if model not in self.env:
_logger.error('Missing model %s', model)
self.flush_model()
has_access = model in self._get_allowed_models(mode)
# We check if a specific or generic rule exists
self._cr.execute("""SELECT BOOL_OR(perm_{mode})
FROM ir_model_access a
JOIN ir_model m ON (m.id = a.model_id)
WHERE m.model = %s
AND a.active IS TRUE
AND (
a.group_id IS NULL OR
-- use subselect fo force a better query plan. See #99695 --
a.group_id IN (
SELECT gu.gid
FROM res_groups_users_rel gu
WHERE gu.uid = %s
)
)
""".format(mode=mode),
(model, self._uid,))
r = self._cr.fetchone()[0]
if not r and raise_exception:
if not has_access and raise_exception:
groups = '\n'.join('\t- %s' % g for g in self.group_names_with_access(model, mode))
document_kind = self.env['ir.model']._get(model).name or model
msg_heads = {
@@ -1834,7 +1839,7 @@ class IrModelAccess(models.Model):
raise AccessError(msg)
return bool(r)
return has_access
__cache_clearing_methods = set()
@@ -1849,7 +1854,7 @@ class IrModelAccess(models.Model):
@api.model
def call_cache_clearing_methods(self):
self.env.invalidate_all()
self.check.clear_cache(self) # clear the cache of check function
self._get_allowed_models.clear_cache(self) # clear the cache of check function
for model, method in self.__cache_clearing_methods:
if model in self.env:
getattr(self.env[model], method)()
+1 -1
View File
@@ -382,7 +382,7 @@ from odoo import api, models, tools
from odoo.tools import config, safe_eval, pycompat, SUPPORTED_DEBUGGER
from odoo.tools.safe_eval import assert_valid_codeobj, _BUILTINS, to_opcodes, _EXPR_OPCODES, _BLACKLIST
from odoo.tools.json import scriptsafe
from odoo.tools.misc import get_lang, str2bool
from odoo.tools.misc import str2bool
from odoo.tools.image import image_data_uri
from odoo.http import request
from odoo.modules.module import get_resource_path