[MERGE] forward port branch 10.0 up to 42c42832b3
This commit is contained in:
@@ -63,7 +63,18 @@ class MailMail(models.Model):
|
|||||||
values['notification'] = True
|
values['notification'] = True
|
||||||
if not values.get('mail_message_id'):
|
if not values.get('mail_message_id'):
|
||||||
self = self.with_context(message_create_from_mail_mail=True)
|
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
|
@api.multi
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
|
|||||||
@@ -740,6 +740,10 @@ class Message(models.Model):
|
|||||||
# delegate creation of tracking after the create as sudo to avoid access rights issues
|
# delegate creation of tracking after the create as sudo to avoid access rights issues
|
||||||
tracking_values_cmd = values.pop('tracking_value_ids', False)
|
tracking_values_cmd = values.pop('tracking_value_ids', False)
|
||||||
message = super(Message, self).create(values)
|
message = super(Message, self).create(values)
|
||||||
|
|
||||||
|
if values.get('attachment_ids'):
|
||||||
|
message.attachment_ids.check(mode='read')
|
||||||
|
|
||||||
if tracking_values_cmd:
|
if tracking_values_cmd:
|
||||||
message.sudo().write({'tracking_value_ids': tracking_values_cmd})
|
message.sudo().write({'tracking_value_ids': tracking_values_cmd})
|
||||||
|
|
||||||
@@ -762,6 +766,9 @@ class Message(models.Model):
|
|||||||
if 'model' in vals or 'res_id' in vals:
|
if 'model' in vals or 'res_id' in vals:
|
||||||
self._invalidate_documents()
|
self._invalidate_documents()
|
||||||
res = super(Message, self).write(vals)
|
res = super(Message, self).write(vals)
|
||||||
|
if vals.get('attachment_ids'):
|
||||||
|
for mail in self:
|
||||||
|
mail.attachment_ids.check(mode='read')
|
||||||
self._invalidate_documents()
|
self._invalidate_documents()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# 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
|
from odoo import _, api, fields, models
|
||||||
|
|
||||||
|
|
||||||
@@ -12,18 +15,27 @@ class Invite(models.TransientModel):
|
|||||||
@api.model
|
@api.model
|
||||||
def default_get(self, fields):
|
def default_get(self, fields):
|
||||||
result = super(Invite, self).default_get(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]
|
user_name = self.env.user.name_get()[0][1]
|
||||||
model = result.get('res_model')
|
model = result.get('res_model')
|
||||||
res_id = result.get('res_id')
|
res_id = result.get('res_id')
|
||||||
if self._context.get('mail_invite_follower_channel_only'):
|
if model and res_id:
|
||||||
result['send_mail'] = False
|
document = self.env['ir.model']._get(model).display_name
|
||||||
if 'message' in fields and model and res_id:
|
title = self.env[model].browse(res_id).display_name
|
||||||
model_name = self.env['ir.model']._get(model).display_name
|
msg_fmt = _('%(user_name)s invited you to follow %(document)s document: %(title)s')
|
||||||
document_name = self.env[model].browse(res_id).name_get()[0][1]
|
else:
|
||||||
message = _('<div><p>Hello,</p><p>%s invited you to follow %s document: %s.</p></div>') % (user_name, model_name, document_name)
|
msg_fmt = _('%(user_name)s invited you to follow a new document')
|
||||||
result['message'] = message
|
|
||||||
elif 'message' in fields:
|
text = msg_fmt % locals()
|
||||||
result['message'] = _('<div><p>Hello,</p><p>%s invited you to follow a new document.</p></div>') % user_name
|
message = html.DIV(
|
||||||
|
html.P(_('Hello,')),
|
||||||
|
html.P(text)
|
||||||
|
)
|
||||||
|
result['message'] = etree.tostring(message)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
res_model = fields.Char('Related Document Model', required=True, index=True, help='Model of the followed resource')
|
res_model = fields.Char('Related Document Model', required=True, index=True, help='Model of the followed resource')
|
||||||
|
|||||||
@@ -464,15 +464,15 @@ class IrTranslation(models.Model):
|
|||||||
if not callable(field.translate):
|
if not callable(field.translate):
|
||||||
return
|
return
|
||||||
|
|
||||||
trans = self.env['ir.translation']
|
Translation = self.env['ir.translation']
|
||||||
outdated = trans
|
outdated = Translation
|
||||||
discarded = trans
|
discarded = Translation
|
||||||
|
|
||||||
for record in records:
|
for record in records:
|
||||||
# get field value and terms to translate
|
# get field value and terms to translate
|
||||||
value = record[field.name]
|
value = record[field.name]
|
||||||
terms = set(field.get_trans_terms(value))
|
terms = set(field.get_trans_terms(value))
|
||||||
record_trans = trans.search([
|
translations = Translation.search([
|
||||||
('type', '=', 'model'),
|
('type', '=', 'model'),
|
||||||
('name', '=', "%s,%s" % (field.model_name, field.name)),
|
('name', '=', "%s,%s" % (field.model_name, field.name)),
|
||||||
('res_id', '=', record.id),
|
('res_id', '=', record.id),
|
||||||
@@ -480,25 +480,32 @@ class IrTranslation(models.Model):
|
|||||||
|
|
||||||
if not terms:
|
if not terms:
|
||||||
# discard all translations for that field
|
# discard all translations for that field
|
||||||
discarded += record_trans
|
discarded += translations
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# remap existing translations on terms when possible
|
# remap existing translations on terms when possible; each term
|
||||||
trans_src = record_trans.mapped('src')
|
# should be translated at most once per language
|
||||||
for trans in record_trans:
|
done = set() # {(src, lang), ...}
|
||||||
if trans.src == trans.value:
|
translations_to_match = []
|
||||||
discarded += trans
|
|
||||||
elif trans.src not in terms:
|
for translation in translations:
|
||||||
matches = get_close_matches(trans.src, terms, 1, 0.9)
|
if translation.src == translation.value:
|
||||||
if matches:
|
discarded += translation
|
||||||
if matches[0] in trans_src:
|
elif translation.src in terms:
|
||||||
# there is already a translation for this term; discard this one
|
done.add((translation.src, translation.lang))
|
||||||
discarded += trans
|
else:
|
||||||
else:
|
translations_to_match.append(translation)
|
||||||
trans.write({'src': matches[0], 'state': trans.state})
|
|
||||||
trans_src.append(matches[0]) # avoid reuse of term
|
for translation in translations_to_match:
|
||||||
else:
|
matches = get_close_matches(translation.src, terms, 1, 0.9)
|
||||||
outdated += trans
|
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
|
# process outdated and discarded translations
|
||||||
outdated.write({'state': 'to_translate'})
|
outdated.write({'state': 'to_translate'})
|
||||||
|
|||||||
@@ -275,30 +275,36 @@ class TestTranslation(TransactionCase):
|
|||||||
class TestXMLTranslation(TransactionCase):
|
class TestXMLTranslation(TransactionCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestXMLTranslation, self).setUp()
|
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):
|
def test_copy(self):
|
||||||
""" Create a simple view, fill in translations, and copy it. """
|
""" Create a simple view, fill in translations, and copy it. """
|
||||||
env_en = self.env(context={})
|
|
||||||
env_fr = self.env(context={'lang': 'fr_FR'})
|
|
||||||
|
|
||||||
archf = '<form string="%s"><div>%s</div><div>%s</div></form>'
|
archf = '<form string="%s"><div>%s</div><div>%s</div></form>'
|
||||||
terms_en = ('Knife', 'Fork', 'Spoon')
|
terms_en = ('Knife', 'Fork', 'Spoon')
|
||||||
terms_fr = ('Couteau', 'Fourchette', 'Cuiller')
|
terms_fr = ('Couteau', 'Fourchette', 'Cuiller')
|
||||||
view0 = self.env['ir.ui.view'].create({
|
view0 = self.create_view(archf, terms_en, fr_FR=terms_fr)
|
||||||
'name': 'test',
|
|
||||||
'model': 'res.partner',
|
env_en = self.env(context={})
|
||||||
'arch': archf % terms_en,
|
env_fr = self.env(context={'lang': 'fr_FR'})
|
||||||
})
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
|
|
||||||
# check translated field
|
# check translated field
|
||||||
self.assertEqual(view0.with_env(env_en).arch_db, archf % terms_en)
|
self.assertEqual(view0.with_env(env_en).arch_db, archf % terms_en)
|
||||||
@@ -325,17 +331,29 @@ class TestXMLTranslation(TransactionCase):
|
|||||||
archf = '<form string="%s"><div>%s</div><div>%s</div></form>'
|
archf = '<form string="%s"><div>%s</div><div>%s</div></form>'
|
||||||
terms_en = ('Knife', 'Fork', 'Spoon')
|
terms_en = ('Knife', 'Fork', 'Spoon')
|
||||||
terms_fr = (' Couteau', 'Fourchette ', ' Cuiller ')
|
terms_fr = (' Couteau', 'Fourchette ', ' Cuiller ')
|
||||||
view0 = self.env['ir.ui.view'].create({
|
self.create_view(archf, terms_en, fr_FR=terms_fr)
|
||||||
'name': 'test',
|
|
||||||
'model': 'res.partner',
|
def test_sync(self):
|
||||||
'arch': archf % terms_en,
|
""" Check translations after minor change in source terms. """
|
||||||
})
|
archf = '<form string="X">%s</form>'
|
||||||
for src, value in zip(terms_en, terms_fr):
|
terms_en = ('Bread and cheeze',)
|
||||||
self.env['ir.translation'].create({
|
terms_fr = ('Pain et fromage',)
|
||||||
'type': 'model',
|
terms_nl = ('Brood and kaas',)
|
||||||
'name': 'ir.ui.view,arch_db',
|
view = self.create_view(archf, terms_en, fr_FR=terms_fr, nl_NL=terms_nl)
|
||||||
'lang': 'fr_FR',
|
|
||||||
'res_id': view0.id,
|
env_en = self.env(context={})
|
||||||
'src': src,
|
env_fr = self.env(context={'lang': 'fr_FR'})
|
||||||
'value': value,
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user