[IMP] model, query: implement sort on translatable fields
The methods `BaseModel._inherits_join_calc` and `BaseModel._generate_order_by` are modified to add a JOIN to the translation table for translated fields, so that the translation value is directly accessible in the query itself. For the JOIN with the translation table to work, the method `Query.add_join` is extended to take extra conditions in the join clause, together with parameters.
This commit is contained in:
committed by
Raphael Collet
parent
44efcf8066
commit
9593a2930f
@@ -464,6 +464,17 @@ class test_translation(common.TransactionCase):
|
||||
fr_context_cat = self.res_category.browse(cr, uid, self.new_fr_cat_id, context={'lang':'fr_FR'})
|
||||
self.assertEqual(fr_context_cat.name, 'Clients (copie)', "Did not used default value for translated value")
|
||||
|
||||
def test_104_orderby_translated_field(self):
|
||||
""" Test search ordered by a translated field. """
|
||||
# create a category with a French translation
|
||||
category = self.env['res.partner.category'].create({'name': 'Padawans'})
|
||||
category_fr = category.with_context(lang='fr_FR')
|
||||
category_fr.write({'name': 'Apprentis'})
|
||||
# search for categories, and sort them by (translated) name
|
||||
categories = category_fr.search([('id', 'in', [self.cat_id, category.id])], order='name')
|
||||
self.assertEqual(categories.ids, [category.id, self.cat_id],
|
||||
"Search ordered by translated name should return Padawans (Apprentis) before Customers (Clients)")
|
||||
|
||||
test_state = None
|
||||
#: Stores state information across multiple test classes
|
||||
def setUpModule():
|
||||
|
||||
+30
-2
@@ -2183,7 +2183,11 @@ class BaseModel(object):
|
||||
implicit=True,
|
||||
)
|
||||
model, alias = parent_model, parent_alias
|
||||
return '"%s"."%s"' % (alias, field)
|
||||
# handle the case where the field is translated
|
||||
if model._columns[field].translate:
|
||||
return model._generate_translated_field(alias, field, query)
|
||||
else:
|
||||
return '"%s"."%s"' % (alias, field)
|
||||
|
||||
def _parent_store_compute(self, cr):
|
||||
if not self._parent_store:
|
||||
@@ -4525,6 +4529,27 @@ class BaseModel(object):
|
||||
apply_rule(rule_where_clause, rule_where_clause_params, rule_tables,
|
||||
parent_model=inherited_model)
|
||||
|
||||
@api.model
|
||||
def _generate_translated_field(self, table_alias, field, query):
|
||||
"""
|
||||
Add possibly missing JOIN with translations table to ``query`` and
|
||||
generate the expression for the translated field.
|
||||
|
||||
:return: the qualified field name (or expression) to use for ``field``
|
||||
"""
|
||||
lang = self._context.get('lang')
|
||||
if lang and lang != 'en_US':
|
||||
alias, alias_statement = query.add_join(
|
||||
(table_alias, 'ir_translation', 'id', 'res_id', field),
|
||||
implicit=False,
|
||||
outer=True,
|
||||
extra='"{rhs}"."name" = %s AND "{rhs}"."lang" = %s AND "{rhs}"."value" != %s',
|
||||
extra_params=["%s,%s" % (self._name, field), lang, ""],
|
||||
)
|
||||
return 'COALESCE("%s"."%s", "%s"."%s")' % (alias, 'value', table_alias, field)
|
||||
else:
|
||||
return '"%s"."%s"' % (table_alias, field)
|
||||
|
||||
@api.model
|
||||
def _generate_m2o_order_by(self, order_field, query):
|
||||
"""
|
||||
@@ -4593,7 +4618,10 @@ class BaseModel(object):
|
||||
elif order_field in self._columns:
|
||||
order_column = self._columns[order_field]
|
||||
if order_column._classic_read:
|
||||
inner_clause = '"%s"."%s"' % (self._table, order_field)
|
||||
if order_column.translate:
|
||||
inner_clause = self._generate_translated_field(self._table, order_field, query)
|
||||
else:
|
||||
inner_clause = '"%s"."%s"' % (self._table, order_field)
|
||||
elif order_column._type == 'many2one':
|
||||
inner_clause = self._generate_m2o_order_by(order_field, query)
|
||||
else:
|
||||
|
||||
+52
-18
@@ -21,7 +21,7 @@ class Query(object):
|
||||
- etc.
|
||||
"""
|
||||
|
||||
def __init__(self, tables=None, where_clause=None, where_clause_params=None, joins=None):
|
||||
def __init__(self, tables=None, where_clause=None, where_clause_params=None, joins=None, extras=None):
|
||||
|
||||
# holds the list of tables joined using default JOIN.
|
||||
# the table names are stored double-quoted (backwards compatibility)
|
||||
@@ -49,6 +49,21 @@ class Query(object):
|
||||
# LEFT JOIN "table_c" ON ("table_a"."table_a_col2" = "table_c"."table_c_col")
|
||||
self.joins = joins or {}
|
||||
|
||||
# holds extra conditions for table joins that should not be in the where
|
||||
# clause but in the join condition itself. The dict is used as follows:
|
||||
#
|
||||
# self.extras = {
|
||||
# ('table_a', ('table_b', 'table_a_col1', 'table_b_col', 'LEFT JOIN')):
|
||||
# ('"table_b"."table_b_col3" = %s', [42])
|
||||
# }
|
||||
#
|
||||
# which should lead to the following SQL:
|
||||
#
|
||||
# SELECT ... FROM "table_a"
|
||||
# LEFT JOIN "table_b" ON ("table_a"."table_a_col1" = "table_b"."table_b_col" AND "table_b"."table_b_col3" = 42)
|
||||
# ...
|
||||
self.extras = extras or {}
|
||||
|
||||
def _get_table_aliases(self):
|
||||
from openerp.osv.expression import get_alias_from_query
|
||||
return [get_alias_from_query(from_statement)[1] for from_statement in self.tables]
|
||||
@@ -61,7 +76,7 @@ class Query(object):
|
||||
mapping[statement] = table
|
||||
return mapping
|
||||
|
||||
def add_join(self, connection, implicit=True, outer=False):
|
||||
def add_join(self, connection, implicit=True, outer=False, extra=None, extra_params=[]):
|
||||
""" Join a destination table to the current table.
|
||||
|
||||
:param implicit: False if the join is an explicit join. This allows
|
||||
@@ -69,7 +84,7 @@ class Query(object):
|
||||
OpenERP 7.0. It therefore adds the JOIN specified in ``connection``
|
||||
If True, the join is done implicitely, by adding the table alias
|
||||
in the from clause and the join condition in the where clause
|
||||
of the query. Implicit joins do not handle outer parameter.
|
||||
of the query. Implicit joins do not handle outer, extra, extra_params parameters.
|
||||
:param connection: a tuple ``(lhs, table, lhs_col, col, link)``.
|
||||
The join corresponds to the SQL equivalent of::
|
||||
|
||||
@@ -84,6 +99,14 @@ class Query(object):
|
||||
columns so it would not be correct (e.g. for
|
||||
``_inherits`` or when a domain criterion explicitly
|
||||
adds filtering)
|
||||
|
||||
:param extra: A string with the extra join condition (SQL), or None.
|
||||
This is used to provide an additional condition to the join
|
||||
clause that cannot be added in the where clause (e.g., for LEFT
|
||||
JOIN concerns). The condition string should refer to the table
|
||||
aliases as "{lhs}" and "{rhs}".
|
||||
|
||||
:param extra_params: a list of parameters for the `extra` condition.
|
||||
"""
|
||||
from openerp.osv.expression import generate_table_alias
|
||||
(lhs, table, lhs_col, col, link) = connection
|
||||
@@ -107,32 +130,43 @@ class Query(object):
|
||||
else:
|
||||
# add JOIN
|
||||
self.tables.append(alias_statement)
|
||||
self.joins.setdefault(lhs, []).append((alias, lhs_col, col, outer and 'LEFT JOIN' or 'JOIN'))
|
||||
join_tuple = (alias, lhs_col, col, outer and 'LEFT JOIN' or 'JOIN')
|
||||
self.joins.setdefault(lhs, []).append(join_tuple)
|
||||
if extra:
|
||||
extra = extra.format(lhs=lhs, rhs=alias)
|
||||
self.extras[(lhs, join_tuple)] = (extra, extra_params)
|
||||
return alias, alias_statement
|
||||
|
||||
def get_sql(self):
|
||||
""" Returns (query_from, query_where, query_params). """
|
||||
from openerp.osv.expression import get_alias_from_query
|
||||
query_from = ''
|
||||
tables_to_process = list(self.tables)
|
||||
alias_mapping = self._get_alias_mapping()
|
||||
from_clause = []
|
||||
from_params = []
|
||||
|
||||
def add_joins_for_table(table, query_from):
|
||||
for (dest_table, lhs_col, col, join) in self.joins.get(table, []):
|
||||
tables_to_process.remove(alias_mapping[dest_table])
|
||||
query_from += ' %s %s ON ("%s"."%s" = "%s"."%s")' % \
|
||||
(join, alias_mapping[dest_table], table, lhs_col, dest_table, col)
|
||||
query_from = add_joins_for_table(dest_table, query_from)
|
||||
return query_from
|
||||
def add_joins_for_table(lhs):
|
||||
for (rhs, lhs_col, rhs_col, join) in self.joins.get(lhs, []):
|
||||
tables_to_process.remove(alias_mapping[rhs])
|
||||
from_clause.append(' %s %s ON ("%s"."%s" = "%s"."%s"' % \
|
||||
(join, alias_mapping[rhs], lhs, lhs_col, rhs, rhs_col))
|
||||
extra = self.extras.get((lhs, (rhs, lhs_col, rhs_col, join)))
|
||||
if extra:
|
||||
from_clause.append(' AND ')
|
||||
from_clause.append(extra[0])
|
||||
from_params.extend(extra[1])
|
||||
from_clause.append(')')
|
||||
add_joins_for_table(rhs)
|
||||
|
||||
for table in tables_to_process:
|
||||
query_from += table
|
||||
for pos, table in enumerate(tables_to_process):
|
||||
if pos > 0:
|
||||
from_clause.append(',')
|
||||
from_clause.append(table)
|
||||
table_alias = get_alias_from_query(table)[1]
|
||||
if table_alias in self.joins:
|
||||
query_from = add_joins_for_table(table_alias, query_from)
|
||||
query_from += ','
|
||||
query_from = query_from[:-1] # drop last comma
|
||||
return query_from, " AND ".join(self.where_clause), self.where_clause_params
|
||||
add_joins_for_table(table_alias)
|
||||
|
||||
return "".join(from_clause), " AND ".join(self.where_clause), from_params + self.where_clause_params
|
||||
|
||||
def __str__(self):
|
||||
return '<osv.Query: "SELECT ... FROM %s WHERE %s" with params: %r>' % self.get_sql()
|
||||
|
||||
Reference in New Issue
Block a user