mirror of
https://github.com/django/django.git
synced 2025-10-23 21:59:11 +00:00
Fixed #3366 -- Part 1 of the email code refactoring and feature extension. This
part refactors email sending into a more object-oriented interface in order to make adding new features possible without making the API unusable. Thanks to Gary Wilson for doing the design thinking and initial coding on this. Includes documentation addition, but it probably needs a rewrite/edit, since I'm not very happy with it at the moment. git-svn-id: http://code.djangoproject.com/svn/django/trunk@5141 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
# Use this module for e-mailing.
|
||||
"""
|
||||
Tools for sending email.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from email.MIMEText import MIMEText
|
||||
from email.Header import Header
|
||||
from email.Utils import formatdate
|
||||
import os
|
||||
import smtplib
|
||||
import socket
|
||||
import time
|
||||
@@ -22,6 +25,28 @@ class CachedDnsName(object):
|
||||
|
||||
DNS_NAME = CachedDnsName()
|
||||
|
||||
# Copied from Python standard library and modified to used the cached hostname
|
||||
# for performance.
|
||||
def make_msgid(idstring=None):
|
||||
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
|
||||
|
||||
<20020201195627.33539.96671@nightshade.la.mastaler.com>
|
||||
|
||||
Optional idstring if given is a string used to strengthen the
|
||||
uniqueness of the message id.
|
||||
"""
|
||||
timeval = time.time()
|
||||
utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
|
||||
pid = os.getpid()
|
||||
randint = random.randrange(100000)
|
||||
if idstring is None:
|
||||
idstring = ''
|
||||
else:
|
||||
idstring = '.' + idstring
|
||||
idhost = DNS_NAME
|
||||
msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
|
||||
return msgid
|
||||
|
||||
class BadHeaderError(ValueError):
|
||||
pass
|
||||
|
||||
@@ -34,6 +59,117 @@ class SafeMIMEText(MIMEText):
|
||||
val = Header(val, settings.DEFAULT_CHARSET)
|
||||
MIMEText.__setitem__(self, name, val)
|
||||
|
||||
class SMTPConnection(object):
|
||||
"""
|
||||
A wrapper that manages the SMTP network connection.
|
||||
"""
|
||||
|
||||
def __init__(self, host=None, port=None, username=None, password=None,
|
||||
fail_silently=False):
|
||||
if host is None:
|
||||
self.host = settings.EMAIL_HOST
|
||||
if port is None:
|
||||
self.port = settings.EMAIL_PORT
|
||||
if username is None:
|
||||
self.username = settings.EMAIL_HOST_USER
|
||||
if password is None:
|
||||
self.password = settings.EMAIL_HOST_PASSWORD
|
||||
self.fail_silently = fail_silently
|
||||
self.connection = None
|
||||
|
||||
def open(self):
|
||||
"""
|
||||
Ensure we have a connection to the email server. Returns whether or not
|
||||
a new connection was required.
|
||||
"""
|
||||
if self.connection:
|
||||
# Nothing to do if the connection is already open.
|
||||
return False
|
||||
try:
|
||||
self.connection = smtplib.SMTP(self.host, self.port)
|
||||
if self.username and self.password:
|
||||
self.connection.login(self.username, self.password)
|
||||
return True
|
||||
except:
|
||||
if not self.fail_silently:
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
"""Close the connection to the email server."""
|
||||
try:
|
||||
try:
|
||||
self.connection.quit()
|
||||
except:
|
||||
if self.fail_silently:
|
||||
return
|
||||
raise
|
||||
finally:
|
||||
self.connection = None
|
||||
|
||||
def send_messages(self, email_messages):
|
||||
"""
|
||||
Send one or more EmailMessage objects and return the number of email
|
||||
messages sent.
|
||||
"""
|
||||
if not email_messages:
|
||||
return
|
||||
new_conn_created = self.open()
|
||||
if not self.connection:
|
||||
# We failed silently on open(). Trying to send would be pointless.
|
||||
return
|
||||
num_sent = 0
|
||||
for message in email_messages:
|
||||
sent = self._send(message)
|
||||
if sent:
|
||||
num_sent += 1
|
||||
if new_conn_created:
|
||||
self.close()
|
||||
return num_sent
|
||||
|
||||
def _send(self, email_message):
|
||||
"""A helper method that does the actual sending."""
|
||||
if not email_message.to:
|
||||
return False
|
||||
try:
|
||||
self.connection.sendmail(email_message.from_email,
|
||||
email_message.to, email_message.message.as_string()())
|
||||
except:
|
||||
if not self.fail_silently:
|
||||
raise
|
||||
return False
|
||||
return True
|
||||
|
||||
class EmailMessage(object):
|
||||
"""
|
||||
A container for email information.
|
||||
"""
|
||||
def __init__(self, subject='', body='', from_email=None, to=None, connection=None):
|
||||
self.to = to or []
|
||||
if from_email is None:
|
||||
self.from_email = settings.DEFAULT_FROM_EMAIL
|
||||
else:
|
||||
self.from_email = from_email
|
||||
self.subject = subject
|
||||
self.body = body
|
||||
self.connection = connection
|
||||
|
||||
def get_connection(self, fail_silently=False):
|
||||
if not self.connection:
|
||||
self.connection = SMTPConnection(fail_silently=fail_silently)
|
||||
return self.connection
|
||||
|
||||
def message(self):
|
||||
msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
|
||||
msg['Subject'] = self.subject
|
||||
msg['From'] = self.from_email
|
||||
msg['To'] = ', '.join(self.to)
|
||||
msg['Date'] = formatdate()
|
||||
msg['Message-ID'] = make_msgid()
|
||||
|
||||
def send(self, fail_silently=False):
|
||||
"""Send the email message."""
|
||||
return self.get_connection(fail_silently).send_messages([self])
|
||||
|
||||
def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None):
|
||||
"""
|
||||
Easy wrapper for sending a single message to a recipient list. All members
|
||||
@@ -41,8 +177,13 @@ def send_mail(subject, message, from_email, recipient_list, fail_silently=False,
|
||||
|
||||
If auth_user is None, the EMAIL_HOST_USER setting is used.
|
||||
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
|
||||
|
||||
NOTE: This method is deprecated. It exists for backwards compatibility.
|
||||
New code should use the EmailMessage class directly.
|
||||
"""
|
||||
return send_mass_mail([[subject, message, from_email, recipient_list]], fail_silently, auth_user, auth_password)
|
||||
connection = SMTPConnection(username=auth_user, password=auth_password,
|
||||
fail_silently=fail_silently)
|
||||
return EmailMessage(subject, message, from_email, recipient_list, connection=connection).send()
|
||||
|
||||
def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None):
|
||||
"""
|
||||
@@ -53,52 +194,24 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password
|
||||
If auth_user and auth_password are set, they're used to log in.
|
||||
If auth_user is None, the EMAIL_HOST_USER setting is used.
|
||||
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
|
||||
|
||||
NOTE: This method is deprecated. It exists for backwards compatibility.
|
||||
New code should use the EmailMessage class directly.
|
||||
"""
|
||||
if auth_user is None:
|
||||
auth_user = settings.EMAIL_HOST_USER
|
||||
if auth_password is None:
|
||||
auth_password = settings.EMAIL_HOST_PASSWORD
|
||||
try:
|
||||
server = smtplib.SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT)
|
||||
if auth_user and auth_password:
|
||||
server.login(auth_user, auth_password)
|
||||
except:
|
||||
if fail_silently:
|
||||
return
|
||||
raise
|
||||
num_sent = 0
|
||||
for subject, message, from_email, recipient_list in datatuple:
|
||||
if not recipient_list:
|
||||
continue
|
||||
from_email = from_email or settings.DEFAULT_FROM_EMAIL
|
||||
msg = SafeMIMEText(message, 'plain', settings.DEFAULT_CHARSET)
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = from_email
|
||||
msg['To'] = ', '.join(recipient_list)
|
||||
msg['Date'] = formatdate()
|
||||
try:
|
||||
random_bits = str(random.getrandbits(64))
|
||||
except AttributeError: # Python 2.3 doesn't have random.getrandbits().
|
||||
random_bits = ''.join([random.choice('1234567890') for i in range(19)])
|
||||
msg['Message-ID'] = "<%d.%s@%s>" % (time.time(), random_bits, DNS_NAME)
|
||||
try:
|
||||
server.sendmail(from_email, recipient_list, msg.as_string())
|
||||
num_sent += 1
|
||||
except:
|
||||
if not fail_silently:
|
||||
raise
|
||||
try:
|
||||
server.quit()
|
||||
except:
|
||||
if fail_silently:
|
||||
return
|
||||
raise
|
||||
return num_sent
|
||||
connection = SMTPConnection(username=auth_user, password=auth_password,
|
||||
fail_silently=fail_silently)
|
||||
messages = [EmailMessage(subject, message, sender, recipient) for subject, message, sender, recipient in datatuple]
|
||||
return connection.send_messages(messages)
|
||||
|
||||
def mail_admins(subject, message, fail_silently=False):
|
||||
"Sends a message to the admins, as defined by the ADMINS setting."
|
||||
send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], fail_silently)
|
||||
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
|
||||
settings.SERVER_EMAIL, [a[1] for a in
|
||||
settings.ADMINS]).send(fail_silently=fail_silently)
|
||||
|
||||
def mail_managers(subject, message, fail_silently=False):
|
||||
"Sends a message to the managers, as defined by the MANAGERS setting."
|
||||
send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], fail_silently)
|
||||
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
|
||||
settings.SERVER_EMAIL, [a[1] for a in
|
||||
settings.MANAGERS]).send(fail_silently=fail_silently)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user