From 34fabe65fe05274c45b554a9206406ea238fa54a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sat, 10 Apr 2021 23:46:23 +0200 Subject: [PATCH] Check rule only if _check_access is set and enforce companies rule issue4080 review343891009 --- CHANGELOG | 2 ++ ir.py | 18 +++++++++++-- ir.xml | 28 ++++++++++---------- res.py | 10 +++++++ tests/__init__.py | 6 ++--- tests/test_company.py | 48 +++++++++++++++++++++++++++++++--- view/user_form.xml | 3 +++ view/user_form_preferences.xml | 6 ++++- 8 files changed, 98 insertions(+), 23 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d4d173e129..27555885a2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Enforce companies rule +* Add company filter * Remove tree structure from company * Add test on context of company multivalue target fields * Use report header for CompanyReport diff --git a/ir.py b/ir.py index 4d4a9e52b1..d133ddf140 100644 --- a/ir.py +++ b/ir.py @@ -56,14 +56,19 @@ class Rule(metaclass=PoolMeta): def __setup__(cls): super().__setup__() cls.domain.help += '\n- "employee" from the current user' + cls.domain.help += '\n- "companies" from the current user' @classmethod def _get_cache_key(cls): key = super(Rule, cls)._get_cache_key() # XXX Use company from context instead of browse to prevent infinite # loop, but the cache is cleared when User is written. - return key + (Transaction().context.get('company'), - Transaction().context.get('employee')) + context = Transaction().context + return key + ( + context.get('company'), + context.get('employee'), + context.get('company_filter'), + ) @classmethod def _get_context(cls): @@ -79,6 +84,15 @@ class Rule(metaclass=PoolMeta): _check_access=False, _datetime=None): context['employee'] = EvalEnvironment( Employee(user.employee.id), Employee) + if user.company_filter == 'one': + context['companies'] = [user.company.id] if user.company else [] + context['employees'] = [user.employee.id] if user.employee else [] + elif user.company_filter == 'all': + context['companies'] = [c.id for c in user.companies] + context['employees'] = [e.id for e in user.employees] + else: + context['companies'] = [] + context['employees'] = [] return context diff --git a/ir.xml b/ir.xml index de5ef43c60..7c42b59f73 100644 --- a/ir.xml +++ b/ir.xml @@ -15,36 +15,36 @@ this repository contains the full copyright notices and license terms. --> sequence_tree - - User in company + + User in companies - + - + - + - + - - User in company + + User in companies - + - + - + - + diff --git a/res.py b/res.py index 6c8d574c8c..8d5f099cab 100644 --- a/res.py +++ b/res.py @@ -55,12 +55,18 @@ class User(metaclass=PoolMeta): ], depends=['company', 'employees'], help="Select the employee to make the user behave as such.") + company_filter = fields.Selection([ + ('one', "Current"), + ('all', "All"), + ], "Company Filter", + help="Define records of which companies are shown.") @classmethod def __setup__(cls): super(User, cls).__setup__() cls._context_fields.insert(0, 'company') cls._context_fields.insert(0, 'employee') + cls._context_fields.insert(0, 'company_filter') @classmethod def __register__(cls, module): @@ -99,6 +105,10 @@ class User(metaclass=PoolMeta): def default_company(cls): return Transaction().context.get('company') + @classmethod + def default_company_filter(cls): + return 'one' + def get_status_bar(self, name): def same_company(record): return record.company == self.company diff --git a/tests/__init__.py b/tests/__init__.py index 531f4832ed..7c5d418f05 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,12 +4,12 @@ try: from trytond.modules.company.tests.test_company import ( suite, create_company, set_company, create_employee, - PartyCompanyCheckEraseMixin, CompanyMultiValueTestMixin) + PartyCompanyCheckEraseMixin, CompanyTestMixin) except ImportError: from .test_company import ( suite, create_company, set_company, create_employee, - PartyCompanyCheckEraseMixin, CompanyMultiValueTestMixin) + PartyCompanyCheckEraseMixin, CompanyTestMixin) __all__ = [ 'suite', 'create_company', 'set_company', 'create_employee', - 'PartyCompanyCheckEraseMixin', 'CompanyMultiValueTestMixin'] + 'PartyCompanyCheckEraseMixin', 'CompanyTestMixin'] diff --git a/tests/test_company.py b/tests/test_company.py index b743f50d66..939cd340f2 100644 --- a/tests/test_company.py +++ b/tests/test_company.py @@ -1,12 +1,15 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest +from collections import defaultdict from contextlib import contextmanager import trytond.tests.test_tryton +from trytond.model import ModelView, ModelStorage from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.transaction import Transaction from trytond.pool import Pool, isregisteredby +from trytond.pyson import Eval, PYSONEncoder from trytond.modules.currency.tests import create_currency, add_currency_rate from trytond.modules.party.tests import PartyCheckEraseMixin @@ -65,7 +68,7 @@ class PartyCompanyCheckEraseMixin(PartyCheckEraseMixin): return super().setup_check_erase_party() -class CompanyMultiValueTestMixin: +class CompanyTestMixin: @with_transaction() def test_company_multivalue_context(self): @@ -97,10 +100,49 @@ class CompanyMultiValueTestMixin: 'in "%s"."%s" context' % ( company, mname, fname)) + @with_transaction() + def test_company_rule(self): + "Test missing company rule" + pool = Pool() + Rule = pool.get('ir.rule') + Company = pool.get('company.company') + Employee = pool.get('company.employee') + User = pool.get('res.user') + + to_check = defaultdict(set) + for mname, model in pool.iterobject(): + if (not isregisteredby(model, self.module) + or model.__access__ + or not (issubclass(model, ModelView) + and issubclass(model, ModelStorage)) + or issubclass(model, (Company, Employee, User))): + continue + for fname, field in model._fields.items(): + if (field._type == 'many2one' + and issubclass(field.get_target(), Company)): + to_check[fname].add(mname) + + for fname, models in to_check.items(): + rules = Rule.search([ + ('rule_group', 'where', [ + ('model.model', 'in', list(models)), + ('global_p', '=', True), + ('perm_read', '=', True), + ]), + ('domain', '=', PYSONEncoder(sort_keys=True).encode( + [(fname, 'in', Eval('companies', []))])), + ]) + with_rules = {r.rule_group.model.model for r in rules} + self.assertGreaterEqual(with_rules, models, + msg='Models "%(models)s" are missing a global rule ' + 'for field "%(field)s"' % { + 'models': ', '.join(models - with_rules), + 'field': fname, + }) + class CompanyTestCase( - PartyCompanyCheckEraseMixin, CompanyMultiValueTestMixin, - ModuleTestCase): + PartyCompanyCheckEraseMixin, CompanyTestMixin, ModuleTestCase): 'Test Company module' module = 'company' diff --git a/view/user_form.xml b/view/user_form.xml index 6787872e73..f9da7f802d 100644 --- a/view/user_form.xml +++ b/view/user_form.xml @@ -6,6 +6,9 @@ this repository contains the full copyright notices and license terms. --> position="before">