From e10e8073db1f254541e1f5a116f43bf92001a028 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 31 May 2011 16:43:18 +0200 Subject: [PATCH] [IMP] ir.rule,expression: merged improved rule computation mechanism, suggested by RCO bzr revid: odo@openerp.com-20110531144318-vut0opufvqd5mu07 --- openerp/addons/base/ir/ir_rule.py | 74 ++++++++------------------ openerp/osv/expression.py | 87 ++++++++++++++++++++++--------- 2 files changed, 85 insertions(+), 76 deletions(-) diff --git a/openerp/addons/base/ir/ir_rule.py b/openerp/addons/base/ir/ir_rule.py index 149e51cd7c4..52f76f2f611 100644 --- a/openerp/addons/base/ir/ir_rule.py +++ b/openerp/addons/base/ir/ir_rule.py @@ -27,6 +27,8 @@ import tools from tools.safe_eval import safe_eval as eval from tools.misc import unquote as unquote +SUPERUSER_UID = 1 + class ir_rule(osv.osv): _name = 'ir.rule' _order = 'name' @@ -46,31 +48,12 @@ class ir_rule(osv.osv): return {'user': self.pool.get('res.users').browse(cr, 1, uid), 'time':time} - def domain_binary_operation(self, cr, uid, str_domain_1, str_domain_2, operator): - """Returns the string representation of the binary operation designated by - ``operator`` (should be '|' or '&') for the two string domains ``str_domain_1`` - and ``str_domain_2`` """ - eval_context = self._eval_context_for_combinations() - canonical_domain_1 = expression.expression.normalize_domain(eval(str_domain_1 or '[]', eval_context)) - canonical_domain_2 = expression.expression.normalize_domain(eval(str_domain_2 or '[]', eval_context)) - return str([operator] + canonical_domain_1 + canonical_domain_1) - - def domain_conjunction(self, cr, uid, str_domain_1, str_domain_2): - """Returns the string representation of the conjunction (AND) of the - two string domains ``str_domain_1`` and ``str_domain_2``""" - return self.domain_binary_operation(cr, uid, str_domain_1, str_domain_2, expression.AND_OPERATOR) - - def domain_disjunction(self, cr, uid, str_domain_1, str_domain_2): - """Returns the string representation of the disjunction (OR) of the - two string domains ``str_domain_1`` and ``str_domain_2``""" - return self.domain_binary_operation(cr, uid, str_domain_1, str_domain_2, expression.OR_OPERATOR) - def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None): res = {} eval_context = self._eval_context(cr, uid) for rule in self.browse(cr, uid, ids, context): if rule.domain_force: - res[rule.id] = expression.expression.normalize_domain(eval(rule.domain_force, eval_context)) + res[rule.id] = expression.normalize(eval(rule.domain_force, eval_context)) else: res[rule.id] = [] return res @@ -116,25 +99,12 @@ class ir_rule(osv.osv): (_check_model_obj, 'Rules are not supported for osv_memory objects !', ['model_id']) ] - def domain_create(self, cr, uid, rule_ids): - count = 0 - dom = [] - for rule in self.browse(cr, uid, rule_ids): - if rule.domain: - dom += rule.domain - count += 1 - if count: - return [expression.AND_OPERATOR] * (count-1) + dom - return [] - @tools.cache() def _compute_domain(self, cr, uid, model_name, mode="read"): if mode not in self._MODES: raise ValueError('Invalid mode: %r' % (mode,)) - group_rule = {} - global_rules = [] - if uid == 1: + if uid == SUPERUSER_UID: return None cr.execute("""SELECT r.id FROM ir_rule r @@ -144,26 +114,26 @@ class ir_rule(osv.osv): AND (r.id IN (SELECT rule_group_id FROM rule_group_rel g_rel JOIN res_groups_users_rel u_rel ON (g_rel.group_id = u_rel.gid) WHERE u_rel.uid = %s) OR r.global)""", (model_name, uid)) - ids = map(lambda x: x[0], cr.fetchall()) - if ids: - for rule in self.browse(cr, uid, ids): + rule_ids = [x[0] for x in cr.fetchall()] + if rule_ids: + # browse user as super-admin root to avoid access errors! + user = self.pool.get('res.users').browse(cr, SUPERUSER_UID, uid) + global_domains = [] # list of domains + group_domains = {} # map: group -> list of domains + for rule in self.browse(cr, SUPERUSER_UID, rule_ids): + dom = expression.normalize(rule.domain) for group in rule.groups: - group_rule.setdefault(group.id, []).append(rule.id) + if group in user.groups_id: + group_domains.setdefault(group, []).append(dom) if not rule.groups: - global_rules.append(rule.id) - global_domain = self.domain_create(cr, uid, global_rules) - count = 0 - group_domains = [] - for value in group_rule.values(): - group_domain = self.domain_create(cr, uid, value) - if group_domain: - group_domains += group_domain - count += 1 - if count and global_domain: - return [expression.AND_OPERATOR] + global_domain + [expression.OR_OPERATOR] * (count-1) + group_domains - if count: - return [expression.OR_OPERATOR] * (count-1) + group_domains - return global_domain + global_domains.append(dom) + # combine global domains and group domains + if group_domains: + group_domain = expression.OR(map(expression.AND, group_domains.values())) + else: + group_domain = [] + domain = expression.AND(global_domains + [group_domain]) + return domain return [] def clear_cache(self, cr, uid): diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index 0283935ea34..4b6a470ed55 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -27,6 +27,69 @@ NOT_OPERATOR = '!' OR_OPERATOR = '|' AND_OPERATOR = '&' +TRUE_DOMAIN = [(1,'=',1)] +FALSE_DOMAIN = [(0,'=',1)] + +def normalize(domain): + """Returns a normalized version of ``domain_expr``, where all implicit '&' operators + have been made explicit. One property of normalized domain expressions is that they + can be easily combined together as if they were single domain components. + """ + assert isinstance(domain, (list, tuple)), "Domains to normalize must have a 'domain' form: a list or tuple of domain components" + if not domain: + return TRUE_DOMAIN + result = [] + expected = 1 # expected number of expressions + op_arity = {NOT_OPERATOR: 1, AND_OPERATOR: 2, OR_OPERATOR: 2} + for token in domain: + if expected == 0: # more than expected, like in [A, B] + result[0:0] = ['&'] # put an extra '&' in front + expected = 1 + result.append(token) + if isinstance(token, (list,tuple)): # domain term + expected -= 1 + else: + expected += op_arity.get(token, 0) - 1 + assert expected == 0 + return result + +def combine(operator, unit, zero, domains): + """Returns a new domain expression where all domain components from ``domains`` + have been added together using the binary operator ``operator``. + + :param unit: the identity element of the domains "set" with regard to the operation + performed by ``operator``, i.e the domain component ``i`` which, when + combined with any domain ``x`` via ``operator``, yields ``x``. + E.g. [(1,'=',1)] is the typical unit for AND_OPERATOR: adding it + to any domain component gives the same domain. + :param zero: the absorbing element of the domains "set" with regard to the operation + performed by ``operator``, i.e the domain component ``z`` which, when + combined with any domain ``x`` via ``operator``, yields ``z``. + E.g. [(1,'=',1)] is the typical zero for OR_OPERATOR: as soon as + you see it in a domain component the resulting domain is the zero. + """ + result = [] + count = 0 + for domain in domains: + if domain == unit: + continue + if domain == zero: + return zero + if domain: + result += domain + count += 1 + result = [operator] * (count - 1) + result + return result + +def AND(domains): + """ AND([D1,D2,...]) returns a domain representing D1 and D2 and ... """ + return combine(AND_OPERATOR, TRUE_DOMAIN, FALSE_DOMAIN, domains) + +def OR(domains): + """ OR([D1,D2,...]) returns a domain representing D1 or D2 or ... """ + return combine(OR_OPERATOR, FALSE_DOMAIN, TRUE_DOMAIN, domains) + + class expression(object): """ parse a domain expression @@ -48,30 +111,6 @@ class expression(object): and (((not internal) and element[1] in OPS) \ or (internal and element[1] in INTERNAL_OPS)) - @classmethod - def normalize_domain(cls, domain_expr): - """Returns a normalized version of ``domain_expr``, where all implicit '&' operators - have been made explicit. One property of normalized domain expressions is that they - can be easily combined together as if they were single domain components. - """ - assert isinstance(domain_expr, (list, tuple)), "Domain to normalize must have a 'domain' form: a list or tuple of domain components" - missing_operators = -1 - for item in domain_expr: - if cls._is_operator(item): - if item != NOT_OPERATOR: - missing_operators -= 1 - else: - missing_operators += 1 - return [AND_OPERATOR] * missing_operators + domain_expr - - def normalize(self): - """Make this expression normalized, i.e. change it so that all implicit '&' - operator become explicit. If the expression had already been parsed, - there is no need to do it again. - """ - self.__exp = expression.normalize_domain(self.__exp) - return self - def __execute_recursive_in(self, cr, s, f, w, ids, op, type): # todo: merge into parent query as sub-query res = []