changeset 670b2dd25ca9 in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=670b2dd25ca9
description:
        Add parent to group to inherit accesses

        issue10032
        review320661002
diffstat:

 CHANGELOG                       |   1 +
 trytond/ir/model.py             |   4 +-
 trytond/ir/rule.py              |   3 +-
 trytond/res/group.py            |  36 +++++++++++++++++++++++++++-
 trytond/res/ir.xml              |  26 ++++++++++----------
 trytond/res/user.py             |  31 +++++++++++++++++++++---
 trytond/res/view/group_form.xml |   3 ++
 trytond/res/view/group_list.xml |   3 +-
 trytond/tests/test_access.py    |  51 +++++++++++++++++++++++++++++++++++++++++
 9 files changed, 135 insertions(+), 23 deletions(-)

diffs (390 lines):

diff -r 74f7b0322ede -r 670b2dd25ca9 CHANGELOG
--- a/CHANGELOG Sun Mar 07 11:13:53 2021 +0100
+++ b/CHANGELOG Sun Mar 07 12:43:09 2021 +0100
@@ -1,3 +1,4 @@
+* Add parent to group to inherit accesses
 * Add __access__ to Model
 * Add route wrapper to allow null origin
 * Add header grouping on report
diff -r 74f7b0322ede -r 670b2dd25ca9 trytond/ir/model.py
--- a/trytond/ir/model.py       Sun Mar 07 11:13:53 2021 +0100
+++ b/trytond/ir/model.py       Sun Mar 07 12:43:09 2021 +0100
@@ -523,7 +523,7 @@
         user = Transaction().user
         model_access = cls.__table__()
         ir_model = Model.__table__()
-        user_group = UserGroup.__table__()
+        user_group = UserGroup.user_group_all_table()
         group = Group.__table__()
 
         access = {}
@@ -729,7 +729,7 @@
         field_access = cls.__table__()
         ir_model = Model.__table__()
         model_field = ModelField.__table__()
-        user_group = UserGroup.__table__()
+        user_group = UserGroup.user_group_all_table()
         group = Group.__table__()
 
         accesses = {}
diff -r 74f7b0322ede -r 670b2dd25ca9 trytond/ir/rule.py
--- a/trytond/ir/rule.py        Sun Mar 07 11:13:53 2021 +0100
+++ b/trytond/ir/rule.py        Sun Mar 07 12:43:09 2021 +0100
@@ -143,6 +143,7 @@
             user = EvalEnvironment(User(user_id), User)
         return {
             'user': user,
+            'groups': User.get_groups()
             }
 
     @staticmethod
@@ -161,7 +162,7 @@
         rule_table = cls.__table__()
         rule_group = RuleGroup.__table__()
         rule_group_group = RuleGroup_Group.__table__()
-        user_group = User_Group.__table__()
+        user_group = User_Group.user_group_all_table()
         model = Model.__table__()
         transaction = Transaction()
 
diff -r 74f7b0322ede -r 670b2dd25ca9 trytond/res/group.py
--- a/trytond/res/group.py      Sun Mar 07 11:13:53 2021 +0100
+++ b/trytond/res/group.py      Sun Mar 07 12:43:09 2021 +0100
@@ -1,7 +1,12 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
 from itertools import chain
-from trytond.model import ModelView, ModelSQL, DeactivableMixin, fields, Unique
+
+from sql import With
+
+from trytond.model import ModelView, ModelSQL, DeactivableMixin, tree, fields
+from trytond.model import Unique
+from trytond.pool import Pool
 from trytond.tools import grouped_slice
 
 
@@ -23,11 +28,14 @@
         return res
 
 
-class Group(DeactivableMixin, ModelSQL, ModelView):
+class Group(DeactivableMixin, tree(), ModelSQL, ModelView):
     "Group"
     __name__ = "res.group"
     name = fields.Char('Name', required=True, select=True, translate=True)
     users = fields.Many2Many('res.user-res.group', 'group', 'user', 'Users')
+    parent = fields.Many2One(
+        'res.group', "Parent",
+        help="The group to inherit accesses from.")
     model_access = fields.One2Many('ir.model.access', 'group',
        'Access Model')
     field_access = fields.One2Many('ir.model.field.access', 'group',
@@ -51,6 +59,19 @@
         cls._order.insert(0, ('name', 'ASC'))
 
     @classmethod
+    def write(cls, *args):
+        super().write(*args)
+        pool = Pool()
+        # Restart the cache on the domain_get method
+        pool.get('ir.rule')._domain_get_cache.clear()
+        # Restart the cache for get_groups
+        pool.get('res.user')._get_groups_cache.clear()
+        # Restart the cache for model access and view
+        pool.get('ir.model.access')._get_access_cache.clear()
+        pool.get('ir.model.field.access')._get_access_cache.clear()
+        ModelView._fields_view_get_cache.clear()
+
+    @classmethod
     def copy(cls, groups, default=None):
         if default is None:
             default = {}
@@ -67,3 +88,14 @@
             default['name'] = name
             new_groups.extend(super(Group, cls).copy([group], default=default))
         return new_groups
+
+    @classmethod
+    def group_parent_all_cte(cls):
+        group = cls.__table__()
+        parents = With('id', 'parent', recursive=True)
+        parents.query = group.select(group.id, group.parent)
+        parents.query |= group.select(group.id, group.id)
+        parents.query |= (group
+            .join(parents, condition=group.parent == parents.id)
+            .select(group.id, parents.parent))
+        return parents
diff -r 74f7b0322ede -r 670b2dd25ca9 trytond/res/ir.xml
--- a/trytond/res/ir.xml        Sun Mar 07 11:13:53 2021 +0100
+++ b/trytond/res/ir.xml        Sun Mar 07 12:43:09 2021 +0100
@@ -445,7 +445,7 @@
         </record>
         <record model="ir.rule" id="rule_menu1">
             <field name="domain"
-                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                eval="[('groups', 'in', Eval('groups', []))]"
                 pyson="1"/>
             <field name="rule_group" ref="rule_group_menu"/>
         </record>
@@ -460,7 +460,7 @@
         </record>
         <record model="ir.rule" id="rule_action1">
             <field name="domain"
-                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                eval="[('groups', 'in', Eval('groups', []))]"
                 pyson="1"/>
             <field name="rule_group" ref="rule_group_action"/>
         </record>
@@ -475,7 +475,7 @@
         </record>
         <record model="ir.rule" id="rule_action_keyword1">
             <field name="domain"
-                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                eval="[('groups', 'in', Eval('groups', []))]"
                 pyson="1"/>
             <field name="rule_group" ref="rule_group_action_keyword"/>
         </record>
@@ -491,7 +491,7 @@
         </record>
         <record model="ir.rule" id="rule_action_report1">
             <field name="domain"
-                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                eval="[('groups', 'in', Eval('groups', []))]"
                 pyson="1"/>
             <field name="rule_group" ref="rule_group_action_report"/>
         </record>
@@ -507,7 +507,7 @@
         </record>
         <record model="ir.rule" id="rule_action_act_window1">
             <field name="domain"
-                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                eval="[('groups', 'in', Eval('groups', []))]"
                 pyson="1"/>
             <field name="rule_group" ref="rule_group_action_act_window"/>
         </record>
@@ -523,7 +523,7 @@
         </record>
         <record model="ir.rule" id="rule_action_wizard1">
             <field name="domain"
-                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                eval="[('groups', 'in', Eval('groups', []))]"
                 pyson="1"/>
             <field name="rule_group" ref="rule_group_action_wizard"/>
         </record>
@@ -539,7 +539,7 @@
         </record>
         <record model="ir.rule" id="rule_action_url1">
             <field name="domain"
-                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                eval="[('groups', 'in', Eval('groups', []))]"
                 pyson="1"/>
             <field name="rule_group" ref="rule_group_action_url"/>
         </record>
@@ -778,7 +778,7 @@
         </record>
         <record model="ir.rule" id="rule_sequence">
             <field name="domain"
-                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                eval="[('groups', 'in', Eval('groups', []))]"
                 pyson="1"/>
             <field name="rule_group" ref="rule_group_sequence"/>
         </record>
@@ -791,7 +791,7 @@
         </record>
         <record model="ir.rule" id="rule_sequence_strict">
             <field name="domain"
-                eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
+                eval="[('groups', 'in', Eval('groups', []))]"
                 pyson="1"/>
             <field name="rule_group" ref="rule_group_sequence_strict"/>
         </record>
@@ -949,7 +949,7 @@
             <field name="perm_delete" eval="False"/>
         </record>
         <record model="ir.rule" id="rule_group_export_read1">
-            <field name="domain" eval="[('groups', 'in', Eval('user.groups', 
[]))]" pyson="1"/>
+            <field name="domain" eval="[('groups', 'in', Eval('groups', []))]" 
pyson="1"/>
             <field name="rule_group" ref="rule_group_export_read"/>
         </record>
         <record model="ir.rule" id="rule_group_export_read2">
@@ -968,7 +968,7 @@
             <field name="perm_delete" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_group_export_write1">
-            <field name="domain" eval="[('write_groups', 'in', 
Eval('user.groups', []))]" pyson="1"/>
+            <field name="domain" eval="[('write_groups', 'in', Eval('groups', 
[]))]" pyson="1"/>
             <field name="rule_group" ref="rule_group_export_write"/>
         </record>
         <record model="ir.rule" id="rule_group_export_write2">
@@ -1006,7 +1006,7 @@
             <field name="perm_delete" eval="False"/>
         </record>
         <record model="ir.rule" id="rule_group_export_line_read1">
-            <field name="domain" eval="[('export.groups', 'in', 
Eval('user.groups', []))]" pyson="1"/>
+            <field name="domain" eval="[('export.groups', 'in', Eval('groups', 
[]))]" pyson="1"/>
             <field name="rule_group" ref="rule_group_export_line_read"/>
         </record>
         <record model="ir.rule" id="rule_group_export_line_read2">
@@ -1025,7 +1025,7 @@
             <field name="perm_delete" eval="True"/>
         </record>
         <record model="ir.rule" id="rule_group_export_line_write1">
-            <field name="domain" eval="[('export.write_groups', 'in', 
Eval('user.groups', []))]" pyson="1"/>
+            <field name="domain" eval="[('export.write_groups', 'in', 
Eval('groups', []))]" pyson="1"/>
             <field name="rule_group" ref="rule_group_export_line_write"/>
         </record>
         <record model="ir.rule" id="rule_group_export_line_write2">
diff -r 74f7b0322ede -r 670b2dd25ca9 trytond/res/user.py
--- a/trytond/res/user.py       Sun Mar 07 11:13:53 2021 +0100
+++ b/trytond/res/user.py       Sun Mar 07 12:43:09 2021 +0100
@@ -24,7 +24,7 @@
 from operator import attrgetter
 from ast import literal_eval
 
-from sql import Literal
+from sql import Literal, Null
 from sql.functions import CurrentTimestamp
 from sql.conditionals import Coalesce, Case
 from sql.aggregate import Count
@@ -587,15 +587,22 @@
         return res
 
     @classmethod
-    def get_groups(cls):
+    def get_groups(cls, name=None):
         '''
-        Return a list of group ids for the user
+        Return a list of all group ids for the user
         '''
         user = Transaction().user
         groups = cls._get_groups_cache.get(user)
         if groups is not None:
             return groups
-        groups = cls.read([Transaction().user], ['groups'])[0]['groups']
+        pool = Pool()
+        UserGroup = pool.get('res.user-res.group')
+        cursor = Transaction().connection.cursor()
+        user_group = UserGroup.user_group_all_table()
+        cursor.execute(*user_group.select(
+                user_group.group,
+                where=user_group.user == user))
+        groups = [g for g, in cursor]
         cls._get_groups_cache.set(user, groups)
         return groups
 
@@ -969,6 +976,22 @@
         pool.get('ir.model.field.access')._get_access_cache.clear()
         ModelView._fields_view_get_cache.clear()
 
+    @classmethod
+    def user_group_all_table(cls):
+        pool = Pool()
+        Group = pool.get('res.group')
+        user_group = cls.__table__()
+        group_parents = Group.group_parent_all_cte()
+
+        return (user_group
+            .join(group_parents,
+                condition=user_group.group == group_parents.id)
+            .select(
+                user_group.user.as_('user'),
+                group_parents.parent.as_('group'),
+                where=group_parents.parent != Null,
+                with_=group_parents))
+
 
 class Warning_(ModelSQL, ModelView):
     'User Warning'
diff -r 74f7b0322ede -r 670b2dd25ca9 trytond/res/view/group_form.xml
--- a/trytond/res/view/group_form.xml   Sun Mar 07 11:13:53 2021 +0100
+++ b/trytond/res/view/group_form.xml   Sun Mar 07 12:43:09 2021 +0100
@@ -6,6 +6,9 @@
     <field name="name"/>
     <label name="active"/>
     <field name="active"/>
+
+    <label name="parent"/>
+    <field name="parent"/>
     <notebook>
         <page string="Members" col="2" id="members">
             <field name="users" colspan="2"/>
diff -r 74f7b0322ede -r 670b2dd25ca9 trytond/res/view/group_list.xml
--- a/trytond/res/view/group_list.xml   Sun Mar 07 11:13:53 2021 +0100
+++ b/trytond/res/view/group_list.xml   Sun Mar 07 12:43:09 2021 +0100
@@ -2,5 +2,6 @@
 <!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
 this repository contains the full copyright notices and license terms. -->
 <tree>
-    <field name="name" expand="1"/>
+    <field name="name" expand="2"/>
+    <field name="parent" expand="1"/>
 </tree>
diff -r 74f7b0322ede -r 670b2dd25ca9 trytond/tests/test_access.py
--- a/trytond/tests/test_access.py      Sun Mar 07 11:13:53 2021 +0100
+++ b/trytond/tests/test_access.py      Sun Mar 07 12:43:09 2021 +0100
@@ -255,6 +255,31 @@
 
         self._assert(record)
 
+    @with_transaction(context=_context)
+    def test_access_inherited_from_parent(self):
+        "Test access inherited from parent"
+        pool = Pool()
+        Group = pool.get('res.group')
+        ModelAccess = pool.get('ir.model.access')
+        TestAccess = pool.get('test.access')
+        record, = TestAccess.create([{}])
+        group, = Group.create([{'name': 'Test'}])
+        Group.write([self.group], {
+                'parent': group.id,
+                })
+        ModelAccess.create([{
+                    'model': self.model.id,
+                    'group': self.group.id,
+                    self._perm: False,
+                    }])
+        ModelAccess.create([{
+                    'model': self.model.id,
+                    'group': group.id,
+                    self._perm: True,
+                    }])
+
+        self._assert(record)
+
 
 class ModelAccessReadTestCase(_ModelAccessTestCase):
     _perm = 'perm_read'
@@ -665,6 +690,32 @@
         self._assert2(record)
 
     @with_transaction(context=_context)
+    def test_access_inherited_from_parent(self):
+        "Test no access with other group"
+        pool = Pool()
+        Group = pool.get('res.group')
+        FieldAccess = pool.get('ir.model.field.access')
+        TestAccess = pool.get('test.access')
+        record, = TestAccess.create([{}])
+        group, = Group.create([{'name': 'Test'}])
+        Group.write([self.group], {
+                'parent': group.id,
+                })
+        FieldAccess.create([{
+                    'field': self.field1.id,
+                    'group': self.group.id,
+                    self._perm: False,
+                    }])
+        FieldAccess.create([{
+                    'field': self.field1.id,
+                    'group': group.id,
+                    self._perm: True,
+                    }])
+
+        self._assert1(record)
+        self._assert2(record)
+
+    @with_transaction(context=_context)
     def test_two_access(self):
         "Test two access"
         pool = Pool()

Reply via email to