Permalink
Browse files

[ADD] expression: support `parent_of` domain operator

Inspired by this commit:
 alhashash@319cec4

Closes #7641
  • Loading branch information...
adrienpeiffer authored and odony committed Sep 28, 2015
1 parent 6820dde commit 295f9be9058229fe896605fcdddd4ec9cf965417
Showing with 93 additions and 11 deletions.
  1. +53 −0 openerp/addons/base/tests/test_osv_expression.yml
  2. +40 −11 openerp/osv/expression.py
@@ -51,6 +51,42 @@
!python {model: res.partner.category }: |
ids = self.search(cr, uid, [('id', 'child_of',[ref('categ_1')])])
assert len(ids) == 1, ids
-
Test hierarchical search in M2M with parent ID (list of ids)
-
!python {model: res.partner.category }: |
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_1')])])
assert len(ids) == 3, ids
-
Test hierarchical search in M2M with parent ID (single id)
-
!python {model: res.partner.category }: |
ids = self.search(cr, uid, [('id', 'parent_of',ref('categ_1'))])
assert len(ids) == 3, ids
-
Test hierarchical search in M2M with parent IDs
-
!python {model: res.partner.category }: |
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_root'), ref('categ_0')])])
assert len(ids) == 2, ids
-
Test hierarchical search in M2M with parent IDs
-
!python {model: res.partner.category }: |
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_0')])])
assert len(ids) == 2, ids
-
Test hierarchical search in M2M with parent IDs
-
!python {model: res.partner.category }: |
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_1')])])
assert len(ids) == 3, ids
-
Test hierarchical search in M2M with parent IDs
-
!python {model: res.partner.category }: |
ids = self.search(cr, uid, [('id', 'parent_of',[ref('categ_root')])])
assert len(ids) == 1, ids
-
Testing that some domain expressions work
-
@@ -405,6 +441,23 @@
assert res_2 == expected
assert res_3 == expected
assert res_4 == expected
# parent_of x returns x and its parents (direct or not).
company = self.browse(cr, uid, ref('ymltest_company4'))
expected = [ref('ymltest_company4'), ref('ymltest_company3')]
expected.sort()
res_1 = self.search(cr, uid, [('id', 'parent_of', [ref('ymltest_company4')])])
res_1.sort()
res_2 = self.search(cr, uid, [('id', 'parent_of', ref('ymltest_company4'))])
res_2.sort()
res_3 = self.search(cr, uid, [('id', 'parent_of', [company.name])])
res_3.sort()
res_4 = self.search(cr, uid, [('id', 'parent_of', company.name)])
res_4.sort()
assert res_1 == expected
assert res_2 == expected
assert res_3 == expected
assert res_4 == expected
-
Equivalent queries, one2many.
-
@@ -141,7 +141,7 @@
# operators are also used. In this case its right operand has the form (subselect, params).
TERM_OPERATORS = ('=', '!=', '<=', '<', '>', '>=', '=?', '=like', '=ilike',
'like', 'not like', 'ilike', 'not ilike', 'in', 'not in',
'child_of')
'child_of', 'parent_of')
# A subset of the above operators, with a 'negative' semantic. When the
# expressions 'in NEGATIVE_TERM_OPERATORS' or 'not in NEGATIVE_TERM_OPERATORS' are used in the code
@@ -725,6 +725,34 @@ def recursive_children(ids, model, parent_field):
return ids + recursive_children(ids2, model, parent_field)
return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))]
def parent_of_domain(left, ids, left_model, parent=None, prefix='', context=None):
""" Return a domain implementing the parent_of operator for [(left,parent_of,ids)],
either as a range using the parent_left/right tree lookup fields
(when available), or as an expanded [(left,in,parent_ids)] """
if left_model._parent_store and (not left_model.pool._init):
doms = []
for node in left_model.browse(cr, uid, ids, context=context):
if doms:
doms.insert(0, OR_OPERATOR)
doms += [AND_OPERATOR, ('parent_right', '>', node.parent_left), ('parent_left', '<=', node.parent_left)]
if prefix:
return [(left, 'in', left_model.search(cr, uid, doms, context=context))]
return doms
else:
def get_parent_ids(record, parent_field):
ids = set([record.id])
while record[parent_field]:
record = record[parent_field]
ids.add(record.id)
return ids
parent_ids = set()
for node in left_model.browse(cr, uid, ids, context=context):
parent_ids |= get_parent_ids(node, parent or left_model._parent_name)
return [(left, 'in', list(parent_ids))]
HIERARCHY_FUNCS = {'child_of': child_of_domain,
'parent_of': parent_of_domain}
def pop():
""" Pop a leaf to process. """
return self.stack.pop()
@@ -791,9 +819,9 @@ def push_result(leaf):
leaf.add_join_context(next_model, model._inherits[next_model._name], 'id', model._inherits[next_model._name])
push(leaf)
elif left == 'id' and operator == 'child_of':
elif left == 'id' and operator in HIERARCHY_FUNCS:
ids2 = to_ids(right, model, context)
dom = child_of_domain(left, ids2, model)
dom = HIERARCHY_FUNCS[operator](left, ids2, model)
for dom_leaf in reversed(dom):
new_leaf = create_substitution_leaf(leaf, dom_leaf, model)
push(new_leaf)
@@ -906,12 +934,12 @@ def push_result(leaf):
# -------------------------------------------------
# Applying recursivity on field(one2many)
elif column._type == 'one2many' and operator == 'child_of':
elif column._type == 'one2many' and operator in HIERARCHY_FUNCS:
ids2 = to_ids(right, comodel, context)
if column._obj != model._name:
dom = child_of_domain(left, ids2, comodel, prefix=column._obj)
dom = HIERARCHY_FUNCS[operator](left, ids2, comodel, prefix=column._obj)
else:
dom = child_of_domain('id', ids2, model, parent=left)
dom = HIERARCHY_FUNCS[operator]('id', ids2, model, parent=left)
for dom_leaf in reversed(dom):
push(create_substitution_leaf(leaf, dom_leaf, model))
@@ -952,14 +980,15 @@ def push_result(leaf):
elif column._type == 'many2many':
rel_table, rel_id1, rel_id2 = column._sql_names(model)
if operator == 'child_of':
if operator in HIERARCHY_FUNCS:
def _rec_convert(ids):
if comodel == model:
return ids
return select_from_where(cr, rel_id1, rel_table, rel_id2, ids, operator)
ids2 = to_ids(right, comodel, context)
dom = child_of_domain('id', ids2, comodel)
dom = HIERARCHY_FUNCS[operator]('id', ids2, comodel)
ids2 = comodel.search(cr, uid, dom, context=context)
push(create_substitution_leaf(leaf, ('id', 'in', _rec_convert(ids2)), model))
else:
@@ -992,12 +1021,12 @@ def _rec_convert(ids):
push(create_substitution_leaf(leaf, ('id', m2m_op, select_distinct_from_where_not_null(cr, rel_id1, rel_table)), model))
elif column._type == 'many2one':
if operator == 'child_of':
if operator in HIERARCHY_FUNCS:
ids2 = to_ids(right, comodel, context)
if column._obj != model._name:
dom = child_of_domain(left, ids2, comodel, prefix=column._obj)
dom = HIERARCHY_FUNCS[operator](left, ids2, comodel, prefix=column._obj)
else:
dom = child_of_domain('id', ids2, model, parent=left)
dom = HIERARCHY_FUNCS[operator]('id', ids2, model, parent=left)
for dom_leaf in reversed(dom):
push(create_substitution_leaf(leaf, dom_leaf, model))
else:

0 comments on commit 295f9be

Please sign in to comment.