Skip to content

Commit

Permalink
[IMP] core: odoo.domains
Browse files Browse the repository at this point in the history
  • Loading branch information
kmagusiak committed Jul 5, 2024
1 parent 8653cb3 commit d04730f
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 16 deletions.
2 changes: 1 addition & 1 deletion src/base/17.0.1.3/attr_domains2expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion src/base/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
103 changes: 93 additions & 10 deletions src/util/domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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")
"""
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/util/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit d04730f

Please sign in to comment.