Browse files

[FIX] models: do not copy translations from specified inherited records

A copy call cn be made with a specified value to an inherits value, e.g.:
   self.env['product.product'].browse(42).copy({'product_tmpl_id': 1})

In such scenario, it is assumed the translations are already correct on the
specified related record and should not be used in the copy translation.
i.e. the translations of the product.template 1 must not be duplicated during
the copy call above

Same logic for One2many fields which should not recursively copy the
translations for user provided values

Closes #27108
  • Loading branch information...
mart-e committed Sep 19, 2018
1 parent 2de8998 commit 60e9e1f738cb04e9d76fed05dda4caa1293963c4
@@ -16,6 +16,7 @@
<record id="message_0_0" model="test_new_api.message">
<field name="discussion" ref="discussion_0"/>
<field name="body">Hey dude!</field>
<field name="label">hello</field>
<record id="message_0_1" model="test_new_api.message">
<field name="discussion" ref="discussion_0"/>
@@ -27,6 +28,11 @@
<field name="body">This is a much longer message</field>
<record id="emailmessage_0_0" model="test_new_api.emailmessage">
<field name="message" ref="message_0_0"/>
<field name="email_to">first@example.localhost</field>
<record id="bool_1" model="domain.bool">
<field name="bool_true" eval="True"/>
@@ -72,7 +72,7 @@ class Discussion(models.Model):
categories = fields.Many2many('test_new_api.category',
'test_new_api_discussion_category', 'discussion', 'category')
participants = fields.Many2many('res.users')
messages = fields.One2many('test_new_api.message', 'discussion')
messages = fields.One2many('test_new_api.message', 'discussion', copy=True)
message_concat = fields.Text(string='Message concatenate')
important_messages = fields.One2many('test_new_api.message', 'discussion',
domain=[('important', '=', True)])
@@ -125,6 +125,7 @@ class Message(models.Model):
'res.partner', compute='_compute_author_partner',
important = fields.Boolean()
label = fields.Char(translate=True)
@api.constrains('author', 'discussion')
@@ -862,6 +862,40 @@ def test_70_x2many_write(self):
self.assertEqual(len(discussion.important_messages), 2)
self.assertEqual(len(discussion.very_important_messages), 2)
def test_80_copy(self):
Translations = self.env['ir.translation']
discussion = self.env.ref('test_new_api.discussion_0')
message = self.env.ref('test_new_api.message_0_0')
message1 = self.env.ref('test_new_api.message_0_1')
email = self.env.ref('test_new_api.emailmessage_0_0')
self.assertEqual(email.message, message)
french = self.env['res.lang']._lang_get('fr_FR') = True
def count(msg):
# return the number of translations of msg.label
return Translations.search_count([
('name', '=', 'test_new_api.message,label'),
('res_id', '=',,
# set a translation for message.label
email.with_context(lang='fr_FR').label = "bonjour"
self.assertEqual(count(message), 1)
self.assertEqual(count(message1), 0)
# setting the parent record should not copy its translations
self.assertEqual(count(message), 1)
self.assertEqual(count(message1), 0)
# setting a one2many should not copy translations on the lines
discussion.copy({'messages': [(6, 0, message1.ids)]})
self.assertEqual(count(message), 1)
self.assertEqual(count(message1), 0)
class TestX2many(common.TransactionCase):
def test_search_many2many(self):
@@ -3824,7 +3824,13 @@ def blacklist_given_fields(model):
return [default]
def copy_translations(old, new):
def copy_translations(old, new, excluded=()):
""" Recursively copy the translations from original to new record
:param old: the original record
:param new: the new record (copy of the original one)
:param excluded: a container of user-provided field names
# avoid recursion through already copied records in case of circular relationship
if '__copy_translations_seen' not in old._context:
old = old.with_context(__copy_translations_seen=defaultdict(set))
@@ -3850,13 +3856,20 @@ def get_trans(field, old, new):
if not field.copy:
if field.type == 'one2many':
if field.inherited and field.related[0] in excluded:
# inherited fields that come from a user-provided parent record
# must not copy translations, as the parent record is not a copy
# of the old parent record
if field.type == 'one2many' and not in excluded:
# we must recursively copy the translations for o2m; here we
# rely on the order of the ids to match the translations as
# foreseen in copy_data()
old_lines = old[name].sorted(key='id')
new_lines = new[name].sorted(key='id')
for (old_line, new_line) in pycompat.izip(old_lines, new_lines):
# don't pass excluded as it is not about those lines
elif field.translate:
@@ -3896,7 +3909,7 @@ def copy(self, default=None):
vals = self.copy_data(default)[0]
# To avoid to create a translation in the lang of the user, copy_translation will do it
new = self.with_context(lang=None).create(vals)
self.with_context(from_copy_translation=True).copy_translations(new, excluded=default or ())
return new

0 comments on commit 60e9e1f

Please sign in to comment.