changeset db49d402632e in trytond:5.0
details: https://hg.tryton.org/trytond?cmd=changeset&node=db49d402632e
description:
Ensure to join with only one translation row
If there are multiple translations for the same record, the join will
generate
result with multiple times the same id on ModelSQL.search.
As DISTINCT ON is not always supported, we must also ensure that
duplicated
translations are all updated.
issue10061
review351261002
(grafted from 8a229271cc541c5ea16971d31f98314412622c32)
diffstat:
trytond/ir/translation.py | 43 +++++++++++++++++--------------
trytond/model/fields/field.py | 58 +++++++++++++++++++++++++-----------------
2 files changed, 57 insertions(+), 44 deletions(-)
diffs (213 lines):
diff -r 653da70634ba -r db49d402632e trytond/ir/translation.py
--- a/trytond/ir/translation.py Fri Feb 12 10:15:00 2021 +0100
+++ b/trytond/ir/translation.py Sat Feb 20 00:48:50 2021 +0100
@@ -485,30 +485,32 @@
return record.model + ',' + field_name
with Transaction().set_context(_check_access=False):
- translations = {}
+ name2translations = defaultdict(list)
for translation in cls.search([
('lang', '=', lang),
('type', '=', ttype),
('name', 'in', [get_name(r) for r in records]),
]):
- translations[translation.name] = translation
+ name2translations[translation.name].append(translation)
to_save = []
for record, value in zip(records, values):
- translation = translations.get(get_name(record))
+ translations = name2translations.get(get_name(record))
if lang == 'en':
src = value
else:
src = getattr(record, field_name)
- if not translation:
+ if not translations:
translation = cls()
translation.name = name
translation.lang = lang
translation.type = ttype
- translation.src = src
- translation.value = value
- translation.fuzzy = False
- to_save.append(translation)
+ translations.append(translation)
+ for translation in translations:
+ translation.src = src
+ translation.value = value
+ translation.fuzzy = False
+ to_save.append(translation)
cls.save(to_save)
return
@@ -516,7 +518,8 @@
with Transaction().set_context(language=Config.get_language()):
records = Model.browse(ids)
- translations = {}
+ id2translations = defaultdict(list)
+ other_translations = defaultdict(list)
with Transaction().set_context(_check_access=False):
for translation in cls.search([
('lang', '=', lang),
@@ -524,9 +527,8 @@
('name', '=', name),
('res_id', 'in', ids),
]):
- translations[translation.res_id] = translation
+ id2translations[translation.res_id].append(translation)
- other_translations = {}
if (lang == Config.get_language()
and Transaction().context.get('fuzzy_translation', True)):
for translation in cls.search([
@@ -535,33 +537,34 @@
('name', '=', name),
('res_id', 'in', ids),
]):
- other_translations.setdefault(translation.res_id, []
- ).append(translation)
+ other_translations[translation.res_id].append(translation)
to_save = []
for record, value in zip(records, values):
- translation = translations.get(record.id)
+ translations = id2translations[record.id]
if lang == Config.get_language():
src = value
else:
src = getattr(record, field_name)
- if not translation:
+ if not translations:
translation = cls()
translation.name = name
translation.lang = lang
translation.type = ttype
translation.res_id = record.id
+ translations.append(translation)
else:
- other_langs = other_translations.get(record.id)
+ other_langs = other_translations[record.id]
if other_langs:
for other_lang in other_langs:
other_lang.src = src
other_lang.fuzzy = True
to_save.append(other_lang)
- translation.value = value
- translation.src = src
- translation.fuzzy = False
- to_save.append(translation)
+ for translation in translations:
+ translation.value = value
+ translation.src = src
+ translation.fuzzy = False
+ to_save.append(translation)
cls.save(to_save)
@classmethod
diff -r 653da70634ba -r db49d402632e trytond/model/fields/field.py
--- a/trytond/model/fields/field.py Fri Feb 12 10:15:00 2021 +0100
+++ b/trytond/model/fields/field.py Sat Feb 20 00:48:50 2021 +0100
@@ -3,8 +3,10 @@
import warnings
from functools import wraps
+import sql
from sql import (operators, Column, Literal, Select, CombiningQuery, Null,
Query, Expression, Cast)
+from sql.aggregate import Min
from sql.conditionals import Coalesce, NullIf
from sql.operators import Concat
@@ -19,6 +21,8 @@
Database = backend.get('Database')
+_sql_version = tuple(map(int, sql.__version__.split('.')))
+
def domain_validate(value):
assert isinstance(value, list), 'domain must be a list'
@@ -393,34 +397,40 @@
def _get_translation_join(self, Model, name,
translation, model, table, from_, language):
if Model.__name__ == 'ir.model':
- return from_.join(translation, 'LEFT',
- condition=(translation.name == Concat(Concat(
- table.model, ','), name))
- & (translation.res_id == -1)
- & (translation.lang == language)
- & (translation.type == 'model')
- & (translation.fuzzy == False))
+ name_ = Concat(Concat(table.model, ','), name)
+ type_ = 'model'
+ res_id = -1
elif Model.__name__ == 'ir.model.field':
+ name_ = Concat(Concat(model.model, ','), table.name)
if name == 'field_description':
type_ = 'field'
else:
type_ = 'help'
- return from_.join(model, 'LEFT',
- condition=model.id == table.model).join(
- translation, 'LEFT',
- condition=(translation.name == Concat(Concat(
- model.model, ','), table.name))
- & (translation.res_id == -1)
- & (translation.lang == language)
- & (translation.type == type_)
- & (translation.fuzzy == False))
+ res_id = -1
+ else:
+ name_ = '%s,%s' % (Model.__name__, name)
+ type_ = 'model'
+ res_id = table.id
+ if backend.name() == 'postgresql' and _sql_version >= (1, 1, 0):
+ query = translation.select(
+ translation.res_id.as_('res_id'),
+ translation.value.as_('value'),
+ distinct=True,
+ distinct_on=[translation.res_id],
+ order_by=[translation.res_id, translation.id.desc])
else:
- return from_.join(translation, 'LEFT',
- condition=(translation.res_id == table.id)
- & (translation.name == '%s,%s' % (Model.__name__, name))
- & (translation.lang == language)
- & (translation.type == 'model')
- & (translation.fuzzy == False))
+ query = translation.select(
+ translation.res_id.as_('res_id'),
+ Min(translation.value).as_('value'),
+ group_by=[translation.res_id])
+ query.where = (
+ (translation.lang == language)
+ & (translation.type == type_)
+ & (translation.name == name_)
+ & (translation.fuzzy == Literal(False))
+ )
+ return query, from_.join(query, 'LEFT',
+ condition=(query.res_id == res_id))
def convert_domain(self, domain, tables, Model):
from trytond.ir.lang import get_parent_language
@@ -438,7 +448,7 @@
column = None
while language:
translation = Translation.__table__()
- join = self._get_translation_join(
+ translation, join = self._get_translation_join(
Model, name, translation, model, table, join, language)
column = Coalesce(NullIf(column, ''), translation.value)
language = get_parent_language(language)
@@ -474,7 +484,7 @@
if key not in tables:
translation = Translation.__table__()
model = IrModel.__table__()
- join = self._get_translation_join(
+ translation, join = self._get_translation_join(
Model, name, translation, model, table, table, language)
if join.left == table:
tables[key] = {