Add parent to group to inherit accesses
issue10032 review320661002
This commit is contained in:
@@ -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
|
||||
|
||||
+2
-2
@@ -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 = {}
|
||||
|
||||
+2
-1
@@ -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()
|
||||
|
||||
|
||||
+34
-2
@@ -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
|
||||
|
||||
+13
-13
@@ -445,7 +445,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<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 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<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">
|
||||
|
||||
+27
-4
@@ -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'
|
||||
|
||||
@@ -6,6 +6,9 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -255,6 +255,31 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
|
||||
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'
|
||||
@@ -664,6 +689,32 @@ class _ModelFieldAccessTestCase(unittest.TestCase):
|
||||
self._assert1(record)
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user