[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:
@@ -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 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 +33 545 55 55 55"/>
|
||||
<field name="mailing_id" invisible="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
|
||||
@@ -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."),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user