[IMP] models, fields: handle the case where field.translate is a callable

Using `field.translate(callback, value)` returns the translation of the given
field value by using the function `callback` to translate simple terms in
`value`.  In practice, the function `callback` uses a translation mapping
retrieved from `ir.translation`.
This commit is contained in:
Raphael Collet
2015-07-06 16:58:03 +02:00
parent d5a1d7b6ae
commit 8ddfb1888b
7 changed files with 133 additions and 49 deletions
+18 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from openerp.osv import fields, osv, api
from openerp.tools.translate import _
from openerp.exceptions import UserError
@@ -84,3 +84,20 @@ class ir_translation(osv.Model):
"""
params += ('machine', 'standard', 'ultra', 'pro',)
return (query, params)
@api.model
def _get_terms_query(self, field, records):
query, params = super(ir_translation, self)._get_terms_query(field, records)
# order translations from worst to best
query += """
ORDER BY
CASE
WHEN gengo_translation=%s then 10
WHEN gengo_translation=%s then 20
WHEN gengo_translation=%s then 30
WHEN gengo_translation=%s then 40
ELSE 0
END ASC
"""
params += ('machine', 'standard', 'ultra', 'pro')
return query, params
+31
View File
@@ -363,6 +363,37 @@ class ir_translation(osv.osv):
res_id = tuple(res_id)
return self.__get_source(cr, uid, name, types, lang, source, res_id)
@api.model
def _get_terms_query(self, field, records):
""" Utility function that makes the query for field terms. """
query = """ SELECT id, res_id, src, value FROM ir_translation
WHERE lang=%s AND type=%s AND name=%s AND res_id IN %s """
name = "%s,%s" % (field.model_name, field.name)
params = (records.env.lang, 'model', name, tuple(records.ids))
return query, params
@api.model
def _get_terms_mapping(self, field, records):
""" Return a function mapping a (trans_id, source, value) to a value.
This method is called before querying the database for translations.
"""
return lambda tid, src, value: value
@api.model
def _get_terms_translations(self, field, records):
""" Return the terms and translations of a given `field` on `records`.
:return: {record_id: {source: value}}
"""
result = {rid: {} for rid in records.ids}
if records:
map_trans = self._get_terms_mapping(field, records)
query, params = self._get_terms_query(field, records)
self._cr.execute(query, params)
for trans_id, res_id, src, value in self._cr.fetchall():
result[res_id][src] = map_trans(trans_id, src, value)
return result
@api.model
@tools.ormcache_context('model_name', keys=('lang',))
def get_field_string(self, model_name):
@@ -8,7 +8,7 @@
<search string="Translations">
<filter icon="terp-gdu-smart-failing"
string="Untranslated"
domain="['|',('value', '=', False),('value','=','')]"/>
domain="['|','|',('state','=','to_translate'),('value', '=', False),('value','=','')]"/>
<filter name="openerp-web"
string="Web-only translations"
domain="[('comments', 'like', 'openerp-web')]"/>
@@ -61,6 +61,7 @@
<field name="name"/>
<field name="lang"/>
<field name="type"/>
<field name="state"/>
</tree>
</field>
</record>
+42 -4
View File
@@ -1110,15 +1110,50 @@ class _String(Field):
_column_translate = property(attrgetter('translate'))
_related_translate = property(attrgetter('translate'))
_description_translate = property(attrgetter('translate'))
def _description_translate(self, env):
return bool(self.translate)
def get_trans_terms(self, value):
""" Return the sequence of terms to translate found in `value`. """
if not callable(self.translate):
return [value] if value else []
terms = []
self.translate(terms.append, value)
return terms
def get_trans_func(self, records):
""" Return a translation function `translate` for `self` on the given
records; the function call `translate(record_id, value)` translates the
field value to the language given by the environment of `records`.
"""
if callable(self.translate):
rec_src_trans = records.env['ir.translation']._get_terms_translations(self, records)
def translate(record_id, value):
src_trans = rec_src_trans[record_id]
return self.translate(src_trans.get, value)
else:
rec_trans = records.env['ir.translation']._get_ids(
'%s,%s' % (self.model_name, self.name), 'model', records.env.lang, records.ids)
def translate(record_id, value):
return rec_trans.get(record_id) or value
return translate
class Char(_String):
""" Basic string field, can be length-limited, usually displayed as a
single-line string in clients
single-line string in clients.
:param int size: the maximum size of values stored for that field
:param bool translate: whether the values of this field can be translated
:param translate: enable the translation of the field's values; use
`translate=True` to translate field values as a whole; `translate` may
also be a callable such that `translate(callback, value)` translates
`value` by using `callback(term)` to retrieve the translation of terms.
"""
type = 'char'
_slots = {
@@ -1143,7 +1178,10 @@ class Text(_String):
""" Very similar to :class:`~.Char` but used for longer contents, does not
have a size and usually displayed as a multiline text box.
:param translate: whether the value of this field can be translated
:param translate: enable the translation of the field's values; use
`translate=True` to translate field values as a whole; `translate` may
also be a callable such that `translate(callback, value)` translates
`value` by using `callback(term)` to retrieve the translation of terms.
"""
type = 'text'
+26 -6
View File
@@ -2135,7 +2135,8 @@ class BaseModel(object):
)
model, alias = parent_model, parent_alias
# handle the case where the field is translated
if model._columns[field].translate:
translate = model._columns[field].translate
if translate and not callable(translate):
return model._generate_translated_field(alias, field, query)
else:
return '"%s"."%s"' % (alias, field)
@@ -3222,6 +3223,7 @@ class BaseModel(object):
field
for field in fields
if field.base_field.column._classic_write
if not (field.inherited and callable(field.base_field.column.translate))
]
# the query may involve several tables: we need fully-qualified names
@@ -3254,8 +3256,18 @@ class BaseModel(object):
result.extend(cr.dictfetchall())
ids = [vals['id'] for vals in result]
fetched = self.browse(ids)
if ids:
# translate the fields if necessary
if context.get('lang'):
for field in fields_pre:
if not field.inherited and callable(field.column.translate):
f = field.name
translate = field.get_trans_func(fetched)
for vals in result:
vals[f] = translate(vals['id'], vals[f])
# apply the symbol_get functions of the fields we just read
for field in fields_pre:
symbol_get = field.base_field.column._symbol_get
@@ -3312,7 +3324,6 @@ class BaseModel(object):
record._cache.update(record._convert_to_cache(vals, validate=False))
# store failed values in cache for the records that could not be read
fetched = self.browse(ids)
missing = self - fetched
if missing:
extras = fetched - self
@@ -3795,7 +3806,7 @@ class BaseModel(object):
upd_todo = []
updend = []
direct = []
totranslate = context.get('lang', False) and (context['lang'] != 'en_US')
totranslate = context.get('lang') and context['lang'] != 'en_US'
for field in vals:
ffield = self._fields.get(field)
if ffield and ffield.deprecated:
@@ -3805,7 +3816,7 @@ class BaseModel(object):
if hasattr(column, 'selection') and vals[field]:
self._check_selection_field_value(cr, user, field, vals[field], context=context)
if column._classic_write and not hasattr(column, '_fnct_inv'):
if (not totranslate) or not column.translate:
if not (totranslate and column.translate):
updates.append((field, '%s', column._symbol_set[1](vals[field])))
direct.append(field)
else:
@@ -3833,7 +3844,16 @@ class BaseModel(object):
if totranslate:
# TODO: optimize
for f in direct:
if self._columns[f].translate:
if callable(self._columns[f].translate):
# We do not handle the update of a field where translate
# is a callable. One should update the value without a
# language, or update translated terms separately.
raise ValidationError(_(
"The translation of the field '%s' cannot be "
"directly updated. You can either update its value "
"in English, or update translated terms separately."
) % self._fields[f].string)
elif self._columns[f].translate:
src_trans = self.pool[self._name].read(cr, user, ids, [f])[0][f]
if not src_trans:
src_trans = vals[f]
@@ -4555,7 +4575,7 @@ class BaseModel(object):
elif order_field in self._columns:
order_column = self._columns[order_field]
if order_column._classic_read:
if order_column.translate:
if order_column.translate and not callable(order_column.translate):
inner_clause = self._generate_translated_field(self._table, order_field, query)
else:
inner_clause = '"%s"."%s"' % (self._table, order_field)
+1 -1
View File
@@ -1026,7 +1026,7 @@ class expression(object):
right += ' 00:00:00'
push(create_substitution_leaf(leaf, (left, operator, right), model))
elif column.translate and right:
elif column.translate and not callable(column.translate) and right:
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(operator, operator)
if need_wildcard:
+13 -36
View File
@@ -723,33 +723,14 @@ def trans_generate(lang, modules, cr):
except AttributeError, exc:
_logger.error("name error in %s: %s", xml_name, str(exc))
continue
objmodel = registry.get(obj.model)
if (objmodel is None or field_name not in objmodel._columns
or not objmodel._translate):
field_model = registry.get(obj.model)
if (field_model is None or not field_model._translate or
field_name not in field_model._fields):
continue
field_def = objmodel._columns[field_name]
name = "%s,%s" % (encode(obj.model), field_name)
push_translation(module, 'field', name, 0, encode(field_def.string))
if field_def.help:
push_translation(module, 'help', name, 0, encode(field_def.help))
if field_def.translate:
ids = objmodel.search(cr, uid, [])
obj_values = objmodel.read(cr, uid, ids, [field_name])
for obj_value in obj_values:
res_id = obj_value['id']
if obj.name in ('ir.model', 'ir.ui.menu'):
res_id = 0
model_data_ids = model_data_obj.search(cr, uid, [
('model', '=', model),
('res_id', '=', res_id),
])
if not model_data_ids:
push_translation(module, 'model', name, 0, encode(obj_value[field_name]))
field_def = field_model._fields[field_name]
if hasattr(field_def, 'selection') and isinstance(field_def.selection, (list, tuple)):
name = "%s,%s" % (encode(obj.model), field_name)
for dummy, val in field_def.selection:
push_translation(module, 'selection', name, 0, encode(val))
@@ -761,9 +742,7 @@ def trans_generate(lang, modules, cr):
parse_func = trans_parse_rml
report_type = "report"
elif obj.report_xsl:
fname = obj.report_xsl
parse_func = trans_parse_xsl
report_type = "xsl"
continue
if fname and obj.report_type in ('pdf', 'xsl'):
try:
report_file = misc.file_open(fname)
@@ -776,17 +755,15 @@ def trans_generate(lang, modules, cr):
except (IOError, etree.XMLSyntaxError):
_logger.exception("couldn't export translation for report %s %s %s", name, report_type, fname)
for field_name, field_def in obj._columns.items():
if model == 'ir.model' and field_name == 'name' and obj.name == obj.model:
# ignore model name if it is the technical one, nothing to translate
continue
if field_def.translate:
for field_name, field_def in obj._fields.iteritems():
if getattr(field_def, 'translate', None):
name = model + "," + field_name
try:
term = obj[field_name] or ''
except:
term = ''
push_translation(module, 'model', name, xml_name, encode(term))
value = obj[field_name] or ''
except Exception:
continue
for term in set(field_def.get_trans_terms(value)):
push_translation(module, 'model', name, xml_name, encode(term))
# End of data for ir.model.data query results