mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #10355 -- Added an API for pluggable e-mail backends.
Thanks to Andi Albrecht for his work on this patch, and to everyone else that contributed during design and development. git-svn-id: http://code.djangoproject.com/svn/django/trunk@11709 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -131,6 +131,12 @@ DATABASE_HOST = ''             # Set to empty string for localhost. Not used wit | ||||
| DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3. | ||||
| DATABASE_OPTIONS = {}          # Set to empty dictionary for default. | ||||
|  | ||||
| # The email backend to use. For possible shortcuts see django.core.mail. | ||||
| # The default is to use the SMTP backend. | ||||
| # Third-party backends can be specified by providing a Python path | ||||
| # to a module that defines an EmailBackend class. | ||||
| EMAIL_BACKEND = 'django.core.mail.backends.smtp' | ||||
|  | ||||
| # Host for sending e-mail. | ||||
| EMAIL_HOST = 'localhost' | ||||
|  | ||||
|   | ||||
							
								
								
									
										110
									
								
								django/core/mail/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								django/core/mail/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| """ | ||||
| Tools for sending email. | ||||
| """ | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.utils.importlib import import_module | ||||
|  | ||||
| # Imported for backwards compatibility, and for the sake | ||||
| # of a cleaner namespace. These symbols used to be in | ||||
| # django/core/mail.py before the introduction of email | ||||
| # backends and the subsequent reorganization (See #10355) | ||||
| from django.core.mail.utils import CachedDnsName, DNS_NAME | ||||
| from django.core.mail.message import \ | ||||
|     EmailMessage, EmailMultiAlternatives, \ | ||||
|     SafeMIMEText, SafeMIMEMultipart, \ | ||||
|     DEFAULT_ATTACHMENT_MIME_TYPE, make_msgid, \ | ||||
|     BadHeaderError, forbid_multi_line_headers | ||||
| from django.core.mail.backends.smtp import EmailBackend as _SMTPConnection | ||||
|  | ||||
| def get_connection(backend=None, fail_silently=False, **kwds): | ||||
|     """Load an e-mail backend and return an instance of it. | ||||
|  | ||||
|     If backend is None (default) settings.EMAIL_BACKEND is used. | ||||
|  | ||||
|     Both fail_silently and other keyword arguments are used in the | ||||
|     constructor of the backend. | ||||
|     """ | ||||
|     path = backend or settings.EMAIL_BACKEND | ||||
|     try: | ||||
|         mod = import_module(path) | ||||
|     except ImportError, e: | ||||
|         raise ImproperlyConfigured(('Error importing email backend %s: "%s"' | ||||
|                                     % (path, e))) | ||||
|     try: | ||||
|         cls = getattr(mod, 'EmailBackend') | ||||
|     except AttributeError: | ||||
|         raise ImproperlyConfigured(('Module "%s" does not define a ' | ||||
|                                     '"EmailBackend" class' % path)) | ||||
|     return cls(fail_silently=fail_silently, **kwds) | ||||
|  | ||||
|  | ||||
| def send_mail(subject, message, from_email, recipient_list, | ||||
|               fail_silently=False, auth_user=None, auth_password=None, | ||||
|               connection=None): | ||||
|     """ | ||||
|     Easy wrapper for sending a single message to a recipient list. All members | ||||
|     of the recipient list will see the other recipients in the 'To' field. | ||||
|  | ||||
|     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: The API for this method is frozen. New code wanting to extend the | ||||
|     functionality should use the EmailMessage class directly. | ||||
|     """ | ||||
|     connection = connection or get_connection(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, connection=None): | ||||
|     """ | ||||
|     Given a datatuple of (subject, message, from_email, recipient_list), sends | ||||
|     each message to each recipient list. Returns the number of e-mails sent. | ||||
|  | ||||
|     If from_email is None, the DEFAULT_FROM_EMAIL setting is used. | ||||
|     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: The API for this method is frozen. New code wanting to extend the | ||||
|     functionality should use the EmailMessage class directly. | ||||
|     """ | ||||
|     connection = connection or get_connection(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, connection=None): | ||||
|     """Sends a message to the admins, as defined by the ADMINS setting.""" | ||||
|     if not settings.ADMINS: | ||||
|         return | ||||
|     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, | ||||
|                  settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], | ||||
|                  connection=connection).send(fail_silently=fail_silently) | ||||
|  | ||||
|  | ||||
| def mail_managers(subject, message, fail_silently=False, connection=None): | ||||
|     """Sends a message to the managers, as defined by the MANAGERS setting.""" | ||||
|     if not settings.MANAGERS: | ||||
|         return | ||||
|     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, | ||||
|                  settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], | ||||
|                  connection=connection).send(fail_silently=fail_silently) | ||||
|  | ||||
|  | ||||
| class SMTPConnection(_SMTPConnection): | ||||
|     def __init__(self, *args, **kwds): | ||||
|         import warnings | ||||
|         warnings.warn( | ||||
|             'mail.SMTPConnection is deprecated; use mail.get_connection() instead.', | ||||
|             DeprecationWarning | ||||
|         ) | ||||
|         super(SMTPConnection, self).__init__(*args, **kwds) | ||||
							
								
								
									
										1
									
								
								django/core/mail/backends/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								django/core/mail/backends/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| # Mail backends shipped with Django. | ||||
							
								
								
									
										39
									
								
								django/core/mail/backends/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								django/core/mail/backends/base.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| """Base email backend class.""" | ||||
|  | ||||
| class BaseEmailBackend(object): | ||||
|     """ | ||||
|     Base class for email backend implementations. | ||||
|  | ||||
|     Subclasses must at least overwrite send_messages(). | ||||
|     """ | ||||
|     def __init__(self, fail_silently=False, **kwargs): | ||||
|         self.fail_silently = fail_silently | ||||
|  | ||||
|     def open(self): | ||||
|         """Open a network connection. | ||||
|  | ||||
|         This method can be overwritten by backend implementations to | ||||
|         open a network connection. | ||||
|  | ||||
|         It's up to the backend implementation to track the status of | ||||
|         a network connection if it's needed by the backend. | ||||
|  | ||||
|         This method can be called by applications to force a single | ||||
|         network connection to be used when sending mails. See the | ||||
|         send_messages() method of the SMTP backend for a reference | ||||
|         implementation. | ||||
|  | ||||
|         The default implementation does nothing. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def close(self): | ||||
|         """Close a network connection.""" | ||||
|         pass | ||||
|  | ||||
|     def send_messages(self, email_messages): | ||||
|         """ | ||||
|         Sends one or more EmailMessage objects and returns the number of email | ||||
|         messages sent. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
							
								
								
									
										34
									
								
								django/core/mail/backends/console.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								django/core/mail/backends/console.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| """ | ||||
| Email backend that writes messages to console instead of sending them. | ||||
| """ | ||||
| import sys | ||||
| import threading | ||||
|  | ||||
| from django.core.mail.backends.base import BaseEmailBackend | ||||
|  | ||||
| class EmailBackend(BaseEmailBackend): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         self.stream = kwargs.pop('stream', sys.stdout) | ||||
|         self._lock = threading.RLock() | ||||
|         super(EmailBackend, self).__init__(*args, **kwargs) | ||||
|  | ||||
|     def send_messages(self, email_messages): | ||||
|         """Write all messages to the stream in a thread-safe way.""" | ||||
|         if not email_messages: | ||||
|             return | ||||
|         self._lock.acquire() | ||||
|         try: | ||||
|             stream_created = self.open() | ||||
|             for message in email_messages: | ||||
|                 self.stream.write('%s\n' % message.message().as_string()) | ||||
|                 self.stream.write('-'*79) | ||||
|                 self.stream.write('\n') | ||||
|                 self.stream.flush()  # flush after each message | ||||
|             if stream_created: | ||||
|                 self.close() | ||||
|         except: | ||||
|             if not self.fail_silently: | ||||
|                 raise | ||||
|         finally: | ||||
|             self._lock.release() | ||||
|         return len(email_messages) | ||||
							
								
								
									
										9
									
								
								django/core/mail/backends/dummy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								django/core/mail/backends/dummy.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| """ | ||||
| Dummy email backend that does nothing. | ||||
| """ | ||||
|  | ||||
| from django.core.mail.backends.base import BaseEmailBackend | ||||
|  | ||||
| class EmailBackend(BaseEmailBackend): | ||||
|     def send_messages(self, email_messages): | ||||
|         return len(email_messages) | ||||
							
								
								
									
										59
									
								
								django/core/mail/backends/filebased.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								django/core/mail/backends/filebased.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| """Email backend that writes messages to a file.""" | ||||
|  | ||||
| import datetime | ||||
| import os | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.core.mail.backends.console import EmailBackend as ConsoleEmailBackend | ||||
|  | ||||
| class EmailBackend(ConsoleEmailBackend): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         self._fname = None | ||||
|         if 'file_path' in kwargs: | ||||
|             self.file_path = kwargs.pop('file_path') | ||||
|         else: | ||||
|             self.file_path = getattr(settings, 'EMAIL_FILE_PATH',None) | ||||
|         # Make sure self.file_path is a string. | ||||
|         if not isinstance(self.file_path, basestring): | ||||
|             raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.file_path) | ||||
|         self.file_path = os.path.abspath(self.file_path) | ||||
|         # Make sure that self.file_path is an directory if it exists. | ||||
|         if os.path.exists(self.file_path) and not os.path.isdir(self.file_path): | ||||
|             raise ImproperlyConfigured('Path for saving email messages exists, but is not a directory: %s' % self.file_path) | ||||
|         # Try to create it, if it not exists. | ||||
|         elif not os.path.exists(self.file_path): | ||||
|             try: | ||||
|                 os.makedirs(self.file_path) | ||||
|             except OSError, err: | ||||
|                 raise ImproperlyConfigured('Could not create directory for saving email messages: %s (%s)' % (self.file_path, err)) | ||||
|         # Make sure that self.file_path is writable. | ||||
|         if not os.access(self.file_path, os.W_OK): | ||||
|             raise ImproperlyConfigured('Could not write to directory: %s' % self.file_path) | ||||
|         # Finally, call super(). | ||||
|         # Since we're using the console-based backend as a base, | ||||
|         # force the stream to be None, so we don't default to stdout | ||||
|         kwargs['stream'] = None | ||||
|         super(EmailBackend, self).__init__(*args, **kwargs) | ||||
|  | ||||
|     def _get_filename(self): | ||||
|         """Return a unique file name.""" | ||||
|         if self._fname is None: | ||||
|             timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") | ||||
|             fname = "%s-%s.log" % (timestamp, abs(id(self))) | ||||
|             self._fname = os.path.join(self.file_path, fname) | ||||
|         return self._fname | ||||
|  | ||||
|     def open(self): | ||||
|         if self.stream is None: | ||||
|             self.stream = open(self._get_filename(), 'a') | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     def close(self): | ||||
|         try: | ||||
|             if self.stream is not None: | ||||
|                 self.stream.close() | ||||
|         finally: | ||||
|             self.stream = None | ||||
|  | ||||
							
								
								
									
										24
									
								
								django/core/mail/backends/locmem.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								django/core/mail/backends/locmem.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| """ | ||||
| Backend for test environment. | ||||
| """ | ||||
|  | ||||
| from django.core import mail | ||||
| from django.core.mail.backends.base import BaseEmailBackend | ||||
|  | ||||
| class EmailBackend(BaseEmailBackend): | ||||
|     """A email backend for use during test sessions. | ||||
|  | ||||
|     The test connection stores email messages in a dummy outbox, | ||||
|     rather than sending them out on the wire. | ||||
|  | ||||
|     The dummy outbox is accessible through the outbox instance attribute. | ||||
|     """ | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(EmailBackend, self).__init__(*args, **kwargs) | ||||
|         if not hasattr(mail, 'outbox'): | ||||
|             mail.outbox = [] | ||||
|  | ||||
|     def send_messages(self, messages): | ||||
|         """Redirect messages to the dummy outbox""" | ||||
|         mail.outbox.extend(messages) | ||||
|         return len(messages) | ||||
							
								
								
									
										103
									
								
								django/core/mail/backends/smtp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								django/core/mail/backends/smtp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| """SMTP email backend class.""" | ||||
|  | ||||
| import smtplib | ||||
| import socket | ||||
| import threading | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.core.mail.backends.base import BaseEmailBackend | ||||
| from django.core.mail.utils import DNS_NAME | ||||
|  | ||||
| class EmailBackend(BaseEmailBackend): | ||||
|     """ | ||||
|     A wrapper that manages the SMTP network connection. | ||||
|     """ | ||||
|     def __init__(self, host=None, port=None, username=None, password=None, | ||||
|                  use_tls=None, fail_silently=False, **kwargs): | ||||
|         super(EmailBackend, self).__init__(fail_silently=fail_silently) | ||||
|         self.host = host or settings.EMAIL_HOST | ||||
|         self.port = port or settings.EMAIL_PORT | ||||
|         self.username = username or settings.EMAIL_HOST_USER | ||||
|         self.password = password or settings.EMAIL_HOST_PASSWORD | ||||
|         self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS | ||||
|         self.connection = None | ||||
|         self._lock = threading.RLock() | ||||
|  | ||||
|     def open(self): | ||||
|         """ | ||||
|         Ensures we have a connection to the email server. Returns whether or | ||||
|         not a new connection was required (True or False). | ||||
|         """ | ||||
|         if self.connection: | ||||
|             # Nothing to do if the connection is already open. | ||||
|             return False | ||||
|         try: | ||||
|             # If local_hostname is not specified, socket.getfqdn() gets used. | ||||
|             # For performance, we use the cached FQDN for local_hostname. | ||||
|             self.connection = smtplib.SMTP(self.host, self.port, | ||||
|                                            local_hostname=DNS_NAME.get_fqdn()) | ||||
|             if self.use_tls: | ||||
|                 self.connection.ehlo() | ||||
|                 self.connection.starttls() | ||||
|                 self.connection.ehlo() | ||||
|             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): | ||||
|         """Closes the connection to the email server.""" | ||||
|         try: | ||||
|             try: | ||||
|                 self.connection.quit() | ||||
|             except socket.sslerror: | ||||
|                 # This happens when calling quit() on a TLS connection | ||||
|                 # sometimes. | ||||
|                 self.connection.close() | ||||
|             except: | ||||
|                 if self.fail_silently: | ||||
|                     return | ||||
|                 raise | ||||
|         finally: | ||||
|             self.connection = None | ||||
|  | ||||
|     def send_messages(self, email_messages): | ||||
|         """ | ||||
|         Sends one or more EmailMessage objects and returns the number of email | ||||
|         messages sent. | ||||
|         """ | ||||
|         if not email_messages: | ||||
|             return | ||||
|         self._lock.acquire() | ||||
|         try: | ||||
|             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() | ||||
|         finally: | ||||
|             self._lock.release() | ||||
|         return num_sent | ||||
|  | ||||
|     def _send(self, email_message): | ||||
|         """A helper method that does the actual sending.""" | ||||
|         if not email_message.recipients(): | ||||
|             return False | ||||
|         try: | ||||
|             self.connection.sendmail(email_message.from_email, | ||||
|                     email_message.recipients(), | ||||
|                     email_message.message().as_string()) | ||||
|         except: | ||||
|             if not self.fail_silently: | ||||
|                 raise | ||||
|             return False | ||||
|         return True | ||||
| @@ -1,13 +1,7 @@ | ||||
| """ | ||||
| Tools for sending email. | ||||
| """ | ||||
| 
 | ||||
| import mimetypes | ||||
| import os | ||||
| import smtplib | ||||
| import socket | ||||
| import time | ||||
| import random | ||||
| import time | ||||
| from email import Charset, Encoders | ||||
| from email.MIMEText import MIMEText | ||||
| from email.MIMEMultipart import MIMEMultipart | ||||
| @@ -16,6 +10,7 @@ from email.Header import Header | ||||
| from email.Utils import formatdate, parseaddr, formataddr | ||||
| 
 | ||||
| from django.conf import settings | ||||
| from django.core.mail.utils import DNS_NAME | ||||
| from django.utils.encoding import smart_str, force_unicode | ||||
| 
 | ||||
| # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from | ||||
| @@ -26,18 +21,10 @@ Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8') | ||||
| # and cannot be guessed). | ||||
| DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream' | ||||
| 
 | ||||
| # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of | ||||
| # seconds, which slows down the restart of the server. | ||||
| class CachedDnsName(object): | ||||
|     def __str__(self): | ||||
|         return self.get_fqdn() | ||||
| 
 | ||||
|     def get_fqdn(self): | ||||
|         if not hasattr(self, '_fqdn'): | ||||
|             self._fqdn = socket.getfqdn() | ||||
|         return self._fqdn | ||||
| class BadHeaderError(ValueError): | ||||
|     pass | ||||
| 
 | ||||
| DNS_NAME = CachedDnsName() | ||||
| 
 | ||||
| # Copied from Python standard library, with the following modifications: | ||||
| # * Used cached hostname for performance. | ||||
| @@ -66,8 +53,6 @@ def make_msgid(idstring=None): | ||||
|     msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost) | ||||
|     return msgid | ||||
| 
 | ||||
| class BadHeaderError(ValueError): | ||||
|     pass | ||||
| 
 | ||||
| def forbid_multi_line_headers(name, val): | ||||
|     """Forbids multi-line headers, to prevent header injection.""" | ||||
| @@ -91,104 +76,18 @@ def forbid_multi_line_headers(name, val): | ||||
|             val = Header(val) | ||||
|     return name, val | ||||
| 
 | ||||
| 
 | ||||
| class SafeMIMEText(MIMEText): | ||||
|     def __setitem__(self, name, val): | ||||
|         name, val = forbid_multi_line_headers(name, val) | ||||
|         MIMEText.__setitem__(self, name, val) | ||||
| 
 | ||||
| 
 | ||||
| class SafeMIMEMultipart(MIMEMultipart): | ||||
|     def __setitem__(self, name, val): | ||||
|         name, val = forbid_multi_line_headers(name, val) | ||||
|         MIMEMultipart.__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, | ||||
|                  use_tls=None, fail_silently=False): | ||||
|         self.host = host or settings.EMAIL_HOST | ||||
|         self.port = port or settings.EMAIL_PORT | ||||
|         self.username = username or settings.EMAIL_HOST_USER | ||||
|         self.password = password or settings.EMAIL_HOST_PASSWORD | ||||
|         self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS | ||||
|         self.fail_silently = fail_silently | ||||
|         self.connection = None | ||||
| 
 | ||||
|     def open(self): | ||||
|         """ | ||||
|         Ensures we have a connection to the email server. Returns whether or | ||||
|         not a new connection was required (True or False). | ||||
|         """ | ||||
|         if self.connection: | ||||
|             # Nothing to do if the connection is already open. | ||||
|             return False | ||||
|         try: | ||||
|             # If local_hostname is not specified, socket.getfqdn() gets used. | ||||
|             # For performance, we use the cached FQDN for local_hostname. | ||||
|             self.connection = smtplib.SMTP(self.host, self.port, | ||||
|                                            local_hostname=DNS_NAME.get_fqdn()) | ||||
|             if self.use_tls: | ||||
|                 self.connection.ehlo() | ||||
|                 self.connection.starttls() | ||||
|                 self.connection.ehlo() | ||||
|             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): | ||||
|         """Closes the connection to the email server.""" | ||||
|         try: | ||||
|             try: | ||||
|                 self.connection.quit() | ||||
|             except socket.sslerror: | ||||
|                 # This happens when calling quit() on a TLS connection | ||||
|                 # sometimes. | ||||
|                 self.connection.close() | ||||
|             except: | ||||
|                 if self.fail_silently: | ||||
|                     return | ||||
|                 raise | ||||
|         finally: | ||||
|             self.connection = None | ||||
| 
 | ||||
|     def send_messages(self, email_messages): | ||||
|         """ | ||||
|         Sends one or more EmailMessage objects and returns 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.recipients(): | ||||
|             return False | ||||
|         try: | ||||
|             self.connection.sendmail(email_message.from_email, | ||||
|                     email_message.recipients(), | ||||
|                     email_message.message().as_string()) | ||||
|         except: | ||||
|             if not self.fail_silently: | ||||
|                 raise | ||||
|             return False | ||||
|         return True | ||||
| 
 | ||||
| class EmailMessage(object): | ||||
|     """ | ||||
| @@ -199,14 +98,14 @@ class EmailMessage(object): | ||||
|     encoding = None     # None => use settings default | ||||
| 
 | ||||
|     def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, | ||||
|             connection=None, attachments=None, headers=None): | ||||
|                  connection=None, attachments=None, headers=None): | ||||
|         """ | ||||
|         Initialize a single email message (which can be sent to multiple | ||||
|         recipients). | ||||
| 
 | ||||
|         All strings used to create the message can be unicode strings (or UTF-8 | ||||
|         bytestrings). The SafeMIMEText class will handle any necessary encoding | ||||
|         conversions. | ||||
|         All strings used to create the message can be unicode strings | ||||
|         (or UTF-8 bytestrings). The SafeMIMEText class will handle any | ||||
|         necessary encoding conversions. | ||||
|         """ | ||||
|         if to: | ||||
|             assert not isinstance(to, basestring), '"to" argument must be a list or tuple' | ||||
| @@ -226,8 +125,9 @@ class EmailMessage(object): | ||||
|         self.connection = connection | ||||
| 
 | ||||
|     def get_connection(self, fail_silently=False): | ||||
|         from django.core.mail import get_connection | ||||
|         if not self.connection: | ||||
|             self.connection = SMTPConnection(fail_silently=fail_silently) | ||||
|             self.connection = get_connection(fail_silently=fail_silently) | ||||
|         return self.connection | ||||
| 
 | ||||
|     def message(self): | ||||
| @@ -332,6 +232,7 @@ class EmailMessage(object): | ||||
|                                   filename=filename) | ||||
|         return attachment | ||||
| 
 | ||||
| 
 | ||||
| class EmailMultiAlternatives(EmailMessage): | ||||
|     """ | ||||
|     A version of EmailMessage that makes it easy to send multipart/alternative | ||||
| @@ -371,56 +272,3 @@ class EmailMultiAlternatives(EmailMessage): | ||||
|             for alternative in self.alternatives: | ||||
|                 msg.attach(self._create_mime_attachment(*alternative)) | ||||
|         return msg | ||||
| 
 | ||||
| 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 | ||||
|     of the recipient list will see the other recipients in the 'To' field. | ||||
| 
 | ||||
|     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: The API for this method is frozen. New code wanting to extend the | ||||
|     functionality should use the EmailMessage class directly. | ||||
|     """ | ||||
|     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): | ||||
|     """ | ||||
|     Given a datatuple of (subject, message, from_email, recipient_list), sends | ||||
|     each message to each recipient list. Returns the number of e-mails sent. | ||||
| 
 | ||||
|     If from_email is None, the DEFAULT_FROM_EMAIL setting is used. | ||||
|     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: The API for this method is frozen. New code wanting to extend the | ||||
|     functionality should use the EmailMessage class directly. | ||||
|     """ | ||||
|     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.""" | ||||
|     if not settings.ADMINS: | ||||
|         return | ||||
|     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.""" | ||||
|     if not settings.MANAGERS: | ||||
|         return | ||||
|     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, | ||||
|                  settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS] | ||||
|                  ).send(fail_silently=fail_silently) | ||||
							
								
								
									
										19
									
								
								django/core/mail/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								django/core/mail/utils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| """ | ||||
| Email message and email sending related helper functions. | ||||
| """ | ||||
|  | ||||
| import socket | ||||
|  | ||||
|  | ||||
| # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of | ||||
| # seconds, which slows down the restart of the server. | ||||
| class CachedDnsName(object): | ||||
|     def __str__(self): | ||||
|         return self.get_fqdn() | ||||
|  | ||||
|     def get_fqdn(self): | ||||
|         if not hasattr(self, '_fqdn'): | ||||
|             self._fqdn = socket.getfqdn() | ||||
|         return self._fqdn | ||||
|  | ||||
| DNS_NAME = CachedDnsName() | ||||
| @@ -2,6 +2,7 @@ import sys, time, os | ||||
| from django.conf import settings | ||||
| from django.db import connection | ||||
| from django.core import mail | ||||
| from django.core.mail.backends import locmem | ||||
| from django.test import signals | ||||
| from django.template import Template | ||||
| from django.utils.translation import deactivate | ||||
| @@ -28,37 +29,22 @@ def instrumented_test_render(self, context): | ||||
|     signals.template_rendered.send(sender=self, template=self, context=context) | ||||
|     return self.nodelist.render(context) | ||||
|  | ||||
| class TestSMTPConnection(object): | ||||
|     """A substitute SMTP connection for use during test sessions. | ||||
|     The test connection stores email messages in a dummy outbox, | ||||
|     rather than sending them out on the wire. | ||||
|  | ||||
|     """ | ||||
|     def __init__(*args, **kwargs): | ||||
|         pass | ||||
|     def open(self): | ||||
|         "Mock the SMTPConnection open() interface" | ||||
|         pass | ||||
|     def close(self): | ||||
|         "Mock the SMTPConnection close() interface" | ||||
|         pass | ||||
|     def send_messages(self, messages): | ||||
|         "Redirect messages to the dummy outbox" | ||||
|         mail.outbox.extend(messages) | ||||
|         return len(messages) | ||||
|  | ||||
| def setup_test_environment(): | ||||
|     """Perform any global pre-test setup. This involves: | ||||
|  | ||||
|         - Installing the instrumented test renderer | ||||
|         - Diverting the email sending functions to a test buffer | ||||
|         - Set the email backend to the locmem email backend. | ||||
|         - Setting the active locale to match the LANGUAGE_CODE setting. | ||||
|     """ | ||||
|     Template.original_render = Template.render | ||||
|     Template.render = instrumented_test_render | ||||
|  | ||||
|     mail.original_SMTPConnection = mail.SMTPConnection | ||||
|     mail.SMTPConnection = TestSMTPConnection | ||||
|     mail.SMTPConnection = locmem.EmailBackend | ||||
|  | ||||
|     settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem' | ||||
|     mail.original_email_backend = settings.EMAIL_BACKEND | ||||
|  | ||||
|     mail.outbox = [] | ||||
|  | ||||
| @@ -77,8 +63,10 @@ def teardown_test_environment(): | ||||
|     mail.SMTPConnection = mail.original_SMTPConnection | ||||
|     del mail.original_SMTPConnection | ||||
|  | ||||
|     del mail.outbox | ||||
|     settings.EMAIL_BACKEND = mail.original_email_backend | ||||
|     del mail.original_email_backend | ||||
|  | ||||
|     del mail.outbox | ||||
|  | ||||
| def get_runner(settings): | ||||
|     test_path = settings.TEST_RUNNER.split('.') | ||||
|   | ||||
		Reference in New Issue
	
	Block a user