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] = {

Reply via email to