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

Reply via email to