diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG
index 3165388392..59be57558a 100644
--- a/trytond/CHANGELOG
+++ b/trytond/CHANGELOG
@@ -1,3 +1,4 @@
+* Include context information in access error
* Remove trytond-worker max tasks per child option for Python <= 3.10
* Do not copy readonly relational fields
* Support list of single value in sortable_values tools
diff --git a/trytond/trytond/ir/message.xml b/trytond/trytond/ir/message.xml
index 1235e0dff9..64d87b19ee 100644
--- a/trytond/trytond/ir/message.xml
+++ b/trytond/trytond/ir/message.xml
@@ -224,6 +224,12 @@ this repository contains the full copyright notices and license terms. -->
You are not allowed to delete records "%(ids)s" of "%(model)s" because of at lease one of those rules:
%(rules)s
+
+ At date/time: %(datetime)s
+
+
+ Within groups: %(groups)s
+
Invalid domain in schema "%(schema)s".
diff --git a/trytond/trytond/ir/model.py b/trytond/trytond/ir/model.py
index 97af35fa76..c71319997b 100644
--- a/trytond/trytond/ir/model.py
+++ b/trytond/trytond/ir/model.py
@@ -630,11 +630,18 @@ class ModelAccess(DeactivableMixin, ModelSQL, ModelView):
or (raise_exception and not transaction.check_access)):
return True
+ User = pool.get('res.user')
+ Group = pool.get('res.group')
+
access = cls.get_access([model_name])[model_name][mode]
if not access and access is not None:
if raise_exception:
- raise AccessError(gettext(
- 'ir.msg_access_rule_error', **Model.__names__()))
+ groups = Group.browse(User.get_groups())
+ raise AccessError(
+ gettext('ir.msg_access_rule_error', **Model.__names__()),
+ gettext(
+ 'ir.msg_context_groups',
+ groups=', '.join(g.rec_name for g in groups)))
else:
return False
return True
@@ -821,6 +828,9 @@ class ModelFieldAccess(DeactivableMixin, ModelSQL, ModelView):
return dict((x, True) for x in fields)
return True
+ User = pool.get('res.user')
+ Group = pool.get('res.group')
+
accesses = dict((f, a[mode])
for f, a in cls.get_access([model_name])[model_name].items())
if access:
@@ -828,9 +838,14 @@ class ModelFieldAccess(DeactivableMixin, ModelSQL, ModelView):
for field in fields:
if not accesses.get(field, True):
if raise_exception:
+ groups = Group.browse(User.get_groups())
raise AccessError(
- gettext('ir.msg_access_rule_field_error',
- **Model.__names__(field)))
+ gettext(
+ 'ir.msg_access_rule_field_error',
+ **Model.__names__(field)),
+ gettext(
+ 'ir.msg_context_groups',
+ groups=', '.join(g.rec_name for g in groups)))
else:
return False
return True
diff --git a/trytond/trytond/model/modelsql.py b/trytond/trytond/model/modelsql.py
index b4ec568dbe..f7a3f6480a 100644
--- a/trytond/trytond/model/modelsql.py
+++ b/trytond/trytond/model/modelsql.py
@@ -1549,8 +1549,14 @@ class ModelSQL(ModelStorage):
@classmethod
def __check_domain_rule(cls, ids, mode):
pool = Pool()
+ Lang = pool.get('ir.lang')
Rule = pool.get('ir.rule')
Model = pool.get('ir.model')
+ try:
+ User = pool.get('res.user')
+ Group = pool.get('res.group')
+ except KeyError:
+ User = Group = None
table = cls.__table__()
transaction = Transaction()
in_max = transaction.database.IN_MAX
@@ -1628,7 +1634,18 @@ class ModelSQL(ModelStorage):
else:
msg = gettext(
f'ir.msg_{mode}_error', ids=ids, model=model)
- raise AccessError(msg)
+
+ ctx_msg = []
+ lang = Lang.get()
+ if cls._history and transaction.context.get('_datetime'):
+ ctx_msg.append(gettext('ir.msg_context_datetime',
+ datetime=lang.strftime(
+ transaction.context['_datetime'])))
+ if domain and User and Group:
+ groups = Group.browse(User.get_groups())
+ ctx_msg.append(gettext('ir.msg_context_groups',
+ groups=', '.join(g.rec_name for g in groups)))
+ raise AccessError(msg, '\n'.join(ctx_msg))
@classmethod
def __search_query(cls, domain, count, query, order):
diff --git a/trytond/trytond/wizard/wizard.py b/trytond/trytond/wizard/wizard.py
index e454f68d94..4413afabb4 100644
--- a/trytond/trytond/wizard/wizard.py
+++ b/trytond/trytond/wizard/wizard.py
@@ -251,6 +251,7 @@ class Wizard(URLMixin, PoolBase):
ModelAccess = pool.get('ir.model.access')
ActionWizard = pool.get('ir.action.wizard')
User = pool.get('res.user')
+ Group = pool.get('res.group')
context = Transaction().context
if Transaction().user == 0:
@@ -265,18 +266,28 @@ class Wizard(URLMixin, PoolBase):
models = ActionWizard.get_models(
cls.__name__, action_id=context.get('action_id'))
if model and models and model not in models:
- raise AccessError(gettext(
+ groups = Group.browse(User.get_groups())
+ raise AccessError(
+ gettext(
'ir.msg_access_wizard_model_error',
wizard=cls.__name__,
- model=model))
+ model=model),
+ gettext(
+ 'ir.msg_context_groups',
+ groups=', '.join(g.rec_name for g in groups)))
groups = set(User.get_groups())
wizard_groups = ActionWizard.get_groups(cls.__name__,
action_id=context.get('action_id'))
if wizard_groups:
if not groups & wizard_groups:
- raise AccessError(gettext(
+ groups = Group.browse(User.get_groups())
+ raise AccessError(
+ gettext(
'ir.msg_access_wizard_error',
- name=cls.__name__))
+ name=cls.__name__),
+ gettext(
+ 'ir.msg_context_groups',
+ groups=', '.join(g.rec_name for g in groups)))
elif model and model != 'ir.ui.menu':
if (not callable(getattr(Model, 'table_query', None))
or Model.write.__func__ != ModelSQL.write.__func__):