[IMP] orm.search: improved support for "order" param: multiple fields, m2o fields, stored function fields, inherited fields

Hopefully this is stable already, otherwise do not hesitate to yell at me ;-)

bzr revid: odo@openerp.com-20100917144807-xvzhdqbk1d6izr9n
This commit is contained in:
Olivier Dony 2010-09-17 16:48:07 +02:00
parent cd3692125f
commit dcc4e7873e
1 changed files with 78 additions and 23 deletions

View File

@ -2314,7 +2314,7 @@ class orm(orm_template):
:param tables: list of table._table names enclosed in double quotes as returned :param tables: list of table._table names enclosed in double quotes as returned
by _where_calc() by _where_calc()
:param where_clause: current list of WHERE clause params :param where_clause: current list of WHERE clause params
:return: (table, where_clause, qualified_field) where ``table`` and ``where_clause`` are the updated :return: (tables, where_clause, qualified_field) where ``tables`` and ``where_clause`` are the updated
versions of the parameters, and ``qualified_field`` is the qualified name of ``field`` versions of the parameters, and ``qualified_field`` is the qualified name of ``field``
in the form ``table.field``, to be referenced in queries. in the form ``table.field``, to be referenced in queries.
""" """
@ -3926,7 +3926,7 @@ class orm(orm_template):
def _check_qorder(self, word): def _check_qorder(self, word):
if not regex_order.match(word): if not regex_order.match(word):
raise except_orm(_('AccessError'), _('Bad query.')) raise except_orm(_('AccessError'), _('Invalid "order" specified. A valid "order" specification is a comma-separated list of valid field names (optionally followed by asc/desc for the direction)'))
return True return True
def _apply_ir_rules(self, cr, uid, where_clause, where_clause_params, tables, mode='read', model_name=None, context=None): def _apply_ir_rules(self, cr, uid, where_clause, where_clause_params, tables, mode='read', model_name=None, context=None):
@ -3952,6 +3952,79 @@ class orm(orm_template):
return True return True
return False return False
def _generate_m2o_order_by(self, order_field, tables, where_clause):
"""
Add possibly missing JOIN and generate the ORDER BY clause for m2o fields,
either native m2o fields or function/related fields that are stored, including
intermediate JOINs for inheritance if required.
"""
if order_field not in self._columns and order_field in self._inherit_fields:
# also add missing joins for reaching the table containing the m2o field
tables, where_clause, qualified_field = self._inherits_join_calc(order_field, tables, where_clause)
order_field_column = self._inherit_fields[order_field][2]
else:
qualified_field = '"%s"."%s"' % (self._table, order_field)
order_field_column = self._columns[order_field]
assert order_field_column._type == 'many2one', 'Invalid field passed to _generate_m2o_order_by()'
assert order_field_column._classic_write or getattr(order_field_column, 'store', False), "Many2one function/related fields must be stored to be used as ordering fields"
# figure out the applicable order_by for the m2o
dest_model = self.pool.get(order_field_column._obj)
m2o_order = dest_model._order
if not regex_order.match(m2o_order):
# _order is complex, can't use it here, so we default to _rec_name
m2o_order = dest_model._rec_name
else:
# extract the first field name, to be able to qualify it and add desc/asc
m2o_order = m2o_order.split(",",1)[0].strip().split(" ",1)[0]
# the perhaps missing join:
quoted_model_table = '"%s"' % dest_model._table
if quoted_model_table not in tables:
tables.append(quoted_model_table)
where_clause.append('%s = %s.id' % (qualified_field, quoted_model_table))
return ('%s.%s' % (quoted_model_table, m2o_order), tables, where_clause)
def _generate_order_by(self, order_spec, tables, where_clause):
"""
Attempt to consruct an appropriate ORDER BY clause based on order_spec, which must be
a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.
:raise" except_orm in case order_spec is malformed
"""
order_by_clause = self._order
if order_spec:
order_by_elements = []
self._check_qorder(order_spec)
for order_part in order_spec.split(','):
order_split = order_part.strip().split(' ')
order_field = order_split[0].strip()
order_direction = order_split[1].strip() if len(order_split) == 2 else ''
if order_field in self._columns:
order_column = self._columns[order_field]
if order_column._classic_read:
order_by_clause = '"%s"."%s"' % (self._table, order_field)
elif order_column._type == 'many2one':
order_by_clause, tables, where_clause = self._generate_m2o_order_by(order_field, tables, where_clause)
else:
continue # ignore non-readable or "non-joignable" fields
elif order_field in self._inherit_fields:
parent_obj = self.pool.get(self._inherit_fields[order_field][0])
order_column = parent_obj._columns[order_field]
if order_column._classic_read:
tables, where_clause, order_by_clause = self._inherits_join_calc(order_field, tables, where_clause)
elif order_column._type == 'many2one':
order_by_clause, tables, where_clause = self._generate_m2o_order_by(order_field, tables, where_clause)
else:
continue # ignore non-readable or "non-joignable" fields
order_by_elements.append("%s %s" % (order_by_clause, order_direction))
order_by_clause = ",".join(order_by_elements)
return order_by_clause and (' ORDER BY %s ' % order_by_clause) or ''
def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None): def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
""" """
Private implementation of search() method, allowing specifying the uid to use for the access right check. Private implementation of search() method, allowing specifying the uid to use for the access right check.
@ -3982,35 +4055,17 @@ class orm(orm_template):
tables = list(set(tables).union(set(previous_tables))) tables = list(set(tables).union(set(previous_tables)))
where = where_clause where = where_clause
order_by = self._generate_order_by(order, tables, where_clause)
order_by = self._order
if order:
self._check_qorder(order)
o = order.split(' ')[0]
if (o in self._columns):
# we can only do efficient sort if the fields is stored in database
if getattr(self._columns[o], '_classic_read'):
order_by = order
elif (o in self._inherit_fields):
parent_obj = self.pool.get(self._inherit_fields[o][0])
if getattr(parent_obj._columns[o], '_classic_read'):
# Allowing inherits'ed field for server side sorting, if they can be sorted by the dbms
inherited_tables, inherit_join, order_by = self._inherits_join_calc(o, tables, where_clause)
limit_str = limit and ' limit %d' % limit or '' limit_str = limit and ' limit %d' % limit or ''
offset_str = offset and ' offset %d' % offset or '' offset_str = offset and ' offset %d' % offset or ''
where_str = where and (" WHERE %s" % " AND ".join(where)) or ''
if where:
where_str = " WHERE %s" % " AND ".join(where)
else:
where_str = ""
if count: if count:
cr.execute('select count(%s.id) from ' % self._table + cr.execute('select count(%s.id) from ' % self._table +
','.join(tables) + where_str + limit_str + offset_str, where_clause_params) ','.join(tables) + where_str + limit_str + offset_str, where_clause_params)
res = cr.fetchall() res = cr.fetchall()
return res[0][0] return res[0][0]
cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str +' order by '+order_by+limit_str+offset_str, where_clause_params) cr.execute('select %s.id from ' % self._table + ','.join(tables) + where_str + order_by + limit_str+offset_str, where_clause_params)
res = cr.fetchall() res = cr.fetchall()
return [x[0] for x in res] return [x[0] for x in res]