changeset 222402492614 in trytond:5.6
details: https://hg.tryton.org/trytond?cmd=changeset&node=222402492614
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 | 59 ++++++++++++++++++++++++-----------------
trytond/tests/test_modelsql.py | 58 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 116 insertions(+), 44 deletions(-)
diffs (289 lines):
diff -r 7179827c1c12 -r 222402492614 trytond/ir/translation.py
--- a/trytond/ir/translation.py Wed Mar 03 18:11:23 2021 +0100
+++ b/trytond/ir/translation.py Sat Feb 20 00:48:50 2021 +0100
@@ -410,32 +410,34 @@
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:
if not src and not value:
continue
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
@@ -443,7 +445,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),
@@ -451,9 +454,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([
@@ -462,17 +464,16 @@
('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:
if not src and not value:
continue
translation = cls()
@@ -480,17 +481,19 @@
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 7179827c1c12 -r 222402492614 trytond/model/fields/field.py
--- a/trytond/model/fields/field.py Wed Mar 03 18:11:23 2021 +0100
+++ b/trytond/model/fields/field.py Sat Feb 20 00:48:50 2021 +0100
@@ -3,11 +3,14 @@
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
+from trytond import backend
from trytond.pyson import PYSON, PYSONEncoder, PYSONDecoder, Eval
from trytond.const import OPERATORS
from trytond.tools.string_ import StringPartitioned, LazyString
@@ -17,6 +20,8 @@
from ...rpc import RPC
+_sql_version = tuple(map(int, sql.__version__.split('.')))
+
def domain_validate(value):
assert isinstance(value, list), 'domain must be a list'
@@ -492,34 +497,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 == Literal(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 == Literal(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 == Literal(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
@@ -537,7 +548,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)
@@ -573,7 +584,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] = {
diff -r 7179827c1c12 -r 222402492614 trytond/tests/test_modelsql.py
--- a/trytond/tests/test_modelsql.py Wed Mar 03 18:11:23 2021 +0100
+++ b/trytond/tests/test_modelsql.py Sat Feb 20 00:48:50 2021 +0100
@@ -768,6 +768,64 @@
self.assertEqual(record.name, "Foo")
self.assertEqual(other.name, "Baz")
+ @with_transaction()
+ def test_search_unique_result(self):
+ "Test unique result on search"
+ pool = Pool()
+ Model = pool.get('test.modelsql.translation')
+ Translation = pool.get('ir.translation')
+
+ with Transaction().set_context(language=self.default_language):
+ record, = Model.create([{'name': "Foo"}])
+ with Transaction().set_context(language=self.other_language):
+ Model.write([record], {'name': "Bar"})
+
+ translation, = Translation.search([
+ ('lang', '=', self.other_language),
+ ('name', '=', 'test.modelsql.translation,name'),
+ ('type', '=', 'model'),
+ ('res_id', '=', record.id),
+ ])
+ Translation.copy([translation], default={'value': "Baz"})
+
+ with Transaction().set_context(language=self.other_language):
+ self.assertEqual(
+ Model.search([('name', 'like', 'Ba%')]),
+ [record])
+ self.assertEqual(
+ Model.search([], order=[('name', 'DESC')]),
+ [record])
+
+ @unittest.skipIf(backend.name != 'postgresql',
+ "Only PostgreSQL support DISTINCT ON")
+ @with_transaction()
+ def test_search_last_translation(self):
+ "Test unique result on search"
+ pool = Pool()
+ Model = pool.get('test.modelsql.translation')
+ Translation = pool.get('ir.translation')
+
+ with Transaction().set_context(language=self.default_language):
+ record, = Model.create([{'name': "Foo"}])
+ with Transaction().set_context(language=self.other_language):
+ Model.write([record], {'name': "Bar"})
+
+ translation, = Translation.search([
+ ('lang', '=', self.other_language),
+ ('name', '=', 'test.modelsql.translation,name'),
+ ('type', '=', 'model'),
+ ('res_id', '=', record.id),
+ ])
+ Translation.copy([translation], default={'value': "Baz"})
+
+ with Transaction().set_context(language=self.other_language):
+ self.assertEqual(
+ Model.search([('name', '=', 'Baz')]),
+ [record])
+ self.assertEqual(
+ Model.search([('name', '=', 'Bar')]),
+ [])
+
def suite():
suite_ = unittest.TestSuite()