[IMP] expression.parse: added the support of domain on one2many fields when _auto_true is activated. Leaf creation from an existing leaf moved as a method in expression object. Added some comments. Added / updated some tests.
bzr revid: tde@openerp.com-20121204170744-rv8e0zlteqoyj60o
This commit is contained in:
parent
52bd6459ef
commit
a8359924c2
|
@ -381,7 +381,42 @@ class test_expression(common.TransactionCase):
|
|||
"_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect parameter")
|
||||
|
||||
# ----------------------------------------
|
||||
# Test3: result-based tests
|
||||
# Test3: domain attribute on one2many fields
|
||||
# ----------------------------------------
|
||||
|
||||
partner_child_ids_col._auto_join = True
|
||||
partner_bank_ids_col._auto_join = True
|
||||
partner_child_ids_col._domain = lambda self: ['!', ('name', '=', self._name)]
|
||||
partner_bank_ids_col._domain = [('acc_number', 'like', '1')]
|
||||
# Do: 2 cascaded one2many with _auto_join, test final leaf is an id
|
||||
self._reinit_mock()
|
||||
partner_ids = partner_obj.search(cr, uid, ['&', (1, '=', 1), ('child_ids.bank_ids.id', 'in', [b_aa, b_ba])])
|
||||
for query in self.query_list:
|
||||
print query
|
||||
print partner_ids
|
||||
print '--------------------'
|
||||
# Test result: at least one of our added data
|
||||
self.assertTrue(set([p_a]).issubset(set(partner_ids)),
|
||||
"_auto_join on one2many with domains incorrect result")
|
||||
self.assertTrue(set([p_ab, p_ba]) not in set(partner_ids),
|
||||
"_auto_join on one2many with domains incorrect result")
|
||||
# Test produced queries that domains effectively present
|
||||
sql_query = self.query_list[0].get_sql()
|
||||
self.assertIn('"res_partner__child_ids__bank_ids"."acc_number" like %s', sql_query[1],
|
||||
"_auto_join on one2many with domains incorrect result")
|
||||
# TDE TODO: check first domain has a correct table name
|
||||
self.assertIn('"res_partner__child_ids__bank_ids"."acc_number" like %s', sql_query[1],
|
||||
"_auto_join on one2many with domains incorrect result")
|
||||
|
||||
partner_child_ids_col._domain = lambda self: [('name', '=', '__%s' % self._name)]
|
||||
self._reinit_mock()
|
||||
partner_ids = partner_obj.search(cr, uid, ['&', (1, '=', 1), ('child_ids.bank_ids.id', 'in', [b_aa, b_ba])])
|
||||
# Test result: no one
|
||||
self.assertFalse(partner_ids,
|
||||
"_auto_join on one2many with domains incorrect result")
|
||||
|
||||
# ----------------------------------------
|
||||
# Test4: result-based tests
|
||||
# ----------------------------------------
|
||||
|
||||
partner_bank_ids_col._auto_join = False
|
||||
|
@ -389,6 +424,8 @@ class test_expression(common.TransactionCase):
|
|||
partner_state_id_col._auto_join = False
|
||||
partner_parent_id_col._auto_join = False
|
||||
state_country_id_col._auto_join = False
|
||||
partner_child_ids_col._domain = []
|
||||
partner_bank_ids_col._domain = []
|
||||
|
||||
# Do: ('child_ids.state_id.country_id.code', 'like', '..') without _auto_join
|
||||
self._reinit_mock()
|
||||
|
@ -419,12 +456,12 @@ class test_expression(common.TransactionCase):
|
|||
self.assertEqual(len(self.query_list), 1,
|
||||
"_auto_join on: ('child_ids.state_id.country_id.code', 'like', '..') number of queries incorrect")
|
||||
|
||||
# # Remove mocks and modifications
|
||||
# partner_bank_ids_col._auto_join = False
|
||||
# partner_child_ids_col._auto_join = False
|
||||
# partner_state_id_col._auto_join = False
|
||||
# state_country_id_col._auto_join = False
|
||||
# BaseModel._where_calc = self._base_model_where_calc
|
||||
# Remove mocks and modifications
|
||||
partner_bank_ids_col._auto_join = False
|
||||
partner_child_ids_col._auto_join = False
|
||||
partner_state_id_col._auto_join = False
|
||||
state_country_id_col._auto_join = False
|
||||
BaseModel._where_calc = self._base_model_where_calc
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest2.main()
|
||||
|
|
|
@ -415,32 +415,32 @@ class ExtendedLeaf(object):
|
|||
for item in self.context_stack:
|
||||
self._tables.append(item[0])
|
||||
self._tables.append(table)
|
||||
# if leaf.is_operator:
|
||||
# self.elements = self.leaf, None, None
|
||||
# elif leaf.is_true_leaf() or leaf.is_false_leaf():
|
||||
# # because we consider left as a string
|
||||
# self.elements = ('%s' % leaf.leaf[0], leaf.leaf[1], leaf.leaf[2])
|
||||
# else:
|
||||
# self.elements = leaf.leaf
|
||||
# self.field_path = self.elements[0].split('.', 1)
|
||||
# self.field = self.table._columns.get(self.field_path[0])
|
||||
# if self.field and self.field._obj:
|
||||
# relational_table = working_table.pool.get(field._obj)
|
||||
# else:
|
||||
# relational_table = None
|
||||
if self.is_operator():
|
||||
self.elements = self.leaf, None, None
|
||||
elif self.is_true_leaf() or self.is_false_leaf():
|
||||
# because we consider left as a string
|
||||
self.elements = ('%s' % leaf[0], leaf[1], leaf[2])
|
||||
else:
|
||||
self.elements = leaf
|
||||
self.field_path = self.elements[0].split('.', 1)
|
||||
self.field = self.table._columns.get(self.field_path[0])
|
||||
if self.field and self.field._obj:
|
||||
self.relational_table = self.table.pool.get(self.field._obj)
|
||||
else:
|
||||
self.relational_table = None
|
||||
# check validity
|
||||
self.check_leaf()
|
||||
|
||||
def __str__(self):
|
||||
return '<osv.ExtendedLeaf: %s on %s (ctx: %s)>' % (str(self.leaf), self.table._table, ','.join(self._get_context_debug()))
|
||||
|
||||
def create_substitution_leaf(self, new_leaf, new_table=None):
|
||||
if new_table is None:
|
||||
new_table = self.table
|
||||
return ExtendedLeaf(new_leaf, new_table, self.context_stack)
|
||||
# def create_substitution_leaf(self, new_leaf, new_table=None):
|
||||
# if new_table is None:
|
||||
# new_table = self.table
|
||||
# return ExtendedLeaf(new_leaf, new_table, self.context_stack)
|
||||
|
||||
def create_sibling_leaf(self, new_leaf):
|
||||
pass
|
||||
# def create_sibling_leaf(self, new_leaf):
|
||||
# pass
|
||||
|
||||
# --------------------------------------------------
|
||||
# Join / Context manipulation
|
||||
|
@ -464,7 +464,7 @@ class ExtendedLeaf(object):
|
|||
# i.e.: many2one: res_country_state as res_partner__state_id
|
||||
# - join condition use aliases
|
||||
# i.e.: inherits: res_users.partner_id = res_users__partner_id.id
|
||||
# i.e.: one2many: res_partner.id = res_partner__bank_ids.partner_id
|
||||
# i.e.: one2many: res_partner.id = res_partner__bank_ids.parr_id
|
||||
# i.e.: many2one: res_partner.state_id = res_partner__state_id.id
|
||||
# Variables explanation:
|
||||
# - src_table: working table before the join
|
||||
|
@ -646,12 +646,9 @@ class expression(object):
|
|||
return [(left, 'in', recursive_children(ids, left_model, parent or left_model._parent_name))]
|
||||
|
||||
# ----------------------------------------
|
||||
# Internal structure
|
||||
# Leafs management
|
||||
# ----------------------------------------
|
||||
|
||||
def _format_table_name(self, table_name):
|
||||
return '"%s"' % (table_name)
|
||||
|
||||
def get_tables(self):
|
||||
""" Returns the list of tables for SQL queries, like select from ... """
|
||||
tables = []
|
||||
|
@ -664,6 +661,16 @@ class expression(object):
|
|||
tables.append(table_name)
|
||||
return tables
|
||||
|
||||
def create_substitution_leaf(self, leaf, new_elements, new_table=None):
|
||||
if new_table is None:
|
||||
new_table = leaf.table
|
||||
new_context_stack = [tuple(context) for context in leaf.context_stack]
|
||||
new_leaf = ExtendedLeaf(new_elements, new_table, context_stack=new_context_stack)
|
||||
return new_leaf
|
||||
|
||||
def create_sibling_leaf(self, leaf, new_elements):
|
||||
pass
|
||||
|
||||
# ----------------------------------------
|
||||
# Parsing
|
||||
# ----------------------------------------
|
||||
|
@ -697,6 +704,11 @@ class expression(object):
|
|||
|
||||
# Get working variables
|
||||
working_table = leaf.table
|
||||
# left, operator, right = leaf.elements
|
||||
# field_path = leaf.field_path
|
||||
# field = leaf.field
|
||||
# relational_table = leaf.relational_table
|
||||
|
||||
if leaf.is_operator():
|
||||
left, operator, right = leaf.leaf, None, None
|
||||
elif leaf.is_true_leaf() or leaf.is_false_leaf():
|
||||
|
@ -748,30 +760,43 @@ class expression(object):
|
|||
elif not field and left == 'id' and operator == 'child_of':
|
||||
ids2 = self.to_ids(cr, uid, right, working_table, context)
|
||||
dom = self.child_of_domain(cr, uid, left, ids2, working_table)
|
||||
leafs_to_stack += [leaf.create_substitution_leaf(dom_leaf, working_table) for dom_leaf in dom]
|
||||
leafs_to_stack += [self.create_substitution_leaf(leaf, dom_leaf, working_table) for dom_leaf in dom]
|
||||
|
||||
elif not field and field_path[0] in MAGIC_COLUMNS:
|
||||
results_to_stack.append(leaf)
|
||||
|
||||
elif not field:
|
||||
raise ValueError("Invalid field %r in leaf %r" % (left, leaf))
|
||||
raise ValueError("Invalid field %r in leaf %r" % (left, str(leaf)))
|
||||
|
||||
# ----------------------------------------
|
||||
# PATH SPOTTED
|
||||
# -> XX
|
||||
# -> many2one or one2many with _auto_join:
|
||||
# - add a join, then jump into linked field: field.remaining on
|
||||
# src_table is replaced by remaining on dst_table, and set for re-evaluation
|
||||
# - if a domain is defined on the field, add it into evaluation
|
||||
# on the relational table
|
||||
# -> many2one, many2many, one2many: replace by an equivalent computed
|
||||
# domain, given by recursively searching on the remaining of the path
|
||||
# -> note: hack about fields.property should not be necessary anymore
|
||||
# as after transforming the field, it will go through this loop once again
|
||||
# ----------------------------------------
|
||||
|
||||
elif len(field_path) > 1 and field._type == 'many2one' and field._auto_join:
|
||||
# res_partner.parent_id = res_partner.id
|
||||
# res_partner.state_id = res_partner__state_id.id
|
||||
leaf.add_join_context(field_path[0], field_path[0], relational_table, 'id')
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf((field_path[1], operator, right), relational_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, (field_path[1], operator, right), relational_table))
|
||||
|
||||
elif len(field_path) > 1 and field._type == 'one2many' and field._auto_join:
|
||||
# res_partner.id = res_partner.parent_id
|
||||
# res_partner.id = res_partner__bank_ids.partner_id
|
||||
leaf.add_join_context(field_path[0], 'id', relational_table, field._fields_id)
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf((field_path[1], operator, right), relational_table))
|
||||
domain = field._domain(working_table) if callable(field._domain) else field._domain
|
||||
if domain:
|
||||
domain = normalize_domain(domain)
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, AND_OPERATOR, relational_table))
|
||||
for elem in domain:
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, elem, relational_table))
|
||||
print '--> appending %s' % str(leafs_to_stack[-1])
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, (field_path[1], operator, right), relational_table))
|
||||
|
||||
elif len(field_path) > 1 and field._auto_join:
|
||||
assert False, \
|
||||
|
@ -817,10 +842,10 @@ class expression(object):
|
|||
else:
|
||||
# we assume that the expression is valid
|
||||
# we create a dummy leaf for forcing the parsing of the resulting expression
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(AND_OPERATOR, working_table))
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(TRUE_LEAF, working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, AND_OPERATOR, working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, TRUE_LEAF, working_table))
|
||||
for domain_element in fct_domain:
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(domain_element, working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, domain_element, working_table))
|
||||
|
||||
# Applying recursivity on field(one2many)
|
||||
elif field._type == 'one2many' and operator == 'child_of':
|
||||
|
@ -829,7 +854,7 @@ class expression(object):
|
|||
dom = self.child_of_domain(cr, uid, left, ids2, relational_table, prefix=field._obj)
|
||||
else:
|
||||
dom = self.child_of_domain(cr, uid, 'id', ids2, working_table, parent=left)
|
||||
leafs_to_stack += [leaf.create_substitution_leaf(dom_leaf, working_table) for dom_leaf in dom]
|
||||
leafs_to_stack += [self.create_substitution_leaf(leaf, dom_leaf, working_table) for dom_leaf in dom]
|
||||
|
||||
elif field._type == 'one2many':
|
||||
call_null = True
|
||||
|
@ -848,17 +873,17 @@ class expression(object):
|
|||
if operator in ['like', 'ilike', 'in', '=']:
|
||||
#no result found with given search criteria
|
||||
call_null = False
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(FALSE_LEAF, working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, FALSE_LEAF, working_table))
|
||||
else:
|
||||
ids2 = select_from_where(cr, field._fields_id, relational_table._table, 'id', ids2, operator)
|
||||
if ids2:
|
||||
call_null = False
|
||||
o2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(('id', o2m_op, ids2), working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, ('id', o2m_op, ids2), working_table))
|
||||
|
||||
if call_null:
|
||||
o2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(('id', o2m_op, select_distinct_from_where_not_null(cr, field._fields_id, relational_table._table)), working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, ('id', o2m_op, select_distinct_from_where_not_null(cr, field._fields_id, relational_table._table)), working_table))
|
||||
|
||||
elif field._type == 'many2many':
|
||||
rel_table, rel_id1, rel_id2 = field._sql_names(working_table)
|
||||
|
@ -872,7 +897,7 @@ class expression(object):
|
|||
ids2 = self.to_ids(cr, uid, right, relational_table, context)
|
||||
dom = self.child_of_domain(cr, uid, 'id', ids2, relational_table)
|
||||
ids2 = relational_table.search(cr, uid, dom, context=context)
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(('id', 'in', _rec_convert(ids2)), working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, ('id', 'in', _rec_convert(ids2)), working_table))
|
||||
else:
|
||||
call_null_m2m = True
|
||||
if right is not False:
|
||||
|
@ -889,17 +914,17 @@ class expression(object):
|
|||
if operator in ['like', 'ilike', 'in', '=']:
|
||||
#no result found with given search criteria
|
||||
call_null_m2m = False
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(FALSE_LEAF, working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, FALSE_LEAF, working_table))
|
||||
else:
|
||||
operator = 'in' # operator changed because ids are directly related to main object
|
||||
else:
|
||||
call_null_m2m = False
|
||||
m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(('id', m2m_op, select_from_where(cr, rel_id1, rel_table, rel_id2, res_ids, operator) or [0]), working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, ('id', m2m_op, select_from_where(cr, rel_id1, rel_table, rel_id2, res_ids, operator) or [0]), working_table))
|
||||
|
||||
if call_null_m2m:
|
||||
m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(('id', m2m_op, select_distinct_from_where_not_null(cr, rel_id1, rel_table)), working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, ('id', m2m_op, select_distinct_from_where_not_null(cr, rel_id1, rel_table)), working_table))
|
||||
|
||||
elif field._type == 'many2one':
|
||||
if operator == 'child_of':
|
||||
|
@ -908,7 +933,7 @@ class expression(object):
|
|||
dom = self.child_of_domain(cr, uid, left, ids2, relational_table, prefix=field._obj)
|
||||
else:
|
||||
dom = self.child_of_domain(cr, uid, 'id', ids2, working_table, parent=left)
|
||||
leafs_to_stack += [leaf.create_substitution_leaf(dom_leaf, working_table) for dom_leaf in dom]
|
||||
leafs_to_stack += [self.create_substitution_leaf(leaf, dom_leaf, working_table) for dom_leaf in dom]
|
||||
else:
|
||||
def _get_expression(relational_table, cr, uid, left, right, operator, context=None):
|
||||
if context is None:
|
||||
|
@ -932,7 +957,7 @@ class expression(object):
|
|||
# resolve string-based m2o criterion into IDs
|
||||
if isinstance(right, basestring) or \
|
||||
right and isinstance(right, (tuple, list)) and all(isinstance(item, basestring) for item in right):
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(_get_expression(relational_table, cr, uid, left, right, operator, context=context), working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, _get_expression(relational_table, cr, uid, left, right, operator, context=context), working_table))
|
||||
else:
|
||||
# right == [] or right == False and all other cases are handled by __leaf_to_sql()
|
||||
results_to_stack.append(leaf)
|
||||
|
@ -947,7 +972,7 @@ class expression(object):
|
|||
elif operator in ('<', '<='):
|
||||
right += ' 23:59:59'
|
||||
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf((left, operator, right), working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, (left, operator, right), working_table))
|
||||
|
||||
elif field.translate:
|
||||
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
|
||||
|
@ -982,7 +1007,7 @@ class expression(object):
|
|||
right,
|
||||
right,
|
||||
]
|
||||
leafs_to_stack.append(leaf.create_substitution_leaf(('id', 'inselect', (subselect, params)), working_table))
|
||||
leafs_to_stack.append(self.create_substitution_leaf(leaf, ('id', 'inselect', (subselect, params)), working_table))
|
||||
|
||||
else:
|
||||
results_to_stack.append(leaf)
|
||||
|
|
Loading…
Reference in New Issue