details: https://code.tryton.org/tryton/commit/eb0ac9f2ae7c
branch: 7.6
user: Nicolas Évrard <[email protected]>
date: Wed Dec 03 08:47:20 2025 +0100
description:
Filter Many2Many group fields on active = True
When the field is used in a domain adding this filter ensure that
comparison
with None will succeed when there are only inactive groups in the field.
Closes #14369
(grafted from 6ec84dbcdde99641b803ebcdfee5aaa0edf9f548)
diffstat:
trytond/trytond/ir/action.py | 5 +-
trytond/trytond/ir/export.py | 3 +
trytond/trytond/ir/model.py | 5 +-
trytond/trytond/ir/sequence.py | 3 +
trytond/trytond/ir/ui/menu.py | 5 +-
trytond/trytond/res/user.py | 10 ++-
trytond/trytond/tests/test_access.py | 106 +++++++++++++++++++++++++++++++++++
7 files changed, 132 insertions(+), 5 deletions(-)
diffs (222 lines):
diff -r ff894a8408bb -r eb0ac9f2ae7c trytond/trytond/ir/action.py
--- a/trytond/trytond/ir/action.py Fri Nov 21 11:50:54 2025 +0100
+++ b/trytond/trytond/ir/action.py Wed Dec 03 08:47:20 2025 +0100
@@ -67,7 +67,10 @@
'Keywords')
icon = fields.Many2One('ir.ui.icon', 'Icon')
groups = fields.Many2Many(
- 'ir.action-res.group', 'action', 'group', "Groups")
+ 'ir.action-res.group', 'action', 'group', "Groups",
+ filter=[
+ ('active', '=', True),
+ ])
_groups_cache = Cache('ir.action.get_groups', context=False)
@classmethod
diff -r ff894a8408bb -r eb0ac9f2ae7c trytond/trytond/ir/export.py
--- a/trytond/trytond/ir/export.py Fri Nov 21 11:50:54 2025 +0100
+++ b/trytond/trytond/ir/export.py Wed Dec 03 08:47:20 2025 +0100
@@ -24,6 +24,9 @@
'res.user', "User", required=True, ondelete='CASCADE')
groups = fields.Many2Many(
'ir.export-res.group', 'export', 'group', "Groups",
+ filter=[
+ ('active', '=', True),
+ ],
help="The user groups that can use the export.")
write_groups = fields.Many2Many(
'ir.export-write-res.group', 'export', 'group',
diff -r ff894a8408bb -r eb0ac9f2ae7c trytond/trytond/ir/model.py
--- a/trytond/trytond/ir/model.py Fri Nov 21 11:50:54 2025 +0100
+++ b/trytond/trytond/ir/model.py Wed Dec 03 08:47:20 2025 +0100
@@ -965,7 +965,10 @@
])
_reset_cache = Cache('ir.model.button.reset')
groups = fields.Many2Many(
- 'ir.model.button-res.group', 'button', 'group', "Groups")
+ 'ir.model.button-res.group', 'button', 'group', "Groups",
+ filter=[
+ ('active', '=', True),
+ ])
_groups_cache = Cache('ir.model.button.groups')
_view_attributes_cache = Cache(
'ir.model.button.view_attributes', context=False)
diff -r ff894a8408bb -r eb0ac9f2ae7c trytond/trytond/ir/sequence.py
--- a/trytond/trytond/ir/sequence.py Fri Nov 21 11:50:54 2025 +0100
+++ b/trytond/trytond/ir/sequence.py Wed Dec 03 08:47:20 2025 +0100
@@ -37,6 +37,9 @@
name = fields.Char('Sequence Name', required=True, translate=True)
groups = fields.Many2Many(
'ir.sequence.type-res.group', 'sequence_type', 'group', "Groups",
+ filter=[
+ ('active', '=', True),
+ ],
help="Groups allowed to edit the sequences of this type.")
diff -r ff894a8408bb -r eb0ac9f2ae7c trytond/trytond/ir/ui/menu.py
--- a/trytond/trytond/ir/ui/menu.py Fri Nov 21 11:50:54 2025 +0100
+++ b/trytond/trytond/ir/ui/menu.py Wed Dec 03 08:47:20 2025 +0100
@@ -107,7 +107,10 @@
('keyword', '=', 'tree_open'),
])
groups = fields.Many2Many(
- 'ir.ui.menu-res.group', 'menu', 'group', "Groups")
+ 'ir.ui.menu-res.group', 'menu', 'group', "Groups",
+ filter=[
+ ('active', '=', True),
+ ])
favorite = fields.Function(fields.Boolean('Favorite'), 'get_favorite')
favorites = fields.Many2Many(
'ir.ui.menu.favorite', 'menu', 'user', "Favorites")
diff -r ff894a8408bb -r eb0ac9f2ae7c trytond/trytond/res/user.py
--- a/trytond/trytond/res/user.py Fri Nov 21 11:50:54 2025 +0100
+++ b/trytond/trytond/res/user.py Wed Dec 03 08:47:20 2025 +0100
@@ -607,16 +607,22 @@
'''
pool = Pool()
UserGroup = pool.get('res.user-res.group')
+ Group = pool.get('res.group')
+
transaction = Transaction()
user = transaction.user
groups = cls._get_groups_cache.get(user)
if groups is not None:
return groups
cursor = transaction.connection.cursor()
+ group = Group.__table__()
user_group = UserGroup.user_group_all_table()
- cursor.execute(*user_group.select(
+ cursor.execute(*user_group
+ .join(group, 'LEFT', user_group.group == group.id)
+ .select(
user_group.group,
- where=user_group.user == user,
+ where=((user_group.user == user)
+ & (group.active == Literal(True))),
order_by=[user_group.group.asc]))
groups = tuple(g for g, in cursor)
cls._get_groups_cache.set(user, groups)
diff -r ff894a8408bb -r eb0ac9f2ae7c trytond/trytond/tests/test_access.py
--- a/trytond/trytond/tests/test_access.py Fri Nov 21 11:50:54 2025 +0100
+++ b/trytond/trytond/tests/test_access.py Wed Dec 03 08:47:20 2025 +0100
@@ -228,6 +228,56 @@
self._assert_raises(record)
@with_transaction(context=_context)
+ def test_no_access_with_inactive_group(self):
+ "Test no access with inactive group"
+ pool = Pool()
+ Group = pool.get('res.group')
+ ModelAccess = pool.get('ir.model.access')
+ TestAccess = pool.get(self.model_name)
+
+ inactive_group, = Group.create([{'name': 'Test', 'active': False}])
+ record, = TestAccess.create([{}])
+ ModelAccess.create([{
+ 'model': self.model_name,
+ 'group': None,
+ self._perm: False,
+ }])
+ ModelAccess.create([{
+ 'model': self.model_name,
+ 'group': inactive_group.id,
+ self._perm: True,
+ }])
+
+ self._assert_raises(record)
+
+ @with_transaction(context=_context)
+ def test_no_access_with_member_inactive_group(self):
+ "Test no access when member of an inactive group"
+ pool = Pool()
+ Group = pool.get('res.group')
+ ModelAccess = pool.get('ir.model.access')
+ TestAccess = pool.get(self.model_name)
+
+ inactive_group, = Group.create([{
+ 'name': 'Test',
+ 'active': False,
+ 'users': [('add', [Transaction().user])],
+ }])
+ record, = TestAccess.create([{}])
+ ModelAccess.create([{
+ 'model': self.model_name,
+ 'group': None,
+ self._perm: False,
+ }])
+ ModelAccess.create([{
+ 'model': self.model_name,
+ 'group': inactive_group.id,
+ self._perm: True,
+ }])
+
+ self._assert_raises(record)
+
+ @with_transaction(context=_context)
def test_one_access_with_other_group_no_perm(self):
"Test one access with other group no perm"
pool = Pool()
@@ -657,6 +707,62 @@
self._assert2(record)
@with_transaction(context=_context)
+ def test_no_access_with_inactive_group(self):
+ "Test no access with inactive group"
+ pool = Pool()
+ Group = pool.get('res.group')
+ FieldAccess = pool.get('ir.model.field.access')
+ TestAccess = pool.get('test.access')
+
+ inactive_group, = Group.create([{'name': 'Test', 'active': False}])
+ record, = TestAccess.create([{}])
+ FieldAccess.create([{
+ 'model': 'test.access',
+ 'field': 'field1',
+ 'group': None,
+ self._perm: False,
+ }])
+ FieldAccess.create([{
+ 'model': 'test.access',
+ 'field': 'field1',
+ 'group': inactive_group.id,
+ self._perm: True,
+ }])
+
+ self._assert_raises1(record)
+ self._assert2(record)
+
+ @with_transaction(context=_context)
+ def test_no_access_with_member_inactive_group(self):
+ "Test no access when member of an inactive group"
+ pool = Pool()
+ Group = pool.get('res.group')
+ FieldAccess = pool.get('ir.model.field.access')
+ TestAccess = pool.get('test.access')
+
+ inactive_group, = Group.create([{
+ 'name': 'Test',
+ 'active': False,
+ 'users': [('add', [Transaction().user])],
+ }])
+ record, = TestAccess.create([{}])
+ FieldAccess.create([{
+ 'model': 'test.access',
+ 'field': 'field1',
+ 'group': None,
+ self._perm: False,
+ }])
+ FieldAccess.create([{
+ 'model': 'test.access',
+ 'field': 'field1',
+ 'group': inactive_group.id,
+ self._perm: True,
+ }])
+
+ self._assert_raises1(record)
+ self._assert2(record)
+
+ @with_transaction(context=_context)
def test_one_access_with_other_group_no_perm(self):
"Test one access with other group no perm"
pool = Pool()