From d04730f944beb5d107ae8d0d29f986950acd4498 Mon Sep 17 00:00:00 2001 From: "Krzysztof Magusiak (krma)" Date: Tue, 18 Jun 2024 09:51:47 +0000 Subject: [PATCH] [IMP] core: odoo.domains odoo/odoo#170009 --- src/base/17.0.1.3/attr_domains2expr.py | 2 +- src/base/tests/test_util.py | 2 +- src/util/domains.py | 103 ++++++++++++++++++++++--- src/util/fields.py | 8 +- 4 files changed, 99 insertions(+), 16 deletions(-) diff --git a/src/base/17.0.1.3/attr_domains2expr.py b/src/base/17.0.1.3/attr_domains2expr.py index 4ff17caa..82099d80 100644 --- a/src/base/17.0.1.3/attr_domains2expr.py +++ b/src/base/17.0.1.3/attr_domains2expr.py @@ -36,10 +36,10 @@ def migrate(cr, version): from lxml import etree -from odoo.osv.expression import DOMAIN_OPERATORS, normalize_domain from odoo.tools.safe_eval import safe_eval from odoo.upgrade import util +from odoo.upgrade.util.domains import DOMAIN_OPERATORS, normalize_domain def adapt_view(cr, view_xmlid): diff --git a/src/base/tests/test_util.py b/src/base/tests/test_util.py index 84f0695b..25c8d0ca 100644 --- a/src/base/tests/test_util.py +++ b/src/base/tests/test_util.py @@ -13,7 +13,7 @@ except ImportError: import mock -from odoo.osv.expression import FALSE_LEAF, TRUE_LEAF +from odoo.upgrade.util.domains import FALSE_LEAF, TRUE_LEAF from odoo.tools import mute_logger from odoo.tools.safe_eval import safe_eval diff --git a/src/util/domains.py b/src/util/domains.py index 20f22721..d6476512 100644 --- a/src/util/domains.py +++ b/src/util/domains.py @@ -24,11 +24,9 @@ unescape = lambda x: x try: - from odoo.osv import expression from odoo.tools import ustr from odoo.tools.safe_eval import safe_eval except ImportError: - from openerp.osv import expression from openerp.tools import ustr from openerp.tools.safe_eval import safe_eval @@ -45,6 +43,91 @@ except NameError: basestring = unicode = str +# import from domains/expression +try: + import odoo.domains as _dom + FALSE_LEAF = _dom._FALSE_LEAF + TRUE_LEAF = _dom._TRUE_LEAF + NOT_OPERATOR = _dom.DomainNot.OPERATOR + AND_OPERATOR = _dom.DomainAnd.OPERATOR + OR_OPERATOR = _dom.DomainOr.OPERATOR + DOMAIN_OPERATORS = {NOT_OPERATOR, AND_OPERATOR, OR_OPERATOR} + + # normalization functions are redefined here because they will be deprecated + # the Domain factory normalized but also distributes operators during creation of domains + + def normalize_domain(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_LEAF] + result = [] + expected = 1 + 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] = [AND_OPERATOR] # put an extra '&' in front + expected = 1 + if isinstance(token, (list, tuple)): # domain term + expected -= 1 + if len(token) == 3 and token[1] in ('any', 'not any'): + token = (token[0], token[1], normalize_domain(token[2])) + else: + token = normalize_leaf(token) + else: + expected += op_arity.get(token, 0) - 1 + result.append(token) + if expected: + raise ValueError(f'Domain {domain} is syntactically not correct.') + return result + + def normalize_leaf(leaf): + if not is_leaf(leaf): + raise TypeError("Leaf must be a tuple or list of 3 values") + left, operator, right = leaf + original = operator + operator = operator.lower() + if operator == '<>': + operator = '!=' + if isinstance(right, bool) and operator in ('in', 'not in'): + _logger.warning("The domain term '%s' should use the '=' or '!=' operator." % ((left, original, right),)) + operator = '=' if operator == 'in' else '!=' + if isinstance(right, (list, tuple)) and operator in ('=', '!='): + _logger.warning("The domain term '%s' should use the 'in' or 'not in' operator." % ((left, original, right),)) + operator = 'in' if operator == '=' else 'not in' + return left, operator, right + + def is_leaf(leaf): + return ( + isinstance(leaf, (tuple, list)) + and len(leaf) == 3 + and leaf[1] in _dom.TERM_OPERATORS + and ( + isinstance(leaf[0], str) + or leaf == TRUE_LEAF + or leaf == FALSE_LEAF + ) + ) +except ImportError: + try: + from odoo.osv import expression + except ImportError: + from openerp.osv import expression + FALSE_LEAF = expression.FALSE_LEAF + TRUE_LEAF = expression.TRUE_LEAF + NOT_OPERATOR = expression.NOT_OPERATOR + AND_OPERATOR = expression.AND_OPERATOR + OR_OPERATOR = expression.OR_OPERATOR + DOMAIN_OPERATORS = expression.DOMAIN_OPERATORS + normalize_domain = expression.normalize_domain + normalize_leaf = expression.normalize_leaf + is_leaf = expression.is_leaf + del expression # unset so it's not used directly + + _logger = logging.getLogger(__name__) DomainField = collections.namedtuple("DomainField", "table domain_column model_select") """ @@ -200,14 +283,14 @@ def _adapt_one_domain(cr, target_model, old, new, model, domain, adapter=None, f # pre-check domain if isinstance(domain, basestring): try: - eval_dom = expression.normalize_domain(safe_eval(domain, evaluation_context, nocopy=True)) + eval_dom = normalize_domain(safe_eval(domain, evaluation_context, nocopy=True)) except Exception as e: oops = ustr(e) _logger.log(NEARLYWARN, "Cannot evaluate %r domain: %r: %s", model, domain, oops) return None else: try: - eval_dom = expression.normalize_domain(domain) + eval_dom = normalize_domain(domain) except Exception as e: oops = ustr(e) _logger.log(NEARLYWARN, "Invalid %r domain: %r: %s", model, domain, oops) @@ -238,7 +321,7 @@ def clean_term(term): final_dom = [] changed = False - op_arity = {expression.NOT_OPERATOR: 1, expression.AND_OPERATOR: 2, expression.OR_OPERATOR: 2} + op_arity = {NOT_OPERATOR: 1, AND_OPERATOR: 2, OR_OPERATOR: 2} op_stack = [] # (operator, number of terms missing) for element in eval_dom: while op_stack and op_stack[-1][1] == 0: @@ -253,26 +336,26 @@ def clean_term(term): final_dom.append(element) continue - if not expression.is_leaf(element): + if not is_leaf(element): _logger.log(NEARLYWARN, "Invalid domain on %r: %s", model, domain) return None if op_stack: op_stack[-1][1] -= 1 # previous operator got a term - if tuple(element) in [expression.TRUE_LEAF, expression.FALSE_LEAF]: + if tuple(element) in [TRUE_LEAF, FALSE_LEAF]: final_dom.append(element) continue is_or = False neg = False for op, _ in reversed(op_stack): - if op != expression.NOT_OPERATOR: - is_or = op == expression.OR_OPERATOR + if op != NOT_OPERATOR: + is_or = op == OR_OPERATOR break neg = not neg - leaf = expression.normalize_leaf(element) + leaf = normalize_leaf(element) path = leaf[0].split(".") # force_adapt=True -> always adapt if found anywhere on left path # otherwise adapt only when {old} field is the last parts of left path diff --git a/src/util/fields.py b/src/util/fields.py index 08f41113..5e379886 100644 --- a/src/util/fields.py +++ b/src/util/fields.py @@ -20,15 +20,15 @@ try: from odoo import release - from odoo.osv import expression from odoo.tools.misc import mute_logger from odoo.tools.safe_eval import safe_eval except ImportError: from openerp import release - from openerp.osv import expression from openerp.tools.misc import mute_logger from openerp.tools.safe_eval import safe_eval +from .domains import TRUE_LEAF, FALSE_LEAF + try: from odoo.tools.sql import make_index_name except ImportError: @@ -214,8 +214,8 @@ def clean_context(context): def adapter(leaf, is_or, negated): # replace by TRUE_LEAF, unless negated or in a OR operation but not negated if is_or ^ negated: - return [expression.FALSE_LEAF] - return [expression.TRUE_LEAF] + return [FALSE_LEAF] + return [TRUE_LEAF] # clean domains adapt_domains(cr, model, fieldname, "ignored", adapter=adapter, skip_inherit=skip_inherit, force_adapt=True)