From 3e19c4c5eb4567a64fcf3551850db6fc1c988775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sun, 7 Mar 2021 12:43:09 +0100 Subject: [PATCH] Add parent to group to inherit accesses issue10032 review320661002 --- 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(-) diff --git a/CHANGELOG b/CHANGELOG index 4656895b4c..80e6f44341 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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 --git a/trytond/ir/model.py b/trytond/ir/model.py index 2b7e469372..e9ab3a7ec6 100644 --- a/trytond/ir/model.py +++ b/trytond/ir/model.py @@ -523,7 +523,7 @@ class ModelAccess(DeactivableMixin, ModelSQL, ModelView): 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 @@ class ModelFieldAccess(DeactivableMixin, ModelSQL, ModelView): 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 --git a/trytond/ir/rule.py b/trytond/ir/rule.py index 7041dbb899..b98f0bc0ab 100644 --- a/trytond/ir/rule.py +++ b/trytond/ir/rule.py @@ -143,6 +143,7 @@ class Rule(ModelSQL, ModelView): user = EvalEnvironment(User(user_id), User) return { 'user': user, + 'groups': User.get_groups() } @staticmethod @@ -161,7 +162,7 @@ class Rule(ModelSQL, ModelView): 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 --git a/trytond/res/group.py b/trytond/res/group.py index c14f3b7a82..00ab650fc7 100644 --- a/trytond/res/group.py +++ b/trytond/res/group.py @@ -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 @@ class MenuMany2Many(fields.Many2Many): 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', @@ -50,6 +58,19 @@ class Group(DeactivableMixin, ModelSQL, ModelView): ] 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: @@ -67,3 +88,14 @@ class Group(DeactivableMixin, ModelSQL, ModelView): 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 --git a/trytond/res/ir.xml b/trytond/res/ir.xml index 6b80fe87bc..c1a1ce6006 100644 --- a/trytond/res/ir.xml +++ b/trytond/res/ir.xml @@ -445,7 +445,7 @@ this repository contains the full copyright notices and license terms. --> @@ -460,7 +460,7 @@ this repository contains the full copyright notices and license terms. --> @@ -475,7 +475,7 @@ this repository contains the full copyright notices and license terms. --> @@ -491,7 +491,7 @@ this repository contains the full copyright notices and license terms. --> @@ -507,7 +507,7 @@ this repository contains the full copyright notices and license terms. --> @@ -523,7 +523,7 @@ this repository contains the full copyright notices and license terms. --> @@ -539,7 +539,7 @@ this repository contains the full copyright notices and license terms. --> @@ -778,7 +778,7 @@ this repository contains the full copyright notices and license terms. --> @@ -791,7 +791,7 @@ this repository contains the full copyright notices and license terms. --> @@ -949,7 +949,7 @@ this repository contains the full copyright notices and license terms. --> - + @@ -968,7 +968,7 @@ this repository contains the full copyright notices and license terms. --> - + @@ -1006,7 +1006,7 @@ this repository contains the full copyright notices and license terms. --> - + @@ -1025,7 +1025,7 @@ this repository contains the full copyright notices and license terms. --> - + diff --git a/trytond/res/user.py b/trytond/res/user.py index 8d338eaac6..9a75956ee8 100644 --- a/trytond/res/user.py +++ b/trytond/res/user.py @@ -24,7 +24,7 @@ from itertools import groupby 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 @@ class User(DeactivableMixin, ModelSQL, ModelView): 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 @@ class UserGroup(ModelSQL): 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 --git a/trytond/res/view/group_form.xml b/trytond/res/view/group_form.xml index 4837a7cfbb..c62568e1b1 100644 --- a/trytond/res/view/group_form.xml +++ b/trytond/res/view/group_form.xml @@ -6,6 +6,9 @@ this repository contains the full copyright notices and license terms. -->