[IMP] mass_mailing[_sms]: send user feedback when testing mass mailings/sms

This commit adds user feedback in the form of a message logged on the related
document (mailing.mailing) when testing a mailing/sms.

Before this change, when sending an email to your own mailbox for testing
purpose, or when sending an SMS to your phone, you did not get any interface
feedback on whether it worked or not.
Now, a logged message will show if it's successful and if not, explain why it
failed with a short error message (no IAP credits / misconfigured outgoing mail
server / ...).

In addition, email and phone inputs are now split on the '\n' character instead
of a coma, which allows easier validation of the email addresses.

Task-2375526

closes odoo/odoo#63421

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Aurélien Warnon
2021-01-28 09:23:35 +00:00
parent 6dc5c785e6
commit fb1b25802b
5 changed files with 100 additions and 22 deletions
@@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, tools
from odoo import _, fields, models, tools
class TestMassMailing(models.TransientModel):
_name = 'mailing.mailing.test'
_description = 'Sample Mail Wizard'
email_to = fields.Char(string='Recipients', required=True,
help='Comma-separated list of email addresses.', default=lambda self: self.env.user.email_formatted)
email_to = fields.Text(string='Recipients', required=True,
help='Carriage-return-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):
@@ -19,8 +19,16 @@ class TestMassMailing(models.TransientModel):
self = self.with_context(ctx)
mails_sudo = self.env['mail.mail'].sudo()
valid_emails = []
invalid_candidates = []
for candidate in self.email_to.splitlines():
test_email = tools.email_split(candidate)
if test_email:
valid_emails.append(test_email[0])
else:
invalid_candidates.append(candidate)
mailing = self.mass_mailing_id
test_emails = tools.email_split(self.email_to)
mass_mail_layout = self.env.ref('mass_mailing.mass_mailing_mail_layout')
record = self.env[mailing.mailing_model_real].search([], limit=1)
@@ -38,20 +46,45 @@ class TestMassMailing(models.TransientModel):
body = self.env['mail.render.mixin']._replace_local_links(body)
body = tools.html_sanitize(body, sanitize_attributes=True, sanitize_style=True)
for test_mail in test_emails:
for valid_email in valid_emails:
mail_values = {
'email_from': mailing.email_from,
'reply_to': mailing.reply_to,
'email_to': test_mail,
'email_to': valid_email,
'subject': subject,
'body_html': mass_mail_layout._render({'body': body}, engine='ir.qweb', minimal_qcontext=True),
'notification': True,
'mailing_id': mailing.id,
'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
'auto_delete': True,
'auto_delete': False, # they are manually deleted after notifying the document
'mail_server_id': mailing.mail_server_id.id,
}
mail = self.env['mail.mail'].sudo().create(mail_values)
mails_sudo |= mail
mails_sudo.send()
notification_messages = []
if invalid_candidates:
notification_messages.append(
_('Mailing addresses incorrect: %s', ', '.join(invalid_candidates)))
for mail_sudo in mails_sudo:
if mail_sudo.state == 'sent':
notification_messages.append(
_('Test mailing successfully sent to %s', mail_sudo.email_to))
elif mail_sudo.state == 'exception':
notification_messages.append(
_('Test mailing could not be sent to %s:<br>%s',
mail_sudo.email_to,
mail_sudo.failure_reason)
)
# manually delete the emails since we passed 'auto_delete: False'
mails_sudo.unlink()
if notification_messages:
self.mass_mailing_id._message_log(body='<ul>%s</ul>' % ''.join(
['<li>%s</li>' % notification_message for notification_message in notification_messages]
))
return True
@@ -7,13 +7,13 @@
<field name="arch" type="xml">
<form string="Send a Sample Mail">
<p class="text-muted">
Send a sample email for testing purpose to the address below.
Send a sample mailing for testing purpose to the address below.
</p>
<group>
<field name="email_to"/>
<field name="email_to" placeholder="email1@example.com&#10;email2@example.com"/>
</group>
<footer>
<button string="Send Sample Mail" name="send_mail_test" type="object" class="btn-primary"/>
<button string="Send" name="send_mail_test" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel" />
</footer>
</form>
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, exceptions, fields, models, _
from odoo import fields, models, _
from odoo.addons.phone_validation.tools import phone_validation
@@ -12,28 +12,55 @@ class MassSMSTest(models.TransientModel):
def _default_numbers(self):
return self.env.user.partner_id.phone_sanitized or ""
numbers = fields.Char(string='Number(s)', required=True,
default=_default_numbers, help='Comma-separated list of phone numbers')
numbers = fields.Text(string='Number(s)', required=True,
default=_default_numbers, help='Carriage-return-separated list of phone numbers')
mailing_id = fields.Many2one('mailing.mailing', string='Mailing', required=True, ondelete='cascade')
def action_send_sms(self):
self.ensure_one()
numbers = [number.strip() for number in self.numbers.split(',')]
numbers = [number.strip() for number in self.numbers.splitlines()]
sanitize_res = phone_validation.phone_sanitize_numbers_w_record(numbers, self.env.user)
sanitized_numbers = [info['sanitized'] for info in sanitize_res.values() if info['sanitized']]
invalid_numbers = [number for number, info in sanitize_res.items() if info['code']]
if invalid_numbers:
raise exceptions.UserError(_('Following numbers are not correctly encoded: %s, example : "+32 495 85 85 77, +33 545 55 55 55"', repr(invalid_numbers)))
record = self.env[self.mailing_id.mailing_model_real].search([], limit=1)
body = self.mailing_id.body_plaintext
if record:
# Returns a proper error if there is a syntax error with jinja
body = self.env['mail.render.mixin']._render_template(body, self.mailing_id.mailing_model_real, record.ids)[record.id]
self.env['sms.api']._send_sms_batch([{
'res_id': 0,
# res_id is used to map the result to the number to log notifications as IAP does not return numbers...
# TODO: clean IAP to make it return a clean dict with numbers / use custom keys / rename res_id to external_id
sent_sms_list = self.env['sms.api']._send_sms_batch([{
'res_id': number,
'number': number,
'content': body,
} for number in sanitized_numbers])
error_messages = {}
if any(sent_sms.get('state') != 'success' for sent_sms in sent_sms_list):
error_messages = self.env['sms.api']._get_sms_api_error_messages()
notification_messages = []
if invalid_numbers:
notification_messages.append(_('The following numbers are not correctly encoded: %s',
', '.join(invalid_numbers)))
for sent_sms in sent_sms_list:
if sent_sms.get('state') == 'success':
notification_messages.append(
_('Test SMS successfully sent to %s', sent_sms.get('res_id')))
elif sent_sms.get('state'):
notification_messages.append(
_('Test SMS could not be sent to %s:<br>%s',
sent_sms.get('res_id'),
error_messages.get(sent_sms['state'], _("An error occurred.")))
)
if notification_messages:
self.mailing_id._message_log(body='<ul>%s</ul>' % ''.join(
['<li>%s</li>' % notification_message for notification_message in notification_messages]
))
return True
@@ -6,10 +6,10 @@
<field name="arch" type="xml">
<form string="Send a Sample SMS">
<p class="text-muted">
Send a sample SMS for testing purpose to the numbers below (comma-separated list).
Send a sample SMS for testing purpose to the numbers below (carriage-return-separated list).
</p>
<group>
<field name="numbers" placeholder="+32 495 85 85 77, +33 545 55 55 55"/>
<field name="numbers" placeholder="+32 495 85 85 77&#10;+33 545 55 55 55"/>
<field name="mailing_id" invisible="1"/>
</group>
<footer>
+19 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
from odoo import _, api, models
from odoo.addons.iap.tools import iap_tools
DEFAULT_ENDPOINT = 'https://iap-sms.odoo.com'
@@ -56,3 +56,21 @@ class SmsApi(models.AbstractModel):
'messages': messages
}
return self._contact_iap('/iap/sms/2/send', params)
@api.model
def _get_sms_api_error_messages(self):
""" Returns a dict containing the error message to display for every known error 'state'
resulting from the '_send_sms_batch' method.
We prefer a dict instead of a message-per-error-state based method so we only call
the 'get_credits_url' once, to avoid extra RPC calls. """
buy_credits_url = self.sudo().env['iap.account'].get_credits_url(service_name='sms')
buy_credits = '<a href="%s" target="_blank">%s</a>' % (
buy_credits_url,
_('Buy credits.')
)
return {
'unregistered': _("You don't have an eligible IAP account."),
'insufficient_credit': ' '.join([_('You don\'t have enough credits on your IAP account.'), buy_credits]),
'wrong_number_format': _("The number you're trying to reach is not correctly formatted."),
}