[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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user