changeset 74f7b0322ede in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=74f7b0322ede
description:
        Add __access__ to Model

        issue9903
        review324601002
diffstat:

 CHANGELOG                            |   1 +
 doc/ref/models/models.rst            |   5 ++
 trytond/backend/postgresql/init.sql  |   1 +
 trytond/backend/sqlite/init.sql      |   3 +-
 trytond/ir/model.py                  |  53 ++++++++++++++++++++++----
 trytond/ir/rule.py                   |  29 +++++++++++++-
 trytond/ir/view/model_field_form.xml |   2 +
 trytond/ir/view/model_field_list.xml |   1 +
 trytond/model/model.py               |   1 +
 trytond/tests/access.py              |  12 ++++++
 trytond/tests/rule.py                |  15 +++++++
 trytond/tests/test_access.py         |  71 +++++++++++++++++++++++------------
 trytond/tests/test_rule.py           |  57 ++++++++++++++++++++++++++++
 trytond/tests/test_tryton.py         |  33 +++++++++++++++-
 14 files changed, 246 insertions(+), 38 deletions(-)

diffs (669 lines):

diff -r 340454839b57 -r 74f7b0322ede CHANGELOG
--- a/CHANGELOG Sun Mar 07 10:01:20 2021 +0100
+++ b/CHANGELOG Sun Mar 07 11:13:53 2021 +0100
@@ -1,3 +1,4 @@
+* Add __access__ to Model
 * Add route wrapper to allow null origin
 * Add header grouping on report
 * Protect trusted devices against brute force attack
diff -r 340454839b57 -r 74f7b0322ede doc/ref/models/models.rst
--- a/doc/ref/models/models.rst Sun Mar 07 10:01:20 2021 +0100
+++ b/doc/ref/models/models.rst Sun Mar 07 11:13:53 2021 +0100
@@ -17,6 +17,11 @@
     It contains the a unique name to reference the model throughout the
     platform.
 
+.. attribute:: Model.__access__
+
+   A set that contains the names of relation field for which the access rights
+   are also checked for this model.
+
 .. attribute:: Model.__rpc__
 
     It contains a dictionary with method name as key and an instance of
diff -r 340454839b57 -r 74f7b0322ede trytond/backend/postgresql/init.sql
--- a/trytond/backend/postgresql/init.sql       Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/backend/postgresql/init.sql       Sun Mar 07 11:13:53 2021 +0100
@@ -29,6 +29,7 @@
     ttype VARCHAR,
     help TEXT,
     module VARCHAR,
+    "access" BOOL,
     PRIMARY KEY(id),
     FOREIGN KEY (model) REFERENCES ir_model(id) ON DELETE CASCADE
 );
diff -r 340454839b57 -r 74f7b0322ede trytond/backend/sqlite/init.sql
--- a/trytond/backend/sqlite/init.sql   Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/backend/sqlite/init.sql   Sun Mar 07 11:13:53 2021 +0100
@@ -20,7 +20,8 @@
     field_description VARCHAR,
     ttype VARCHAR,
     help TEXT,
-    module VARCHAR
+    module VARCHAR,
+    "access" BOOLEAN
 );
 
 
diff -r 340454839b57 -r 74f7b0322ede trytond/ir/model.py
--- a/trytond/ir/model.py       Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/ir/model.py       Sun Mar 07 11:13:53 2021 +0100
@@ -243,6 +243,15 @@
         depends=['module'])
     module = fields.Char('Module',
        help="Module in which this field is defined.")
+    access = fields.Boolean(
+        "Access",
+        states={
+            'invisible': ~Eval('relation'),
+            },
+        depends=['relation'],
+        help="If checked, the access right on the model of the field "
+        "is also tested against the relation of the field.")
+
     _get_name_cache = Cache('ir.model.field.get_name')
 
     @classmethod
@@ -273,6 +282,7 @@
                 ir_model_field.relation.as_('relation'),
                 ir_model_field.module.as_('module'),
                 ir_model_field.help.as_('help'),
+                ir_model_field.access.as_('access'),
                 where=ir_model.model == model.__name__))
         model_fields = {f['name']: f for f in cursor_dict(cursor)}
 
@@ -283,27 +293,32 @@
                 relation = field.relation_name
             else:
                 relation = None
+            access = field_name in model.__access__
 
             if field_name not in model_fields:
                 cursor.execute(*ir_model_field.insert(
                         [ir_model_field.model, ir_model_field.name,
                             ir_model_field.field_description,
                             ir_model_field.ttype, ir_model_field.relation,
-                            ir_model_field.help, ir_model_field.module],
+                            ir_model_field.help, ir_model_field.module,
+                            ir_model_field.access],
                         [[
                                 model_id, field_name,
                                 field.string,
                                 field._type, relation,
-                                field.help, module_name]]))
+                                field.help, module_name,
+                                access]]))
             elif (model_fields[field_name]['field_description'] != field.string
                     or model_fields[field_name]['ttype'] != field._type
                     or model_fields[field_name]['relation'] != relation
-                    or model_fields[field_name]['help'] != field.help):
+                    or model_fields[field_name]['help'] != field.help
+                    or model_fields[field_name]['access'] != access):
                 cursor.execute(*ir_model_field.update(
                         [ir_model_field.field_description,
                             ir_model_field.ttype, ir_model_field.relation,
-                            ir_model_field.help],
-                        [field.string, field._type, relation, field.help],
+                            ir_model_field.help, ir_model_field.access],
+                        [field.string, field._type, relation, field.help,
+                            access],
                         where=(ir_model_field.id
                             == model_fields[field_name]['id'])))
 
@@ -520,6 +535,18 @@
         else:
             return access
 
+        def fill_models(Model, models):
+            if Model.__name__ in models:
+                return
+            models.append(Model.__name__)
+            for field_name in Model.__access__:
+                field = getattr(Model, field_name)
+                fill_models(field.get_target(), models)
+        model2models = defaultdict(list)
+        for model in models:
+            fill_models(pool.get(model), model2models[model])
+
+        all_models = list(set(sum(model2models.values(), [])))
         default = {'read': True, 'write': True, 'create': True, 'delete': True}
         access = dict((m, default) for m in models)
         cursor.execute(*model_access.join(ir_model, 'LEFT',
@@ -542,16 +569,24 @@
                 Max(Case(
                         (model_access.perm_delete == Literal(True), 1),
                         else_=0)),
-                where=ir_model.model.in_(models)
+                where=ir_model.model.in_(all_models)
                 & (model_access.active == Literal(True))
                 & ((
                         (user_group.user == user)
                         & (group.active == Literal(True)))
                     | (model_access.group == Null)),
                 group_by=ir_model.model))
-        access.update(dict(
-                (m, {'read': r, 'write': w, 'create': c, 'delete': d})
-                for m, r, w, c, d in cursor.fetchall()))
+        raw_access = {
+            m: {'read': r, 'write': w, 'create': c, 'delete': d}
+            for m, r, w, c, d in cursor.fetchall()}
+
+        for model in models:
+            access[model] = {
+                perm: max(
+                    (raw_access[m][perm] for m in model2models[model]
+                        if m in raw_access),
+                    default=True)
+                for perm in ['read', 'write', 'create', 'delete']}
         for model, maccess in access.items():
             cls._get_access_cache.set((user, model), maccess)
         return access
diff -r 340454839b57 -r 74f7b0322ede trytond/ir/rule.py
--- a/trytond/ir/rule.py        Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/ir/rule.py        Sun Mar 07 11:13:53 2021 +0100
@@ -167,6 +167,25 @@
 
         assert mode in cls.modes
 
+        model_names = []
+        model2field = defaultdict(list)
+
+        def update_model_names(Model, path=None):
+            if Model.__name__ in model_names:
+                return
+            model_names.append(Model.__name__)
+            if path:
+                model2field[Model.__name__].append(path)
+            for field_name in Model.__access__:
+                field = getattr(Model, field_name)
+                Target = field.get_target()
+                if path:
+                    target_path = path + '.' + field_name
+                else:
+                    target_path = field_name
+                update_model_names(Target, target_path)
+        update_model_names(pool.get(model_name))
+
         cursor = transaction.connection.cursor()
         user_id = transaction.user
         # root user above constraint
@@ -179,7 +198,7 @@
                 ).join(model,
                 condition=rule_group.model == model.id
                 ).select(rule_table.id,
-                where=(model.model == model_name)
+                where=(model.model.in_(model_names))
                 & (getattr(rule_group, 'perm_%s' % mode) == Literal(True))
                 & (rule_group.id.in_(
                         rule_group_group.join(
@@ -198,7 +217,7 @@
         cursor.execute(*rule_group.join(model,
                 condition=rule_group.model == model.id
                 ).select(rule_group.id,
-                where=(model.model == model_name)
+                where=(model.model.in_(model_names))
                 & ~rule_group.id.in_(rule_table.select(rule_table.rule_group))
                 & rule_group.id.in_(rule_group_group.join(user_group,
                         condition=rule_group_group.group == user_group.group
@@ -215,6 +234,12 @@
                 assert rule.domain, ('Rule domain empty,'
                     'check if migration was done')
                 dom = decoder.decode(rule.domain)
+                target_model = rule.rule_group.model.model
+                if target_model in model2field:
+                    target_dom = ['OR']
+                    for field in model2field[target_model]:
+                        target_dom.append((field, 'where', dom))
+                    dom = target_dom
                 if rule.rule_group.global_p:
                     clause_global[rule.rule_group].append(dom)
                 else:
diff -r 340454839b57 -r 74f7b0322ede trytond/ir/view/model_field_form.xml
--- a/trytond/ir/view/model_field_form.xml      Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/ir/view/model_field_form.xml      Sun Mar 07 11:13:53 2021 +0100
@@ -14,6 +14,8 @@
     <field name="field_description" colspan="3"/>
     <label name="help" yfill="1"/>
     <field name="help" colspan="3"/>
+    <label name="access"/>
+    <field name="access"/>
     <label name="module"/>
     <field name="module"/>
 </form>
diff -r 340454839b57 -r 74f7b0322ede trytond/ir/view/model_field_list.xml
--- a/trytond/ir/view/model_field_list.xml      Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/ir/view/model_field_list.xml      Sun Mar 07 11:13:53 2021 +0100
@@ -7,5 +7,6 @@
     <field name="ttype"/>
     <field name="relation" expand="1"/>
     <field name="field_description" expand="1"/>
+    <field name="access"/>
     <field name="module"/>
 </tree>
diff -r 340454839b57 -r 74f7b0322ede trytond/model/model.py
--- a/trytond/model/model.py    Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/model/model.py    Sun Mar 07 11:13:53 2021 +0100
@@ -45,6 +45,7 @@
             'fields_get': RPC(cache=dict(days=1)),
             'pre_validate': RPC(instantiate=0),
             }
+        cls.__access__ = set()
 
         # Copy fields and update depends
         for attr in dir(cls):
diff -r 340454839b57 -r 74f7b0322ede trytond/tests/access.py
--- a/trytond/tests/access.py   Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/tests/access.py   Sun Mar 07 11:13:53 2021 +0100
@@ -23,8 +23,20 @@
     value = fields.Integer("Value")
 
 
+class TestAccessModel(TestAccess):
+    "Test Access from Model"
+    __name__ = 'test.access.model'
+    access = fields.Many2One('test.access', "Access")
+
+    @classmethod
+    def __setup__(cls):
+        super().__setup__()
+        cls.__access__.add('access')
+
+
 def register(module):
     Pool.register(
         TestAccess,
         TestAccessRelate,
+        TestAccessModel,
         module=module, type_='model')
diff -r 340454839b57 -r 74f7b0322ede trytond/tests/rule.py
--- a/trytond/tests/rule.py     Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/tests/rule.py     Sun Mar 07 11:13:53 2021 +0100
@@ -17,8 +17,23 @@
     field = fields.Char("Field")
 
 
+class TestRuleModel(ModelSQL):
+    "Test Rule from Model"
+    __name__ = 'test.rule.model'
+    __access__ = 'test.rule'
+
+    name = fields.Char("Name")
+    rule = fields.Many2One('test.rule', "Rule")
+
+    @classmethod
+    def __setup__(cls):
+        super().__setup__()
+        cls.__access__.add('rule')
+
+
 def register(module):
     Pool.register(
         TestRule,
         TestRuleRelation,
+        TestRuleModel,
         module=module, type_='model')
diff -r 340454839b57 -r 74f7b0322ede trytond/tests/test_access.py
--- a/trytond/tests/test_access.py      Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/tests/test_access.py      Sun Mar 07 11:13:53 2021 +0100
@@ -18,11 +18,13 @@
     def setUpClass(cls):
         activate_module('tests')
 
+    model_name = model_access_name = 'test.access'
+
     @property
     def model(self):
         pool = Pool()
         Model = pool.get('ir.model')
-        model, = Model.search([('model', '=', 'test.access')])
+        model, = Model.search([('model', '=', self.model_access_name)])
         return model
 
     @property
@@ -42,7 +44,7 @@
     def test_access_empty(self):
         "Test access without model access"
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
 
         self._assert(record)
@@ -52,7 +54,7 @@
         "Test access without group"
         pool = Pool()
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         ModelAccess.create([{
                     'model': self.model.id,
@@ -67,7 +69,7 @@
         "Test no access without group"
         pool = Pool()
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         ModelAccess.create([{
                     'model': self.model.id,
@@ -82,7 +84,7 @@
         "Test one access with groups"
         pool = Pool()
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         ModelAccess.create([{
                     'model': self.model.id,
@@ -102,7 +104,7 @@
         "Test one access without group"
         pool = Pool()
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         ModelAccess.create([{
                     'model': self.model.id,
@@ -122,7 +124,7 @@
         "Test all access with groups"
         pool = Pool()
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         ModelAccess.create([{
                     'model': self.model.id,
@@ -142,7 +144,7 @@
         "Test no access with groups"
         pool = Pool()
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         ModelAccess.create([{
                     'model': self.model.id,
@@ -162,7 +164,7 @@
         "Test one access with group"
         pool = Pool()
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         ModelAccess.create([{
                     'model': self.model.id,
@@ -177,7 +179,7 @@
         "Test no access with group"
         pool = Pool()
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         ModelAccess.create([{
                     'model': self.model.id,
@@ -193,7 +195,7 @@
         pool = Pool()
         Group = pool.get('res.group')
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         group, = Group.create([{'name': 'Test'}])
         ModelAccess.create([{
@@ -215,7 +217,7 @@
         pool = Pool()
         Group = pool.get('res.group')
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         group, = Group.create([{'name': 'Test'}])
         ModelAccess.create([{
@@ -237,7 +239,7 @@
         pool = Pool()
         Group = pool.get('res.group')
         ModelAccess = pool.get('ir.model.access')
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
         group, = Group.create([{'name': 'Test'}])
         ModelAccess.create([{
@@ -259,13 +261,13 @@
 
     def _assert(self, record):
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         TestAccess.read([record.id], ['field1'])
         TestAccess.search([])
 
     def _assert_raises(self, record):
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         with self.assertRaises(AccessError):
             TestAccess.read([record.id], ['field1'])
         with self.assertRaises(AccessError):
@@ -275,7 +277,7 @@
     def test_access_relate_empty(self):
         "Test access on search relate without model access"
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         record, = TestAccess.create([{}])
 
         TestAccess.read([record.id], ['relate.value'])
@@ -286,7 +288,7 @@
     def test_access_relate(self):
         "Test access on search relate"
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         Model = pool.get('ir.model')
         ModelAccess = pool.get('ir.model.access')
         record, = TestAccess.create([{}])
@@ -307,7 +309,7 @@
     def test_no_access_relate(self):
         "Test no access on search relate"
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         Model = pool.get('ir.model')
         ModelAccess = pool.get('ir.model.access')
         record, = TestAccess.create([{}])
@@ -333,12 +335,12 @@
 
     def _assert(self, record):
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         TestAccess.write([record], {})
 
     def _assert_raises(self, record):
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         with self.assertRaises(AccessError):
             TestAccess.write([record], {})
 
@@ -348,12 +350,12 @@
 
     def _assert(self, record):
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         TestAccess.create([{}])
 
     def _assert_raises(self, record):
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         with self.assertRaises(AccessError):
             TestAccess.create([{}])
 
@@ -363,16 +365,35 @@
 
     def _assert(self, record):
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         TestAccess.delete([record])
 
     def _assert_raises(self, record):
         pool = Pool()
-        TestAccess = pool.get('test.access')
+        TestAccess = pool.get(self.model_name)
         with self.assertRaises(AccessError):
             TestAccess.delete([record])
 
 
+class ModelAccessModelTestCase(_ModelAccessTestCase):
+    model_name = 'test.access.model'
+    _perm = 'perm_read'
+
+    def _assert(self, record):
+        pool = Pool()
+        TestAccess = pool.get(self.model_name)
+        TestAccess.read([record.id], ['field1'])
+        TestAccess.search([])
+
+    def _assert_raises(self, record):
+        pool = Pool()
+        TestAccess = pool.get(self.model_name)
+        with self.assertRaises(AccessError):
+            TestAccess.read([record.id], ['field1'])
+        with self.assertRaises(AccessError):
+            TestAccess.search([])
+
+
 class _ModelFieldAccessTestCase(unittest.TestCase):
     _perm = None
 
@@ -927,6 +948,8 @@
     suite_.addTests(unittest.TestLoader(
         ).loadTestsFromTestCase(ModelAccessDeleteTestCase))
     suite_.addTests(unittest.TestLoader(
+        ).loadTestsFromTestCase(ModelAccessModelTestCase))
+    suite_.addTests(unittest.TestLoader(
         ).loadTestsFromTestCase(ModelFieldAccessReadTestCase))
     suite_.addTests(unittest.TestLoader(
         ).loadTestsFromTestCase(ModelFieldAccessWriteTestCase))
diff -r 340454839b57 -r 74f7b0322ede trytond/tests/test_rule.py
--- a/trytond/tests/test_rule.py        Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/tests/test_rule.py        Sun Mar 07 11:13:53 2021 +0100
@@ -623,6 +623,63 @@
         value, = TestRule.read([non_deletable.id], ['_delete'])
         self.assertEqual(value['_delete'], False)
 
+    @with_transaction()
+    def test_model_with_rule(self):
+        "Test model with rule"
+        pool = Pool()
+        TestRule = pool.get('test.rule')
+        TestRuleModel = pool.get('test.rule.model')
+        RuleGroup = pool.get('ir.rule.group')
+        Model = pool.get('ir.model')
+
+        model, = Model.search([('model', '=', 'test.rule')])
+        rule_group, = RuleGroup.create([{
+                    'name': "Field different from foo",
+                    'model': model.id,
+                    'global_p': True,
+                    'perm_read': True,
+                    'perm_create': False,
+                    'perm_write': False,
+                    'perm_delete': False,
+                    'rules': [('create', [{
+                                    'domain': json.dumps(
+                                        [('field', '!=', 'foo')]),
+                                    }])],
+                    }])
+        rule, = TestRule.create([{'field': 'bar'}])
+        test, = TestRuleModel.create([{'rule': rule.id, 'name': 'foo'}])
+
+        TestRuleModel.read([test.id], ['name'])
+
+    @with_transaction()
+    def test_model_with_rule_fail(self):
+        "Test model with rule fail"
+        pool = Pool()
+        TestRule = pool.get('test.rule')
+        TestRuleModel = pool.get('test.rule.model')
+        RuleGroup = pool.get('ir.rule.group')
+        Model = pool.get('ir.model')
+
+        model, = Model.search([('model', '=', 'test.rule')])
+        rule_group, = RuleGroup.create([{
+                    'name': "Field different from foo",
+                    'model': model.id,
+                    'global_p': True,
+                    'perm_read': True,
+                    'perm_create': False,
+                    'perm_write': False,
+                    'perm_delete': False,
+                    'rules': [('create', [{
+                                    'domain': json.dumps(
+                                        [('field', '!=', 'foo')]),
+                                    }])],
+                    }])
+        rule, = TestRule.create([{'field': 'foo'}])
+        test, = TestRuleModel.create([{'rule': rule.id, 'name': 'foo'}])
+
+        with self.assertRaisesRegex(AccessError, "Field different from foo"):
+            TestRuleModel.read([test.id], ['name'])
+
 
 def suite():
     suite_ = unittest.TestSuite()
diff -r 340454839b57 -r 74f7b0322ede trytond/tests/test_tryton.py
--- a/trytond/tests/test_tryton.py      Sun Mar 07 10:01:20 2021 +0100
+++ b/trytond/tests/test_tryton.py      Sun Mar 07 11:13:53 2021 +0100
@@ -247,6 +247,23 @@
                     field._type, mname))
 
     @with_transaction()
+    def test_model__access__(self):
+        "Test existing model __access__"
+        pool = Pool()
+        for mname, Model in pool.iterobject():
+            if not isregisteredby(Model, self.module):
+                continue
+            for field_name in Model.__access__:
+                self.assertIn(field_name, Model._fields.keys(),
+                    msg="Wrong __access__ '%s' for %s" % (field_name, mname))
+                field = Model._fields[field_name]
+                Target = field.get_target()
+                self.assertTrue(
+                    Target,
+                    msg='Missing target for __access__ "%s" of %s' % (
+                        field_name, mname))
+
+    @with_transaction()
     def test_view(self):
         'Test validity of all views of the module'
         pool = Pool()
@@ -529,10 +546,22 @@
         'Test missing default model access'
         pool = Pool()
         Access = pool.get('ir.model.access')
-        no_groups = {a.model.name for a in Access.search([
+        no_groups = {a.model.model for a in Access.search([
                     ('group', '=', None),
                     ])}
-        with_groups = {a.model.name for a in Access.search([
+
+        def has_access(Model, models):
+            if Model.__name__ in models:
+                return True
+            for field_name in Model.__access__:
+                Target = Model._fields[field_name].get_target()
+                if has_access(Target, models):
+                    return True
+        for mname, Model in pool.iterobject():
+            if has_access(Model, no_groups):
+                no_groups.add(mname)
+
+        with_groups = {a.model.model for a in Access.search([
                     ('group', '!=', None),
                     ])}
 

Reply via email to