Merged in [7635] from rjsparks@nostrum.com:

Replumbed how SMTP Exceptions are handled, which involved refactoring several mail functions
 - Legacy-Id: 7637
Note: SVN reference [7635] has been migrated to Git commit 73b0831b57
This commit is contained in:
Henrik Levkowetz 2014-04-24 22:14:57 +00:00
commit 4b6ca22ff7
3 changed files with 84 additions and 31 deletions

View file

@ -7,6 +7,7 @@ from email.MIMEMultipart import MIMEMultipart
from email import message_from_string
import smtplib
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
from django.template.loader import render_to_string
from django.template import Context,RequestContext
@ -169,17 +170,19 @@ def send_mail(request, to, frm, subject, template, context, *args, **kwargs):
txt = render_to_string(template, context, context_instance=mail_context(request))
return send_mail_text(request, to, frm, subject, txt, *args, **kwargs)
def send_mail_text(request, to, frm, subject, txt, cc=None, extra=None, toUser=False, bcc=None):
"""Send plain text message."""
def encode_message(txt):
if isinstance(txt, unicode):
msg = MIMEText(txt.encode('utf-8'), 'plain', 'UTF-8')
else:
msg = MIMEText(txt)
return msg
def send_mail_text(request, to, frm, subject, txt, cc=None, extra=None, toUser=False, bcc=None):
"""Send plain text message."""
msg = encode_message(txt)
send_mail_mime(request, to, frm, subject, msg, cc, extra, toUser, bcc)
def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=False, bcc=None):
"""Send MIME message with content already filled in."""
def condition_message(to, frm, subject, msg, cc, extra):
if isinstance(frm, tuple):
frm = formataddr(frm)
if isinstance(to, list) or isinstance(to, tuple):
@ -200,13 +203,21 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=F
for k, v in extra.items():
if v:
msg[k] = v
def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=False, bcc=None):
"""Send MIME message with content already filled in."""
condition_message(to, frm, subject, msg, cc, extra)
# start debug server with python -m smtpd -n -c DebuggingServer localhost:2025
# then put USING_DEBUG_EMAIL_SERVER=True and EMAIL_HOST='localhost'
# and EMAIL_PORT=2025 in settings_local.py
debugging = getattr(settings, "USING_DEBUG_EMAIL_SERVER", False) and settings.EMAIL_HOST == 'localhost' and settings.EMAIL_PORT == 2025
if test_mode or debugging or settings.SERVER_MODE == 'production':
send_smtp(msg, bcc)
with smtp_error_logging(send_smtp) as logging_send:
with smtp_error_user_warning(logging_send,request) as send:
send(msg, bcc)
elif settings.SERVER_MODE == 'test':
if toUser:
copy_email(msg, to, toUser=True, originalBcc=bcc)
@ -219,12 +230,12 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=F
if copy_to and not test_mode and not debugging: # if we're running automated tests, this copy is just annoying
if bcc:
msg['X-Tracker-Bcc']=bcc
copy_email(msg, copy_to,originalBcc=bcc)
with smtp_error_logging(copy_email) as logging_copy:
with smtp_error_user_warning(logging_copy,request) as copy:
copy(msg, copy_to,originalBcc=bcc)
def send_mail_preformatted(request, preformatted, extra={}, override={}):
"""Parse preformatted string containing mail with From:, To:, ...,
and send it through the standard IETF mail interface (inserting
extra headers as needed)."""
def parse_preformatted(preformatted, extra={}, override={}):
"""Parse preformatted string containing mail with From:, To:, ...,"""
msg = message_from_string(preformatted.encode("utf-8"))
for k, v in override.iteritems():
@ -242,7 +253,15 @@ def send_mail_preformatted(request, preformatted, extra={}, override={}):
bcc = msg['Bcc']
del msg['Bcc']
return (msg, headers, bcc)
def send_mail_preformatted(request, preformatted, extra={}, override={}):
"""Parse preformatted string containing mail with From:, To:, ...,
and send it through the standard IETF mail interface (inserting
extra headers as needed)."""
(msg,headers,bcc) = parse_preformatted(preformatted, extra, override)
send_mail_text(request, msg['To'], msg["From"], msg["Subject"], msg.get_payload(), extra=headers, bcc=bcc)
return msg
@ -278,6 +297,26 @@ def log_smtp_exception(e):
log(" Traceback: %s" % tb)
return (extype, value, tb)
@contextmanager
def smtp_error_user_warning(thing,request):
try:
yield thing
except smtplib.SMTPException as e:
(extype, value, tb) = log_smtp_exception(e)
warning = "An error occured while sending email with\n"
warning += "Subject: %s\n" % e.original_msg.get('Subject','[no subject]')
warning += "To: %s\n" % e.original_msg.get('To','[no to]')
warning += "Cc: %s\n" % e.original_msg.get('Cc','[no cc]')
if isinstance(e,SMTPSomeRefusedRecipients):
warning += e.detailed_refusals()
else:
warning += "SMTP Exception: %s\n"%extype
warning += "Error Message: %s\n\n"%value
warning += "The message was not delivered to anyone."
messages.warning(request,warning,extra_tags='preformatted',fail_silently=True)
raise
@contextmanager
def smtp_error_logging(thing):
try:
@ -324,9 +363,31 @@ def smtp_error_logging(thing):
%s
--------- END ORIGINAL MESSAGE ---------
""") % e.original_msg.as_string()
send_error_to_secretariat(msg)
def send_error_to_secretariat(raw_msg):
(parsed_msg , headers , bcc) = parse_preformatted(raw_msg)
send_msg = encode_message(parsed_msg.get_payload())
cc = None
condition_message(parsed_msg['To'], parsed_msg['From'], parsed_msg['Subject'], send_msg, cc, headers)
debugging = getattr(settings, "USING_DEBUG_EMAIL_SERVER", False) and settings.EMAIL_HOST == 'localhost' and settings.EMAIL_PORT == 2025
try:
if test_mode or debugging or settings.SERVER_MODE == 'production':
send_smtp(send_msg, bcc)
try:
send_mail_preformatted(request=None, preformatted=msg)
except smtplib.SMTPException:
log("Exception encountered while sending a ticket to the secretariat")
(extype,value) = sys.exc_info()[:2]
log("SMTP Exception: %s : %s" % (extype,value))
copy_to = settings.EMAIL_COPY_TO
except AttributeError:
copy_to = "ietf.tracker.archive+%s@gmail.com" % settings.SERVER_MODE
if copy_to and not test_mode and not debugging: # if we're running automated tests, this copy is just annoying
if bcc:
send_msg['X-Tracker-Bcc']=bcc
copy_email(send_msg, copy_to,originalBcc=bcc)
except smtplib.SMTPException:
log("Exception encountered while sending a ticket to the secretariat")
(extype,value) = sys.exc_info()[:2]
log("SMTP Exception: %s : %s" % (extype,value))

View file

@ -1,11 +1,10 @@
import os.path
from smtplib import SMTPRecipientsRefused
from django.conf import settings
from django.test import TestCase
from ietf.utils.management.commands import pyflakes
from ietf.utils.mail import send_mail_text, outbox, SMTPSomeRefusedRecipients, smtp_error_logging
from ietf.utils.mail import send_mail_text, outbox
class PyFlakesTestCase(TestCase):
@ -22,21 +21,13 @@ class TestSMTPServer(TestCase):
def send_mail(to):
send_mail_text(None, to=to, frm=None, subject="Test for rejection", txt="dummy body")
with self.assertRaises(SMTPSomeRefusedRecipients):
send_mail('good@example.com,poison@example.com')
with self.assertRaises(SMTPRecipientsRefused):
send_mail('poison@example.com')
len_before = len(outbox)
with smtp_error_logging(send_mail) as send:
send('good@example.com,poison@example.com')
send_mail('good@example.com,poison@example.com')
self.assertEqual(len(outbox),len_before+2)
self.assertTrue('Some recipients were refused' in outbox[-1]['Subject'])
len_before = len(outbox)
with smtp_error_logging(send_mail) as send:
send('poison@example.com')
send_mail('poison@example.com')
self.assertEqual(len(outbox),len_before+2)
self.assertTrue('error while sending email' in outbox[-1]['Subject'])

View file

@ -228,6 +228,7 @@ li.info { margin: 0.5em; background-color: #ff8; }
li.success { margin: 0.5em; background-color: #4f4; }
li.warning { margin: 0.5em; background-color: #fc8; color: black;}
li.error { margin: 0.5em; background-color: #f44; }
li.preformatted { white-space:pre-wrap; }
.errorlist, errorlist li {
background: red;
@ -423,4 +424,4 @@ span.fieldRequired {
.login {
font-style: italic;
}
}