From 489494e733920e4f18e8c86b278166156ff678cf Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Mon, 1 Apr 2019 09:46:56 +0000 Subject: [PATCH] [FIX] base: upsert translations during copy_translations When doing a copy, create the new translations directly in SQL and handle potential conflicts. Conflicts can occure in case of reinstallation as showed in opw-1955062 and opw-1950117. In case of "leftovers" of translations (e.g. remaining after the uninstallation of a module), creating the new translations (when reinstalling the module) may produce a conflict with (type, name, res_id, lang) and raise an error. This is NOT a problem of reading .po file during installation (which handles correctly conflicts) but of business code creating new records and linked translations (e.g. website copying website.menu records). Removing old translations during uninstall is handled in a previous commit in ir.model.fields _drop_column method. This commit fixes the reinstallation on instances with leftover translations and fixes the issue without needing a manual intervention (i.e. delete the old translations manually). Closes odoo/odoo#32056 Signed-off-by: Raphael Collet (rco) --- odoo/addons/base/models/ir_translation.py | 41 +++++++++++++++++++++++ odoo/models.py | 5 +-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/odoo/addons/base/models/ir_translation.py b/odoo/addons/base/models/ir_translation.py index 940708ea0b7..09b344be2a1 100644 --- a/odoo/addons/base/models/ir_translation.py +++ b/odoo/addons/base/models/ir_translation.py @@ -670,6 +670,47 @@ class IrTranslation(models.Model): }) self._modified_model(field.model_name) + @api.model + def _upsert_translations(self, vals_list): + """ Insert or update translations of type 'model' or 'model_terms'. + + This method is used for creations of translations where the given + ``vals_list`` is trusted to be the right values and potential + conflicts should be updated to the new given value. + """ + rows_by_type = defaultdict(list) + for vals in vals_list: + rows_by_type[vals['type']].append(( + vals['name'], vals['lang'], vals['res_id'], vals['src'], vals['type'], + vals.get('module'), vals['value'], vals.get('state'), vals.get('comments'), + )) + + if rows_by_type['model']: + query = """ + INSERT INTO ir_translation (name, lang, res_id, src, type, + module, value, state, comments) + VALUES {} + ON CONFLICT (type, lang, name, res_id) WHERE type='model' + DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = + (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, + EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments) + WHERE EXCLUDED.value IS NOT NULL AND EXCLUDED.value != ''; + """.format(", ".join(["%s"] * len(rows_by_type['model']))) + self.env.cr.execute(query, rows_by_type['model']) + + if rows_by_type['model_terms']: + query = """ + INSERT INTO ir_translation (name, lang, res_id, src, type, + module, value, state, comments) + VALUES {} + ON CONFLICT (type, name, lang, res_id, md5(src)) + DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = + (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, + EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments) + WHERE EXCLUDED.value IS NOT NULL AND EXCLUDED.value != ''; + """.format(", ".join(["%s"] * len(rows_by_type['model_terms']))) + self.env.cr.execute(query, rows_by_type['model_terms']) + @api.model def translate_fields(self, model, id, field=None): """ Open a view for translating the field(s) of the record (model, id). """ diff --git a/odoo/models.py b/odoo/models.py index 6423e18f314..0972da58928 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -4242,11 +4242,12 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): if vals['lang'] == old.env.lang and field.translate is True: # force a source if the new_val was not changed by copy override if new_val == old[name]: - vals['source'] = old_wo_lang[name] + new_wo_lang[name] = old_wo_lang[name] + vals['src'] = old_wo_lang[name] # the value should be the new value (given by copy()) vals['value'] = new_val vals_list.append(vals) - Translation.create(vals_list) + Translation._upsert_translations(vals_list) @api.multi @api.returns('self', lambda value: value.id)