From 9043305fa01fc21dc8a7dd996aa20765dbc7d93c Mon Sep 17 00:00:00 2001 From: XavierDo Date: Fri, 2 Nov 2018 11:23:10 +0100 Subject: [PATCH 1/3] [FIX] mail: verify attachment access early to avoid errors later --- addons/mail/models/mail_mail.py | 13 ++++++++++++- addons/mail/models/mail_message.py | 7 +++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index d1ffb1d756b..87305781784 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -62,7 +62,18 @@ class MailMail(models.Model): values['notification'] = True if not values.get('mail_message_id'): self = self.with_context(message_create_from_mail_mail=True) - return super(MailMail, self).create(values) + new_mail = super(MailMail, self).create(values) + if values.get('attachment_ids'): + new_mail.attachment_ids.check(mode='read') + return new_mail + + @api.multi + def write(self, vals): + res = super(MailMail, self).write(vals) + if vals.get('attachment_ids'): + for mail in self: + mail.attachment_ids.check(mode='read') + return res @api.multi def unlink(self): diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 88bd6ae321d..95ff558c900 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -742,6 +742,10 @@ class Message(models.Model): # delegate creation of tracking after the create as sudo to avoid access rights issues tracking_values_cmd = values.pop('tracking_value_ids', False) message = super(Message, self).create(values) + + if values.get('attachment_ids'): + message.attachment_ids.check(mode='read') + if tracking_values_cmd: message.sudo().write({'tracking_value_ids': tracking_values_cmd}) @@ -764,6 +768,9 @@ class Message(models.Model): if 'model' in vals or 'res_id' in vals: self._invalidate_documents() res = super(Message, self).write(vals) + if vals.get('attachment_ids'): + for mail in self: + mail.attachment_ids.check(mode='read') self._invalidate_documents() return res From 5472bbded51056a2322a24304db62e1d65dca464 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Sat, 3 Nov 2018 16:47:43 +0100 Subject: [PATCH 2/3] [FIX] mail: invite: use proper translation terms Fix translation placeholders in invite wizard, to make them easier to understand for translators. Also rework the message generation code to ensure proper XML structure. --- addons/mail/wizard/invite.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/addons/mail/wizard/invite.py b/addons/mail/wizard/invite.py index 1d345af517a..17e5fbf842a 100644 --- a/addons/mail/wizard/invite.py +++ b/addons/mail/wizard/invite.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from lxml import etree +from lxml.html import builder as html + from odoo import _, api, fields, models @@ -12,18 +15,27 @@ class Invite(models.TransientModel): @api.model def default_get(self, fields): result = super(Invite, self).default_get(fields) + if self._context.get('mail_invite_follower_channel_only'): + result['send_mail'] = False + if 'message' not in fields: + return result + user_name = self.env.user.name_get()[0][1] model = result.get('res_model') res_id = result.get('res_id') - if self._context.get('mail_invite_follower_channel_only'): - result['send_mail'] = False - if 'message' in fields and model and res_id: - model_name = self.env['ir.model'].search([('model', '=', model)]).name_get()[0][1] - document_name = self.env[model].browse(res_id).name_get()[0][1] - message = _('

Hello,

%s invited you to follow %s document: %s.

') % (user_name, model_name, document_name) - result['message'] = message - elif 'message' in fields: - result['message'] = _('

Hello,

%s invited you to follow a new document.

') % user_name + if model and res_id: + document = self.env['ir.model'].search([('model', '=', model)]).name_get()[0][1] + title = self.env[model].browse(res_id).display_name + msg_fmt = _('%(user_name)s invited you to follow %(document)s document: %(title)s') + else: + msg_fmt = _('%(user_name)s invited you to follow a new document') + + text = msg_fmt % locals() + message = html.DIV( + html.P(_('Hello,')), + html.P(text) + ) + result['message'] = etree.tostring(message) return result res_model = fields.Char('Related Document Model', required=True, index=True, help='Model of the followed resource') From 42c42832b3bf49c3fb2438ca14721e8df52ee14d Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 27 Nov 2018 10:51:14 +0000 Subject: [PATCH 3/3] [FIX] base: translations lost after synchronization Modifying a source term in an XML/HTML translated field can lose translations if the same term is translated in several languages. closes odoo/odoo#29078 --- odoo/addons/base/ir/ir_translation.py | 49 ++++++++------ odoo/addons/base/tests/test_translate.py | 82 +++++++++++++++--------- 2 files changed, 78 insertions(+), 53 deletions(-) diff --git a/odoo/addons/base/ir/ir_translation.py b/odoo/addons/base/ir/ir_translation.py index 651987ebdf6..ec193543d3d 100644 --- a/odoo/addons/base/ir/ir_translation.py +++ b/odoo/addons/base/ir/ir_translation.py @@ -464,15 +464,15 @@ class IrTranslation(models.Model): if not callable(field.translate): return - trans = self.env['ir.translation'] - outdated = trans - discarded = trans + Translation = self.env['ir.translation'] + outdated = Translation + discarded = Translation for record in records: # get field value and terms to translate value = record[field.name] terms = set(field.get_trans_terms(value)) - record_trans = trans.search([ + translations = Translation.search([ ('type', '=', 'model'), ('name', '=', "%s,%s" % (field.model_name, field.name)), ('res_id', '=', record.id), @@ -480,25 +480,32 @@ class IrTranslation(models.Model): if not terms: # discard all translations for that field - discarded += record_trans + discarded += translations continue - # remap existing translations on terms when possible - trans_src = record_trans.mapped('src') - for trans in record_trans: - if trans.src == trans.value: - discarded += trans - elif trans.src not in terms: - matches = get_close_matches(trans.src, terms, 1, 0.9) - if matches: - if matches[0] in trans_src: - # there is already a translation for this term; discard this one - discarded += trans - else: - trans.write({'src': matches[0], 'state': trans.state}) - trans_src.append(matches[0]) # avoid reuse of term - else: - outdated += trans + # remap existing translations on terms when possible; each term + # should be translated at most once per language + done = set() # {(src, lang), ...} + translations_to_match = [] + + for translation in translations: + if translation.src == translation.value: + discarded += translation + elif translation.src in terms: + done.add((translation.src, translation.lang)) + else: + translations_to_match.append(translation) + + for translation in translations_to_match: + matches = get_close_matches(translation.src, terms, 1, 0.9) + src = matches[0] if matches else None + if not src: + outdated += translation + elif (src, translation.lang) in done: + discarded += translation + else: + translation.write({'src': src, 'state': translation.state}) + done.add((src, translation.lang)) # process outdated and discarded translations outdated.write({'state': 'to_translate'}) diff --git a/odoo/addons/base/tests/test_translate.py b/odoo/addons/base/tests/test_translate.py index 6c34629d6d8..711ba58f75a 100644 --- a/odoo/addons/base/tests/test_translate.py +++ b/odoo/addons/base/tests/test_translate.py @@ -227,30 +227,36 @@ class TestTranslation(TransactionCase): class TestXMLTranslation(TransactionCase): def setUp(self): super(TestXMLTranslation, self).setUp() - self.env['ir.translation'].load_module_terms(['base'], ['fr_FR']) + self.env['ir.translation'].load_module_terms(['base'], ['fr_FR', 'nl_NL']) + + def create_view(self, archf, terms, **kwargs): + view = self.env['ir.ui.view'].create({ + 'name': 'test', + 'model': 'res.partner', + 'arch': archf % terms, + }) + for lang, trans_terms in kwargs.items(): + for src, val in zip(terms, trans_terms): + self.env['ir.translation'].create({ + 'type': 'model', + 'name': 'ir.ui.view,arch_db', + 'lang': lang, + 'res_id': view.id, + 'src': src, + 'value': val, + 'state': 'translated', + }) + return view def test_copy(self): """ Create a simple view, fill in translations, and copy it. """ - env_en = self.env(context={}) - env_fr = self.env(context={'lang': 'fr_FR'}) - archf = '
%s
%s
' terms_en = ('Knife', 'Fork', 'Spoon') terms_fr = ('Couteau', 'Fourchette', 'Cuiller') - view0 = self.env['ir.ui.view'].create({ - 'name': 'test', - 'model': 'res.partner', - 'arch': archf % terms_en, - }) - for src, value in zip(terms_en, terms_fr): - self.env['ir.translation'].create({ - 'type': 'model', - 'name': 'ir.ui.view,arch_db', - 'lang': 'fr_FR', - 'res_id': view0.id, - 'src': src, - 'value': value, - }) + view0 = self.create_view(archf, terms_en, fr_FR=terms_fr) + + env_en = self.env(context={}) + env_fr = self.env(context={'lang': 'fr_FR'}) # check translated field self.assertEqual(view0.with_env(env_en).arch_db, archf % terms_en) @@ -277,17 +283,29 @@ class TestXMLTranslation(TransactionCase): archf = '
%s
%s
' terms_en = ('Knife', 'Fork', 'Spoon') terms_fr = (' Couteau', 'Fourchette ', ' Cuiller ') - view0 = self.env['ir.ui.view'].create({ - 'name': 'test', - 'model': 'res.partner', - 'arch': archf % terms_en, - }) - for src, value in zip(terms_en, terms_fr): - self.env['ir.translation'].create({ - 'type': 'model', - 'name': 'ir.ui.view,arch_db', - 'lang': 'fr_FR', - 'res_id': view0.id, - 'src': src, - 'value': value, - }) + self.create_view(archf, terms_en, fr_FR=terms_fr) + + def test_sync(self): + """ Check translations after minor change in source terms. """ + archf = '
%s
' + terms_en = ('Bread and cheeze',) + terms_fr = ('Pain et fromage',) + terms_nl = ('Brood and kaas',) + view = self.create_view(archf, terms_en, fr_FR=terms_fr, nl_NL=terms_nl) + + env_en = self.env(context={}) + env_fr = self.env(context={'lang': 'fr_FR'}) + env_nl = self.env(context={'lang': 'nl_NL'}) + + self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr) + self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl) + + # modify source term in view (fixed type in 'cheeze') + terms_en = ('Bread and cheese',) + view.write({'arch_db': archf % terms_en}) + + # check whether translations have been synchronized + self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr) + self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)