[REF] mail, various: improve mail creation calls, notably author and email from default computation

Purpose of this commit is to correctly compute author_id and email_from
in mail_message and mail_mail as they depends from each other. Moreover it
is a good idea in various flows to specify email and author when giving
creation values to avoid default computation that is not always guaranteed to
be accurate notably when involving super user.

Mail message creation could lead to desynchronized values between author
and email_from. This is improved with this commit by correctly inheriting
from default_get and computing both of them at the same time instead of having
two default values. Indeed they depend on each other.

Same thing is done for mail composer. Mail Thread offers a tool method to
find email_from / author_id based on having one of those values or current
user and it is called whenever necessary.

Some calls to mail template send_mail are also cleaned.

Task ID 1853147
PR #32243
This commit is contained in:
Thibault Delavallée
2019-11-29 13:35:14 +00:00
parent b9a22d2f24
commit 1838191eec
14 changed files with 63 additions and 47 deletions
+1 -4
View File
@@ -40,10 +40,7 @@ class AuthSignupHome(Home):
user_sudo = request.env['res.users'].sudo().search([('login', '=', qcontext.get('login'))])
template = request.env.ref('auth_signup.mail_template_user_signup_account_created', raise_if_not_found=False)
if user_sudo and template:
template.sudo().with_context(
lang=user_sudo.lang,
auth_login=werkzeug.url_encode({'auth_login': user_sudo.email}),
).send_mail(user_sudo.id, force_send=True)
template.sudo().send_mail(user_sudo.id, force_send=True)
return self.web_login(*args, **kw)
except UserError as e:
qcontext['error'] = e.name or e.value
+1 -1
View File
@@ -289,7 +289,7 @@
Your login is <strong>${object.email}</strong><br/>
To gain access to your account, you can use the following link:
<div style="margin: 16px 0px 16px 0px;">
<a href="/web/login?${ctx['auth_login']}"
<a href="/web/login?auth_login=${object.email}"
style="background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
Go to My Account
</a>
+2 -1
View File
@@ -195,8 +195,9 @@ class ResUsers(models.Model):
for user in self:
if not user.email:
raise UserError(_("Cannot send email: user %s has no email address.") % user.name)
# TDE FIXME: make this template technical (qweb)
with self.env.cr.savepoint():
template.with_context(lang=user.lang).send_mail(user.id, force_send=True, raise_exception=True)
template.send_mail(user.id, force_send=True, raise_exception=True)
_logger.info("Password reset email sent for user <%s> to <%s>", user.login, user.email)
def send_unregistered_user_reminder(self, after_days=5):
+2
View File
@@ -617,6 +617,8 @@ class HrExpense(models.Model):
)
else:
self.env['mail.mail'].create({
'email_from': self.env.user.email_formatted,
'author_id': self.env.user.partner_id.id,
'body_html': body,
'subject': 'Re: %s' % msg_dict.get('subject', ''),
'email_to': msg_dict.get('email_from', False),
+2 -1
View File
@@ -173,7 +173,8 @@ class MailChannel(models.Model):
mail_body = self.env['mail.thread']._replace_local_links(mail_body)
mail = self.env['mail.mail'].create({
'subject': _('Conversation with %s') % self.livechat_operator_id.name,
'email_from': self.env.company.email,
'email_from': company.catchall_formatted or company.email_formatted,
'author_id': self.env.user.partner_id.id,
'email_to': email,
'body_html': mail_body,
})
+4 -1
View File
@@ -368,6 +368,8 @@ class Channel(models.Model):
# The fields "email_from" and "reply_to" are filled in automatically by method create in model mail.message.
if self.moderation_notify and self.moderation_notify_msg and message_type == 'email' and moderation_status == 'pending_moderation':
self.env['mail.mail'].create({
'author_id': self.env.user.partner_id.id,
'email_from': self.env.user.company_id.catchall_formatted or self.env.user.company_id.email_formatted,
'body_html': self.moderation_notify_msg,
'subject': 'Re: %s' % (kwargs.get('subject', '')),
'email_to': email,
@@ -418,9 +420,10 @@ class Channel(models.Model):
]).mapped('email')
for partner in partners.filtered(lambda p: p.email and not (p.email in banned_emails)):
create_values = {
'email_from': partner.company_id.catchall_formatted or partner.company_id.email_formatted,
'author_id': self.env.user.partner_id.id,
'body_html': view.render({'channel': self, 'partner': partner}, engine='ir.qweb', minimal_qcontext=True),
'subject': _("Guidelines of channel %s") % self.name,
'email_from': partner.company_id.catchall_formatted or partner.company_id.email,
'recipient_ids': [(4, partner.id)]
}
mail = self.env['mail.mail'].create(create_values)
+19 -22
View File
@@ -9,7 +9,7 @@ from collections import defaultdict
from operator import itemgetter
from odoo import _, api, fields, models, modules, tools
from odoo.exceptions import UserError, AccessError
from odoo.exceptions import AccessError
from odoo.http import request
from odoo.osv import expression
from odoo.tools import groupby
@@ -27,14 +27,17 @@ class Message(models.Model):
_rec_name = 'record_name'
@api.model
def _get_default_from(self):
if self.env.user.email:
return tools.formataddr((self.env.user.name, self.env.user.email))
raise UserError(_("Unable to post message, please configure the sender's email address."))
@api.model
def _get_default_author(self):
return self.env.user.partner_id
def default_get(self, fields):
res = super(Message, self).default_get(fields)
missing_author = 'author_id' in fields and 'author_id' not in res
missing_email_from = 'email_from' in fields and 'email_from' not in res
if missing_author or missing_email_from:
author_id, email_from = self.env['mail.thread']._message_compute_author(res.get('author_id'), res.get('email_from'), raise_exception=False)
if missing_email_from:
res['email_from'] = email_from
if missing_author:
res['author_id'] = author_id
return res
# content
subject = fields.Char('Subject')
@@ -69,12 +72,9 @@ class Message(models.Model):
'mail.activity.type', 'Mail Activity Type',
index=True, ondelete='set null')
# origin
email_from = fields.Char(
'From', default=_get_default_from,
help="Email address of the sender. This field is set when no matching partner is found and replaces the author_id field in the chatter.")
email_from = fields.Char('From', help="Email address of the sender. This field is set when no matching partner is found and replaces the author_id field in the chatter.")
author_id = fields.Many2one(
'res.partner', 'Author', index=True,
ondelete='set null', default=_get_default_author,
'res.partner', 'Author', index=True, ondelete='set null',
help="Author of the message. If not set, email_from may hold an email address that did not match any partner.")
author_avatar = fields.Binary("Author's avatar", related='author_id.image_128', readonly=False)
# recipients: include inactive partners (they may have been archived after
@@ -574,7 +574,8 @@ class Message(models.Model):
tracking_values_list = []
for values in values_list:
if 'email_from' not in values: # needed to compute reply_to
values['email_from'] = self._get_default_from()
author_id, email_from = self.env['mail.thread']._message_compute_author(values.get('author_id'), email_from=None, raise_exception=False)
values['email_from'] = email_from
if not values.get('message_id'):
values['message_id'] = self._get_message_id(values)
if 'reply_to' not in values:
@@ -833,16 +834,12 @@ class Message(models.Model):
for msg in self:
if not msg.email_from:
continue
if self.env.user.partner_id.email:
email_from = tools.formataddr((self.env.user.partner_id.name, self.env.user.partner_id.email))
else:
email_from = self.env.company.catchall_formatted
body_html = tools.append_content_to_html('<div>%s</div>' % tools.ustr(comment), msg.body, plaintext=False)
vals = {
'subject': subject,
'body_html': body_html,
'email_from': email_from,
'author_id': self.env.user.partner_id.id,
'email_from': self.env.user.email_formatted or self.env.company_id.catchall_formatted,
'email_to': msg.email_from,
'auto_delete': True,
'state': 'outgoing'
@@ -924,7 +921,7 @@ class Message(models.Model):
partner_ids=moderator.partner_id.ids,
subject=_('Message are pending moderation'), # tocheck: target language
body=template.render({'record': moderator.partner_id}, engine='ir.qweb', minimal_qcontext=True),
email_from=moderator.company_id.catchall_formatted or moderator.company_id.email,
email_from=moderator.company_id.catchall_formatted or moderator.company_id.email_formatted,
)
# ------------------------------------------------------
+3
View File
@@ -685,6 +685,7 @@ class MailThread(models.AbstractModel):
def _routing_create_bounce_email(self, email_from, body_html, message, **mail_values):
bounce_to = tools.decode_message_header(message, 'Return-Path') or email_from
bounce_mail_values = {
'author_id': False,
'body_html': body_html,
'subject': 'Re: %s' % message.get('subject'),
'email_to': bounce_to,
@@ -693,6 +694,8 @@ class MailThread(models.AbstractModel):
bounce_from = self.env['ir.mail_server']._get_default_bounce_address()
if bounce_from:
bounce_mail_values['email_from'] = tools.formataddr(('MAILER-DAEMON', bounce_from))
else:
bounce_mail_values['email_from'] = tools.decode_message_header(message, 'To')
bounce_mail_values.update(mail_values)
self.env['mail.mail'].create(bounce_mail_values).send()
+4 -1
View File
@@ -5,6 +5,7 @@ from lxml import etree
from lxml.html import builder as html
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class Invite(models.TransientModel):
@@ -47,7 +48,9 @@ class Invite(models.TransientModel):
send_mail = fields.Boolean('Send Email', default=True, help="If checked, the partners will receive an email warning they have been added in the document's followers.")
def add_followers(self):
email_from = self.env['mail.message']._get_default_from()
if not self.env.user.email:
raise UserError(_("Unable to post message, please configure the sender's email address."))
email_from = self.env.user.email_formatted
for wizard in self:
Model = self.env[wizard.res_model]
document = Model.browse(wizard.res_id)
+8 -8
View File
@@ -54,14 +54,14 @@ class MailComposer(models.TransientModel):
result = super(MailComposer, self).default_get(fields)
# author
if 'author_id' not in result:
result['author_id'] = self.env.user.partner_id.id
if 'email_from' not in result and self.env.user.email:
result['email_from'] = self.env.user.email_formatted
elif 'email_from' not in result:
author = self.env['res.partner'].browse(result['author_id'])
if author.email:
result['email_from'] = tools.formataddr((author.name, author.email))
missing_author = 'author_id' in fields and 'author_id' not in result
missing_email_from = 'email_from' in fields and 'email_from' not in result
if missing_author or missing_email_from:
author_id, email_from = self.env['mail.thread']._message_compute_author(result.get('author_id'), result.get('email_from'), raise_exception=False)
if missing_email_from:
result['email_from'] = email_from
if missing_author:
result['author_id'] = author_id
result['composition_mode'] = result.get('composition_mode', 'comment')
result['model'] = result.get('model', self._context.get('active_model'))
+2 -2
View File
@@ -69,7 +69,7 @@ class MassMailing(models.Model):
active = fields.Boolean(default=True, tracking=True)
subject = fields.Char('Subject', help='Subject of emails to send', required=True, translate=True)
email_from = fields.Char(string='Send From', required=True,
default=lambda self: self.env['mail.message']._get_default_from())
default=lambda self: self.env.user.email_formatted)
sent_date = fields.Datetime(string='Sent Date', copy=False)
schedule_date = fields.Datetime(string='Scheduled for', tracking=True)
# don't translate 'body_arch', the translations are only on 'body_html'
@@ -92,7 +92,7 @@ class MassMailing(models.Model):
reply_to_mode = fields.Selection(
[('thread', 'Recipient Followers'), ('email', 'Specified Email Address')], string='Reply-To Mode', required=True)
reply_to = fields.Char(string='Reply To', help='Preferred Reply-To Address',
default=lambda self: self.env['mail.message']._get_default_from())
default=lambda self: self.env.user.email_formatted)
# recipients
mailing_model_real = fields.Char(compute='_compute_model', string='Recipients Real Model', default='mailing.contact', required=True)
mailing_model_id = fields.Many2one('ir.model', string='Recipients Model', domain=[('model', 'in', MASS_MAILING_BUSINESS_MODELS)],
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools
from odoo import fields, models, tools
class TestMassMailing(models.TransientModel):
@@ -9,7 +9,7 @@ class TestMassMailing(models.TransientModel):
_description = 'Sample Mail Wizard'
email_to = fields.Char(string='Recipients', required=True,
help='Comma-separated list of email addresses.', default=lambda self: self.env['mail.message']._get_default_from())
help='Comma-separated list of email addresses.', default=lambda self: self.env.user.email_formatted)
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mailing', required=True, ondelete='cascade')
def send_mail_test(self):
+2 -1
View File
@@ -199,7 +199,8 @@ class TxPaypal(models.Model):
'body_html': mail_body,
'subject': _('Add your Paypal account to Odoo'),
'email_to': self.acquirer_id.paypal_email_account,
'email_from': self.acquirer_id.create_uid.email
'email_from': self.acquirer_id.create_uid.email_formatted,
'author_id': self.acquirer_id.create_uid.partner_id.id,
}
self.env['mail.mail'].sudo().create(mail_values).send()
+11 -3
View File
@@ -502,10 +502,18 @@ class Slide(models.Model):
mail_ids = []
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
for record in self:
template = self.channel_id.share_template_id.with_context(
user=self.env.user,
email=email,
base_url=base_url,
fullscreen=fullscreen
)
email_values = {'email_to': email}
if self.env.user.has_group('base.group_portal'):
mail_ids.append(self.channel_id.share_template_id.with_context(user=self.env.user, email=email, base_url=base_url, fullscreen=fullscreen).sudo().send_mail(record.id, notif_layout='mail.mail_notification_light', email_values={'email_from': self.env.company.catchall_formatted or self.env.company.email, 'email_to': email}))
else:
mail_ids.append(self.channel_id.share_template_id.with_context(user=self.env.user, email=email, base_url=base_url, fullscreen=fullscreen).send_mail(record.id, notif_layout='mail.mail_notification_light', email_values={'email_to': email}))
template = template.sudo()
email_values['email_from'] = self.env.company.catchall_formatted or self.env.company.email_formatted
mail_ids.append(template.send_mail(record.id, notif_layout='mail.mail_notification_light', email_values=email_values))
return mail_ids
def action_like(self):