Ticket #10355: email_backends.2.diff
File email_backends.2.diff, 60.6 KB (added by , 15 years ago) |
---|
-
django/conf/global_settings.py
diff -r 1b4df7524b7c django/conf/global_settings.py
a b 131 131 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 132 132 DATABASE_OPTIONS = {} # Set to empty dictionary for default. 133 133 134 # The email backend to use. For possible shortcuts see django.core.mail. 135 # The default is to use the 'smtp' backend for sending emails using a 136 # SMTP server. 137 # Third-party backends could be configured by a Python import path. 138 EMAIL_BACKEND = 'smtp' 139 134 140 # Host for sending e-mail. 135 141 EMAIL_HOST = 'localhost' 136 142 … … 142 148 EMAIL_HOST_PASSWORD = '' 143 149 EMAIL_USE_TLS = False 144 150 151 # Directory for saving messages when using the file backend. 152 EMAIL_FILE_PATH = None 153 145 154 # List of strings representing installed apps. 146 155 INSTALLED_APPS = () 147 156 -
deleted file django/core/mail.py
diff -r 1b4df7524b7c django/core/mail.py
+ - 1 """2 Tools for sending email.3 """4 5 import mimetypes6 import os7 import smtplib8 import socket9 import time10 import random11 from email import Charset, Encoders12 from email.MIMEText import MIMEText13 from email.MIMEMultipart import MIMEMultipart14 from email.MIMEBase import MIMEBase15 from email.Header import Header16 from email.Utils import formatdate, parseaddr, formataddr17 18 from django.conf import settings19 from django.utils.encoding import smart_str, force_unicode20 21 # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from22 # some spam filters.23 Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')24 25 # Default MIME type to use on attachments (if it is not explicitly given26 # and cannot be guessed).27 DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'28 29 # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of30 # seconds, which slows down the restart of the server.31 class CachedDnsName(object):32 def __str__(self):33 return self.get_fqdn()34 35 def get_fqdn(self):36 if not hasattr(self, '_fqdn'):37 self._fqdn = socket.getfqdn()38 return self._fqdn39 40 DNS_NAME = CachedDnsName()41 42 # Copied from Python standard library, with the following modifications:43 # * Used cached hostname for performance.44 # * Added try/except to support lack of getpid() in Jython (#5496).45 def make_msgid(idstring=None):46 """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:47 48 <20020201195627.33539.96671@nightshade.la.mastaler.com>49 50 Optional idstring if given is a string used to strengthen the51 uniqueness of the message id.52 """53 timeval = time.time()54 utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))55 try:56 pid = os.getpid()57 except AttributeError:58 # No getpid() in Jython, for example.59 pid = 160 randint = random.randrange(100000)61 if idstring is None:62 idstring = ''63 else:64 idstring = '.' + idstring65 idhost = DNS_NAME66 msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)67 return msgid68 69 class BadHeaderError(ValueError):70 pass71 72 def forbid_multi_line_headers(name, val):73 """Forbids multi-line headers, to prevent header injection."""74 val = force_unicode(val)75 if '\n' in val or '\r' in val:76 raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))77 try:78 val = val.encode('ascii')79 except UnicodeEncodeError:80 if name.lower() in ('to', 'from', 'cc'):81 result = []82 for item in val.split(', '):83 nm, addr = parseaddr(item)84 nm = str(Header(nm, settings.DEFAULT_CHARSET))85 result.append(formataddr((nm, str(addr))))86 val = ', '.join(result)87 else:88 val = Header(val, settings.DEFAULT_CHARSET)89 else:90 if name.lower() == 'subject':91 val = Header(val)92 return name, val93 94 class SafeMIMEText(MIMEText):95 def __setitem__(self, name, val):96 name, val = forbid_multi_line_headers(name, val)97 MIMEText.__setitem__(self, name, val)98 99 class SafeMIMEMultipart(MIMEMultipart):100 def __setitem__(self, name, val):101 name, val = forbid_multi_line_headers(name, val)102 MIMEMultipart.__setitem__(self, name, val)103 104 class SMTPConnection(object):105 """106 A wrapper that manages the SMTP network connection.107 """108 109 def __init__(self, host=None, port=None, username=None, password=None,110 use_tls=None, fail_silently=False):111 self.host = host or settings.EMAIL_HOST112 self.port = port or settings.EMAIL_PORT113 self.username = username or settings.EMAIL_HOST_USER114 self.password = password or settings.EMAIL_HOST_PASSWORD115 self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS116 self.fail_silently = fail_silently117 self.connection = None118 119 def open(self):120 """121 Ensures we have a connection to the email server. Returns whether or122 not a new connection was required (True or False).123 """124 if self.connection:125 # Nothing to do if the connection is already open.126 return False127 try:128 # If local_hostname is not specified, socket.getfqdn() gets used.129 # For performance, we use the cached FQDN for local_hostname.130 self.connection = smtplib.SMTP(self.host, self.port,131 local_hostname=DNS_NAME.get_fqdn())132 if self.use_tls:133 self.connection.ehlo()134 self.connection.starttls()135 self.connection.ehlo()136 if self.username and self.password:137 self.connection.login(self.username, self.password)138 return True139 except:140 if not self.fail_silently:141 raise142 143 def close(self):144 """Closes the connection to the email server."""145 try:146 try:147 self.connection.quit()148 except socket.sslerror:149 # This happens when calling quit() on a TLS connection150 # sometimes.151 self.connection.close()152 except:153 if self.fail_silently:154 return155 raise156 finally:157 self.connection = None158 159 def send_messages(self, email_messages):160 """161 Sends one or more EmailMessage objects and returns the number of email162 messages sent.163 """164 if not email_messages:165 return166 new_conn_created = self.open()167 if not self.connection:168 # We failed silently on open(). Trying to send would be pointless.169 return170 num_sent = 0171 for message in email_messages:172 sent = self._send(message)173 if sent:174 num_sent += 1175 if new_conn_created:176 self.close()177 return num_sent178 179 def _send(self, email_message):180 """A helper method that does the actual sending."""181 if not email_message.recipients():182 return False183 try:184 self.connection.sendmail(email_message.from_email,185 email_message.recipients(),186 email_message.message().as_string())187 except:188 if not self.fail_silently:189 raise190 return False191 return True192 193 class EmailMessage(object):194 """195 A container for email information.196 """197 content_subtype = 'plain'198 mixed_subtype = 'mixed'199 encoding = None # None => use settings default200 201 def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,202 connection=None, attachments=None, headers=None):203 """204 Initialize a single email message (which can be sent to multiple205 recipients).206 207 All strings used to create the message can be unicode strings (or UTF-8208 bytestrings). The SafeMIMEText class will handle any necessary encoding209 conversions.210 """211 if to:212 assert not isinstance(to, basestring), '"to" argument must be a list or tuple'213 self.to = list(to)214 else:215 self.to = []216 if bcc:217 assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'218 self.bcc = list(bcc)219 else:220 self.bcc = []221 self.from_email = from_email or settings.DEFAULT_FROM_EMAIL222 self.subject = subject223 self.body = body224 self.attachments = attachments or []225 self.extra_headers = headers or {}226 self.connection = connection227 228 def get_connection(self, fail_silently=False):229 if not self.connection:230 self.connection = SMTPConnection(fail_silently=fail_silently)231 return self.connection232 233 def message(self):234 encoding = self.encoding or settings.DEFAULT_CHARSET235 msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),236 self.content_subtype, encoding)237 msg = self._create_message(msg)238 msg['Subject'] = self.subject239 msg['From'] = self.extra_headers.pop('From', self.from_email)240 msg['To'] = ', '.join(self.to)241 242 # Email header names are case-insensitive (RFC 2045), so we have to243 # accommodate that when doing comparisons.244 header_names = [key.lower() for key in self.extra_headers]245 if 'date' not in header_names:246 msg['Date'] = formatdate()247 if 'message-id' not in header_names:248 msg['Message-ID'] = make_msgid()249 for name, value in self.extra_headers.items():250 msg[name] = value251 return msg252 253 def recipients(self):254 """255 Returns a list of all recipients of the email (includes direct256 addressees as well as Bcc entries).257 """258 return self.to + self.bcc259 260 def send(self, fail_silently=False):261 """Sends the email message."""262 if not self.recipients():263 # Don't bother creating the network connection if there's nobody to264 # send to.265 return 0266 return self.get_connection(fail_silently).send_messages([self])267 268 def attach(self, filename=None, content=None, mimetype=None):269 """270 Attaches a file with the given filename and content. The filename can271 be omitted and the mimetype is guessed, if not provided.272 273 If the first parameter is a MIMEBase subclass it is inserted directly274 into the resulting message attachments.275 """276 if isinstance(filename, MIMEBase):277 assert content == mimetype == None278 self.attachments.append(filename)279 else:280 assert content is not None281 self.attachments.append((filename, content, mimetype))282 283 def attach_file(self, path, mimetype=None):284 """Attaches a file from the filesystem."""285 filename = os.path.basename(path)286 content = open(path, 'rb').read()287 self.attach(filename, content, mimetype)288 289 def _create_message(self, msg):290 return self._create_attachments(msg)291 292 def _create_attachments(self, msg):293 if self.attachments:294 body_msg = msg295 msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)296 if self.body:297 msg.attach(body_msg)298 for attachment in self.attachments:299 if isinstance(attachment, MIMEBase):300 msg.attach(attachment)301 else:302 msg.attach(self._create_attachment(*attachment))303 return msg304 305 def _create_mime_attachment(self, content, mimetype):306 """307 Converts the content, mimetype pair into a MIME attachment object.308 """309 basetype, subtype = mimetype.split('/', 1)310 if basetype == 'text':311 attachment = SafeMIMEText(smart_str(content,312 settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)313 else:314 # Encode non-text attachments with base64.315 attachment = MIMEBase(basetype, subtype)316 attachment.set_payload(content)317 Encoders.encode_base64(attachment)318 return attachment319 320 def _create_attachment(self, filename, content, mimetype=None):321 """322 Converts the filename, content, mimetype triple into a MIME attachment323 object.324 """325 if mimetype is None:326 mimetype, _ = mimetypes.guess_type(filename)327 if mimetype is None:328 mimetype = DEFAULT_ATTACHMENT_MIME_TYPE329 attachment = self._create_mime_attachment(content, mimetype)330 if filename:331 attachment.add_header('Content-Disposition', 'attachment',332 filename=filename)333 return attachment334 335 class EmailMultiAlternatives(EmailMessage):336 """337 A version of EmailMessage that makes it easy to send multipart/alternative338 messages. For example, including text and HTML versions of the text is339 made easier.340 """341 alternative_subtype = 'alternative'342 343 def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,344 connection=None, attachments=None, headers=None, alternatives=None):345 """346 Initialize a single email message (which can be sent to multiple347 recipients).348 349 All strings used to create the message can be unicode strings (or UTF-8350 bytestrings). The SafeMIMEText class will handle any necessary encoding351 conversions.352 """353 super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)354 self.alternatives=alternatives or []355 356 def attach_alternative(self, content, mimetype):357 """Attach an alternative content representation."""358 assert content is not None359 assert mimetype is not None360 self.alternatives.append((content, mimetype))361 362 def _create_message(self, msg):363 return self._create_attachments(self._create_alternatives(msg))364 365 def _create_alternatives(self, msg):366 if self.alternatives:367 body_msg = msg368 msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)369 if self.body:370 msg.attach(body_msg)371 for alternative in self.alternatives:372 msg.attach(self._create_mime_attachment(*alternative))373 return msg374 375 def send_mail(subject, message, from_email, recipient_list,376 fail_silently=False, auth_user=None, auth_password=None):377 """378 Easy wrapper for sending a single message to a recipient list. All members379 of the recipient list will see the other recipients in the 'To' field.380 381 If auth_user is None, the EMAIL_HOST_USER setting is used.382 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.383 384 Note: The API for this method is frozen. New code wanting to extend the385 functionality should use the EmailMessage class directly.386 """387 connection = SMTPConnection(username=auth_user, password=auth_password,388 fail_silently=fail_silently)389 return EmailMessage(subject, message, from_email, recipient_list,390 connection=connection).send()391 392 def send_mass_mail(datatuple, fail_silently=False, auth_user=None,393 auth_password=None):394 """395 Given a datatuple of (subject, message, from_email, recipient_list), sends396 each message to each recipient list. Returns the number of e-mails sent.397 398 If from_email is None, the DEFAULT_FROM_EMAIL setting is used.399 If auth_user and auth_password are set, they're used to log in.400 If auth_user is None, the EMAIL_HOST_USER setting is used.401 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.402 403 Note: The API for this method is frozen. New code wanting to extend the404 functionality should use the EmailMessage class directly.405 """406 connection = SMTPConnection(username=auth_user, password=auth_password,407 fail_silently=fail_silently)408 messages = [EmailMessage(subject, message, sender, recipient)409 for subject, message, sender, recipient in datatuple]410 return connection.send_messages(messages)411 412 def mail_admins(subject, message, fail_silently=False):413 """Sends a message to the admins, as defined by the ADMINS setting."""414 if not settings.ADMINS:415 return416 EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,417 settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]418 ).send(fail_silently=fail_silently)419 420 def mail_managers(subject, message, fail_silently=False):421 """Sends a message to the managers, as defined by the MANAGERS setting."""422 if not settings.MANAGERS:423 return424 EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,425 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]426 ).send(fail_silently=fail_silently) -
new file django/core/mail/__init__.py
diff -r 1b4df7524b7c django/core/mail/__init__.py
- + 1 """ 2 Tools for sending email. 3 """ 4 5 from django.conf import settings 6 from django.core.exceptions import ImproperlyConfigured 7 from django.core.mail.message import EmailMessage, EmailMultiAlternatives 8 from django.utils.importlib import import_module 9 10 # Imported for backwards compatibility 11 from django.core.mail.backends.smtp import EmailBackend as _SMTPConnection 12 13 14 # Shortcuts for builtin email backends. If settings.EMAIL_BACKEND isn't in 15 # this list it's treated as a Python import path. 16 BACKENDS = ('smtp', 'dummy', 'console', 'locmem', 'file') 17 18 19 def get_connection(fail_silently=False, **kwds): 20 """Load configured email backend and return an instance of it. 21 22 Both fail_silently and other keyword arguments are used in the 23 constructor of the backend. 24 """ 25 path = settings.EMAIL_BACKEND 26 if path in BACKENDS: 27 path = 'django.core.mail.backends.%s' % path 28 try: 29 mod = import_module(path) 30 except ImportError, e: 31 raise ImproperlyConfigured(('Error importing email backend %s: "%s"' 32 % (path, e))) 33 try: 34 cls = getattr(mod, 'EmailBackend') 35 except AttributeError: 36 raise ImproperlyConfigured(('Module "%s" does not define a ' 37 '"EmailBackend" class' % path)) 38 return cls(fail_silently=fail_silently, **kwds) 39 40 41 def send_mail(subject, message, from_email, recipient_list, 42 fail_silently=False, auth_user=None, auth_password=None): 43 """ 44 Easy wrapper for sending a single message to a recipient list. All members 45 of the recipient list will see the other recipients in the 'To' field. 46 47 If auth_user is None, the EMAIL_HOST_USER setting is used. 48 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. 49 50 Note: The API for this method is frozen. New code wanting to extend the 51 functionality should use the EmailMessage class directly. 52 """ 53 connection = get_connection(username=auth_user, password=auth_password, 54 fail_silently=fail_silently) 55 return EmailMessage(subject, message, from_email, recipient_list, 56 connection=connection).send() 57 58 59 def send_mass_mail(datatuple, fail_silently=False, auth_user=None, 60 auth_password=None): 61 """ 62 Given a datatuple of (subject, message, from_email, recipient_list), sends 63 each message to each recipient list. Returns the number of e-mails sent. 64 65 If from_email is None, the DEFAULT_FROM_EMAIL setting is used. 66 If auth_user and auth_password are set, they're used to log in. 67 If auth_user is None, the EMAIL_HOST_USER setting is used. 68 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. 69 70 Note: The API for this method is frozen. New code wanting to extend the 71 functionality should use the EmailMessage class directly. 72 """ 73 connection = get_connection(username=auth_user, password=auth_password, 74 fail_silently=fail_silently) 75 messages = [EmailMessage(subject, message, sender, recipient) 76 for subject, message, sender, recipient in datatuple] 77 return connection.send_messages(messages) 78 79 80 def mail_admins(subject, message, fail_silently=False): 81 """Sends a message to the admins, as defined by the ADMINS setting.""" 82 if not settings.ADMINS: 83 return 84 EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, 85 settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS] 86 ).send(fail_silently=fail_silently) 87 88 89 def mail_managers(subject, message, fail_silently=False): 90 """Sends a message to the managers, as defined by the MANAGERS setting.""" 91 if not settings.MANAGERS: 92 return 93 EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, 94 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS] 95 ).send(fail_silently=fail_silently) 96 97 98 class SMTPConnection(_SMTPConnection): 99 100 def __init__(self, *args, **kwds): 101 import warnings 102 warnings.warn( 103 'mail.SMTPConnection is deprectated; use mail.get_connection() instead.', 104 DeprecationWarning 105 ) 106 super(SMTPConnection, self).__init__(*args, **kwds) -
new file django/core/mail/backends/__init__.py
diff -r 1b4df7524b7c django/core/mail/backends/__init__.py
- + 1 # Mail backends shipped with Django. -
new file django/core/mail/backends/base.py
diff -r 1b4df7524b7c django/core/mail/backends/base.py
- + 1 """Base email backend class.""" 2 3 4 class BaseEmailBackend(object): 5 """ 6 Base class for email backend implementations. 7 8 Subclasses must at least overwrite send_messages(). 9 """ 10 11 def __init__(self, fail_silently=False, **kwds): 12 self.fail_silently = fail_silently 13 14 def open(self): 15 """Open a network connection. 16 17 This method can be overwritten by backend implementations to 18 open a network connection. 19 20 It's up to the backend implementation to track the status of 21 a network connection if it's needed by the backend. 22 23 This method can be called by applications to force a single 24 network connection to be used when sending mails. See the 25 send_messages() method of the SMTP backend for a reference 26 implementation. 27 28 The default implementation does nothing. 29 """ 30 pass 31 32 def close(self): 33 """Close a network connection.""" 34 pass 35 36 def send_messages(self, email_messages): 37 """ 38 Sends one or more EmailMessage objects and returns the number of email 39 messages sent. 40 """ 41 raise NotImplementedError -
new file django/core/mail/backends/console.py
diff -r 1b4df7524b7c django/core/mail/backends/console.py
- + 1 """ 2 Email backend that writes messages to console instead of sending them. 3 """ 4 5 import sys 6 7 from django.core.mail.backends.base import BaseEmailBackend 8 9 10 class EmailBackend(BaseEmailBackend): 11 12 def send_messages(self, email_messages): 13 for message in email_messages: 14 sys.stdout.write('%s\n' % message.message().as_string()) 15 return len(email_messages) -
new file django/core/mail/backends/dummy.py
diff -r 1b4df7524b7c django/core/mail/backends/dummy.py
- + 1 """ 2 Dummy email backend that does nothing. 3 """ 4 5 6 from django.core.mail.backends.base import BaseEmailBackend 7 8 9 class EmailBackend(BaseEmailBackend): 10 11 def send_messages(self, email_messages): 12 return len(email_messages) -
new file django/core/mail/backends/file.py
diff -r 1b4df7524b7c django/core/mail/backends/file.py
- + 1 """Email backend that writes messages to a file.""" 2 3 import os 4 import sys 5 import thread 6 import threading 7 import time 8 9 try: 10 from hashlib import md5 11 except ImportError: 12 from md5 import md5 13 14 from django.conf import settings 15 from django.core.exceptions import ImproperlyConfigured 16 from django.core.mail.backends.base import BaseEmailBackend 17 18 19 class EmailBackend(BaseEmailBackend): 20 21 def __init__(self, *args, **kwds): 22 self.stream = None 23 self._lock = threading.RLock() 24 self._counter = 0 25 if 'file_path' in kwds: 26 self.outdir = kwds.pop('file_path') 27 else: 28 self.outdir = settings.EMAIL_FILE_PATH 29 # Make sure self.outdir is a string. 30 if not isinstance(self.outdir, basestring): 31 raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.outdir) 32 self.outdir = os.path.abspath(self.outdir) 33 # Make sure that self.outdir is an directory if it exists. 34 if os.path.exists(self.outdir) and not os.path.isdir(self.outdir): 35 raise ImproperlyConfigured('Path for saving email messages exists, but is not a directory: %s' % self.outdir) 36 # Try to create it, if it not exists. 37 elif not os.path.exists(self.outdir): 38 try: 39 os.makedirs(self.outdir) 40 except OSError, err: 41 raise ImproperlyConfigured('Could not create directory for saving email messages: %s (%s)' % (self.outdir, err)) 42 # Make sure that self.outdir is writable. 43 if not os.access(self.outdir, os.W_OK): 44 raise ImproperlyConfigured('Could not write to directory: %s' % self.outdir) 45 # Finally, call super(). 46 super(EmailBackend, self).__init__(*args, **kwds) 47 48 def _count(self): 49 # counter is required to build a unique filename if connections 50 # are opened in a really short time. 51 if self._counter == sys.maxint-1: 52 self._counter = 0 53 self._counter += 1 54 return self._counter 55 56 def _get_filename(self): 57 """Return a unique file name.""" 58 digest = md5('%d%d%s%d' % (id(self), thread.get_ident(), 59 time.time(), self._count()) 60 ).hexdigest() 61 return os.path.join(self.outdir, '%s-messages.log' % digest) 62 63 def open(self): 64 if self.stream is None: 65 self.stream = open(self._get_filename(), 'a') 66 return True 67 return False 68 69 def close(self): 70 try: 71 if self.stream is not None: 72 self.stream.close() 73 finally: 74 self.stream = None 75 76 def send_messages(self, email_messages): 77 """Write all messages to the file in a thread-safe way.""" 78 if not email_messages: 79 return 80 self._lock.acquire() 81 try: 82 stream_created = self.open() 83 for message in email_messages: 84 self.stream.write('%s\n' % message.message().as_string()) 85 self.stream.write('-'*79) 86 self.stream.write('\n') 87 self.stream.flush() # flush after each message 88 if stream_created: 89 self.close() 90 except: 91 if not self.fail_silently: 92 raise 93 finally: 94 self._lock.release() 95 return len(email_messages) -
new file django/core/mail/backends/locmem.py
diff -r 1b4df7524b7c django/core/mail/backends/locmem.py
- + 1 """ 2 Backend for test environment. 3 """ 4 5 from django.core import mail 6 from django.core.mail.backends.base import BaseEmailBackend 7 8 9 class EmailBackend(BaseEmailBackend): 10 """A email backend for use during test sessions. 11 12 The test connection stores email messages in a dummy outbox, 13 rather than sending them out on the wire. 14 15 The dummy outbox is accessible through the outbox instance attribute. 16 """ 17 18 def __init__(self, *args, **kwds): 19 super(EmailBackend, self).__init__(*args, **kwds) 20 self.outbox = [] 21 22 def send_messages(self, messages): 23 """Redirect messages to the dummy outbox""" 24 self.outbox.extend(messages) 25 if hasattr(mail, 'outbox'): 26 mail.outbox.extend(messages) 27 return len(messages) -
new file django/core/mail/backends/smtp.py
diff -r 1b4df7524b7c django/core/mail/backends/smtp.py
- + 1 """SMTP email backend class.""" 2 3 import smtplib 4 import socket 5 import threading 6 7 from django.conf import settings 8 from django.core.mail.backends.base import BaseEmailBackend 9 from django.core.mail.utils import DNS_NAME 10 11 12 class EmailBackend(BaseEmailBackend): 13 """ 14 A wrapper that manages the SMTP network connection. 15 """ 16 17 def __init__(self, host=None, port=None, username=None, password=None, 18 use_tls=None, fail_silently=False, **kwds): 19 super(EmailBackend, self).__init__(fail_silently=fail_silently) 20 self.host = host or settings.EMAIL_HOST 21 self.port = port or settings.EMAIL_PORT 22 self.username = username or settings.EMAIL_HOST_USER 23 self.password = password or settings.EMAIL_HOST_PASSWORD 24 self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS 25 self.connection = None 26 self._lock = threading.RLock() 27 28 def open(self): 29 """ 30 Ensures we have a connection to the email server. Returns whether or 31 not a new connection was required (True or False). 32 """ 33 if self.connection: 34 # Nothing to do if the connection is already open. 35 return False 36 try: 37 # If local_hostname is not specified, socket.getfqdn() gets used. 38 # For performance, we use the cached FQDN for local_hostname. 39 self.connection = smtplib.SMTP(self.host, self.port, 40 local_hostname=DNS_NAME.get_fqdn()) 41 if self.use_tls: 42 self.connection.ehlo() 43 self.connection.starttls() 44 self.connection.ehlo() 45 if self.username and self.password: 46 self.connection.login(self.username, self.password) 47 return True 48 except: 49 if not self.fail_silently: 50 raise 51 52 def close(self): 53 """Closes the connection to the email server.""" 54 try: 55 try: 56 self.connection.quit() 57 except socket.sslerror: 58 # This happens when calling quit() on a TLS connection 59 # sometimes. 60 self.connection.close() 61 except: 62 if self.fail_silently: 63 return 64 raise 65 finally: 66 self.connection = None 67 68 def send_messages(self, email_messages): 69 """ 70 Sends one or more EmailMessage objects and returns the number of email 71 messages sent. 72 """ 73 if not email_messages: 74 return 75 self._lock.acquire() 76 try: 77 new_conn_created = self.open() 78 if not self.connection: 79 # We failed silently on open(). 80 # Trying to send would be pointless. 81 return 82 num_sent = 0 83 for message in email_messages: 84 sent = self._send(message) 85 if sent: 86 num_sent += 1 87 if new_conn_created: 88 self.close() 89 finally: 90 self._lock.release() 91 return num_sent 92 93 def _send(self, email_message): 94 """A helper method that does the actual sending.""" 95 if not email_message.recipients(): 96 return False 97 try: 98 self.connection.sendmail(email_message.from_email, 99 email_message.recipients(), 100 email_message.message().as_string()) 101 except: 102 if not self.fail_silently: 103 raise 104 return False 105 return True -
new file django/core/mail/message.py
diff -r 1b4df7524b7c django/core/mail/message.py
- + 1 import mimetypes 2 import os 3 import random 4 import time 5 from email import Charset, Encoders 6 from email.MIMEText import MIMEText 7 from email.MIMEMultipart import MIMEMultipart 8 from email.MIMEBase import MIMEBase 9 from email.Header import Header 10 from email.Utils import formatdate, parseaddr, formataddr 11 12 from django.conf import settings 13 from django.core.mail.utils import DNS_NAME 14 from django.utils.encoding import smart_str, force_unicode 15 16 # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from 17 # some spam filters. 18 Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8') 19 20 # Default MIME type to use on attachments (if it is not explicitly given 21 # and cannot be guessed). 22 DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream' 23 24 25 class BadHeaderError(ValueError): 26 pass 27 28 29 # Copied from Python standard library, with the following modifications: 30 # * Used cached hostname for performance. 31 # * Added try/except to support lack of getpid() in Jython (#5496). 32 def make_msgid(idstring=None): 33 """Returns a string suitable for RFC 2822 compliant Message-ID, e.g: 34 35 <20020201195627.33539.96671@nightshade.la.mastaler.com> 36 37 Optional idstring if given is a string used to strengthen the 38 uniqueness of the message id. 39 """ 40 timeval = time.time() 41 utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval)) 42 try: 43 pid = os.getpid() 44 except AttributeError: 45 # No getpid() in Jython, for example. 46 pid = 1 47 randint = random.randrange(100000) 48 if idstring is None: 49 idstring = '' 50 else: 51 idstring = '.' + idstring 52 idhost = DNS_NAME 53 msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost) 54 return msgid 55 56 57 def forbid_multi_line_headers(name, val): 58 """Forbids multi-line headers, to prevent header injection.""" 59 val = force_unicode(val) 60 if '\n' in val or '\r' in val: 61 raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) 62 try: 63 val = val.encode('ascii') 64 except UnicodeEncodeError: 65 if name.lower() in ('to', 'from', 'cc'): 66 result = [] 67 for item in val.split(', '): 68 nm, addr = parseaddr(item) 69 nm = str(Header(nm, settings.DEFAULT_CHARSET)) 70 result.append(formataddr((nm, str(addr)))) 71 val = ', '.join(result) 72 else: 73 val = Header(val, settings.DEFAULT_CHARSET) 74 else: 75 if name.lower() == 'subject': 76 val = Header(val) 77 return name, val 78 79 80 class SafeMIMEText(MIMEText): 81 def __setitem__(self, name, val): 82 name, val = forbid_multi_line_headers(name, val) 83 MIMEText.__setitem__(self, name, val) 84 85 86 class SafeMIMEMultipart(MIMEMultipart): 87 def __setitem__(self, name, val): 88 name, val = forbid_multi_line_headers(name, val) 89 MIMEMultipart.__setitem__(self, name, val) 90 91 92 class EmailMessage(object): 93 """ 94 A container for email information. 95 """ 96 content_subtype = 'plain' 97 mixed_subtype = 'mixed' 98 encoding = None # None => use settings default 99 100 def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, 101 connection=None, attachments=None, headers=None): 102 """ 103 Initialize a single email message (which can be sent to multiple 104 recipients). 105 106 All strings used to create the message can be unicode strings 107 (or UTF-8 bytestrings). The SafeMIMEText class will handle any 108 necessary encoding conversions. 109 """ 110 if to: 111 assert not isinstance(to, basestring), '"to" argument must be a list or tuple' 112 self.to = list(to) 113 else: 114 self.to = [] 115 if bcc: 116 assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple' 117 self.bcc = list(bcc) 118 else: 119 self.bcc = [] 120 self.from_email = from_email or settings.DEFAULT_FROM_EMAIL 121 self.subject = subject 122 self.body = body 123 self.attachments = attachments or [] 124 self.extra_headers = headers or {} 125 self.connection = connection 126 127 def get_connection(self, fail_silently=False): 128 from django.core.mail import get_connection 129 if not self.connection: 130 self.connection = get_connection(fail_silently=fail_silently) 131 return self.connection 132 133 def message(self): 134 encoding = self.encoding or settings.DEFAULT_CHARSET 135 msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET), 136 self.content_subtype, encoding) 137 msg = self._create_message(msg) 138 msg['Subject'] = self.subject 139 msg['From'] = self.extra_headers.pop('From', self.from_email) 140 msg['To'] = ', '.join(self.to) 141 142 # Email header names are case-insensitive (RFC 2045), so we have to 143 # accommodate that when doing comparisons. 144 header_names = [key.lower() for key in self.extra_headers] 145 if 'date' not in header_names: 146 msg['Date'] = formatdate() 147 if 'message-id' not in header_names: 148 msg['Message-ID'] = make_msgid() 149 for name, value in self.extra_headers.items(): 150 msg[name] = value 151 return msg 152 153 def recipients(self): 154 """ 155 Returns a list of all recipients of the email (includes direct 156 addressees as well as Bcc entries). 157 """ 158 return self.to + self.bcc 159 160 def send(self, fail_silently=False): 161 """Sends the email message.""" 162 if not self.recipients(): 163 # Don't bother creating the network connection if there's nobody to 164 # send to. 165 return 0 166 return self.get_connection(fail_silently).send_messages([self]) 167 168 def attach(self, filename=None, content=None, mimetype=None): 169 """ 170 Attaches a file with the given filename and content. The filename can 171 be omitted and the mimetype is guessed, if not provided. 172 173 If the first parameter is a MIMEBase subclass it is inserted directly 174 into the resulting message attachments. 175 """ 176 if isinstance(filename, MIMEBase): 177 assert content == mimetype == None 178 self.attachments.append(filename) 179 else: 180 assert content is not None 181 self.attachments.append((filename, content, mimetype)) 182 183 def attach_file(self, path, mimetype=None): 184 """Attaches a file from the filesystem.""" 185 filename = os.path.basename(path) 186 content = open(path, 'rb').read() 187 self.attach(filename, content, mimetype) 188 189 def _create_message(self, msg): 190 return self._create_attachments(msg) 191 192 def _create_attachments(self, msg): 193 if self.attachments: 194 body_msg = msg 195 msg = SafeMIMEMultipart(_subtype=self.mixed_subtype) 196 if self.body: 197 msg.attach(body_msg) 198 for attachment in self.attachments: 199 if isinstance(attachment, MIMEBase): 200 msg.attach(attachment) 201 else: 202 msg.attach(self._create_attachment(*attachment)) 203 return msg 204 205 def _create_mime_attachment(self, content, mimetype): 206 """ 207 Converts the content, mimetype pair into a MIME attachment object. 208 """ 209 basetype, subtype = mimetype.split('/', 1) 210 if basetype == 'text': 211 attachment = SafeMIMEText(smart_str(content, 212 settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET) 213 else: 214 # Encode non-text attachments with base64. 215 attachment = MIMEBase(basetype, subtype) 216 attachment.set_payload(content) 217 Encoders.encode_base64(attachment) 218 return attachment 219 220 def _create_attachment(self, filename, content, mimetype=None): 221 """ 222 Converts the filename, content, mimetype triple into a MIME attachment 223 object. 224 """ 225 if mimetype is None: 226 mimetype, _ = mimetypes.guess_type(filename) 227 if mimetype is None: 228 mimetype = DEFAULT_ATTACHMENT_MIME_TYPE 229 attachment = self._create_mime_attachment(content, mimetype) 230 if filename: 231 attachment.add_header('Content-Disposition', 'attachment', 232 filename=filename) 233 return attachment 234 235 236 class EmailMultiAlternatives(EmailMessage): 237 """ 238 A version of EmailMessage that makes it easy to send multipart/alternative 239 messages. For example, including text and HTML versions of the text is 240 made easier. 241 """ 242 alternative_subtype = 'alternative' 243 244 def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, 245 connection=None, attachments=None, headers=None, alternatives=None): 246 """ 247 Initialize a single email message (which can be sent to multiple 248 recipients). 249 250 All strings used to create the message can be unicode strings (or UTF-8 251 bytestrings). The SafeMIMEText class will handle any necessary encoding 252 conversions. 253 """ 254 super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers) 255 self.alternatives=alternatives or [] 256 257 def attach_alternative(self, content, mimetype): 258 """Attach an alternative content representation.""" 259 assert content is not None 260 assert mimetype is not None 261 self.alternatives.append((content, mimetype)) 262 263 def _create_message(self, msg): 264 return self._create_attachments(self._create_alternatives(msg)) 265 266 def _create_alternatives(self, msg): 267 if self.alternatives: 268 body_msg = msg 269 msg = SafeMIMEMultipart(_subtype=self.alternative_subtype) 270 if self.body: 271 msg.attach(body_msg) 272 for alternative in self.alternatives: 273 msg.attach(self._create_mime_attachment(*alternative)) 274 return msg -
new file django/core/mail/utils.py
diff -r 1b4df7524b7c django/core/mail/utils.py
- + 1 """ 2 Email message and email sending related helper functions. 3 """ 4 5 import socket 6 7 8 # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of 9 # seconds, which slows down the restart of the server. 10 class CachedDnsName(object): 11 def __str__(self): 12 return self.get_fqdn() 13 14 def get_fqdn(self): 15 if not hasattr(self, '_fqdn'): 16 self._fqdn = socket.getfqdn() 17 return self._fqdn 18 19 DNS_NAME = CachedDnsName() -
django/test/utils.py
diff -r 1b4df7524b7c django/test/utils.py
a b 28 28 signals.template_rendered.send(sender=self, template=self, context=context) 29 29 return self.nodelist.render(context) 30 30 31 class TestSMTPConnection(object):32 """A substitute SMTP connection for use during test sessions.33 The test connection stores email messages in a dummy outbox,34 rather than sending them out on the wire.35 36 """37 def __init__(*args, **kwargs):38 pass39 def open(self):40 "Mock the SMTPConnection open() interface"41 pass42 def close(self):43 "Mock the SMTPConnection close() interface"44 pass45 def send_messages(self, messages):46 "Redirect messages to the dummy outbox"47 mail.outbox.extend(messages)48 return len(messages)49 31 50 32 def setup_test_environment(): 51 33 """Perform any global pre-test setup. This involves: 52 34 53 35 - Installing the instrumented test renderer 54 - Diverting the email sending functions to a test buffer36 - Replacing the email backend with the email backend used for testing. 55 37 - Setting the active locale to match the LANGUAGE_CODE setting. 56 38 """ 39 from django.core.mail.backends.locmem import EmailBackend 57 40 Template.original_render = Template.render 58 41 Template.render = instrumented_test_render 59 42 60 mail.original_SMTPConnection = mail.SMTPConnection 61 mail.SMTPConnection = TestSMTPConnection 43 mail.original_email_backend = settings.EMAIL_BACKEND 44 settings.EMAIL_BACKEND = 'locmem' 45 mail.original_smtp_connection = mail.SMTPConnection 46 mail.SMTPConnection = lambda *a, **k: mail.get_connection() 62 47 63 48 mail.outbox = [] 64 49 … … 74 59 Template.render = Template.original_render 75 60 del Template.original_render 76 61 77 mail.SMTPConnection = mail.original_SMTPConnection 78 del mail.original_SMTPConnection 62 settings.EMAIL_BACKEND = mail.original_email_backend 63 del mail.original_email_backend 64 mail.SMTPConnection = mail.original_smtp_connection 65 del mail.original_smtp_connection 79 66 80 67 del mail.outbox 81 68 -
docs/ref/settings.txt
diff -r 1b4df7524b7c docs/ref/settings.txt
a b 379 379 This is only used if ``CommonMiddleware`` is installed (see 380 380 :ref:`topics-http-middleware`). 381 381 382 .. setting:: EMAIL_BACKEND 383 384 EMAIL_BACKEND 385 ------------- 386 387 .. versionadded:: 1.2 388 389 Default: ``'smtp'`` 390 391 The backend to use for sending emails. For available backend see 392 :ref:`topics-email`. 393 382 394 .. setting:: EMAIL_HOST 383 395 384 396 EMAIL_HOST -
docs/topics/email.txt
diff -r 1b4df7524b7c docs/topics/email.txt
a b 178 178 179 179 .. _emailmessage-and-smtpconnection: 180 180 181 The EmailMessage and SMTPConnection classes182 ====================== =====================181 The EmailMessage class 182 ====================== 183 183 184 184 .. versionadded:: 1.0 185 185 186 186 Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin 187 wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes 188 in ``django.core.mail``. If you ever need to customize the way Django sends 189 e-mail, you can subclass these two classes to suit your needs. 187 wrappers that make use of the ``EmailMessage`` class in ``django.core.mail``. 188 189 Not all features of the ``EmailMessage`` class are available through the 190 ``send_mail()`` and related wrapper functions. If you wish to use advanced 191 features, such as BCC'ed recipients, file attachments, or multi-part 192 e-mail, you'll need to create ``EmailMessage`` instances directly. 190 193 191 194 .. note:: 192 Not all features of the ``EmailMessage`` class are available through the193 ``send_mail()`` and related wrapper functions. If you wish to use advanced194 features, such as BCC'ed recipients, file attachments, or multi-part195 e-mail, you'll need to create ``EmailMessage`` instances directly.196 195 197 196 This is a design feature. ``send_mail()`` and related functions were 198 197 originally the only interface Django provided. However, the list of … … 201 200 original functions only for backwards compatibility. 202 201 203 202 In general, ``EmailMessage`` is responsible for creating the e-mail message 204 itself. ``SMTPConnection`` is responsible for the network connection side of 205 the operation. This means you can reuse the same connection (an 206 ``SMTPConnection`` instance) for multiple messages. 203 itself. The email backend is responsible for sending the email. By default 204 the SMTP backend is used to send emails using a SMTP server. 205 206 If you're sending lots of messages at once, the ``send_messages()`` method of 207 the email backend is useful. It takes a list of ``EmailMessage`` 208 instances (or subclasses) and sends them over a single connection. For example, 209 if you have a function called ``get_notification_email()`` that returns a 210 list of ``EmailMessage`` objects representing some periodic e-mail you wish to 211 send out, you could send this with:: 212 213 connection = SMTPConnection() # Use default settings for connection 214 messages = get_notification_email() 215 connection.send_messages(messages) 216 217 If you want to resuse the same connection for multiple messages, but can't 218 use ``send_messages()``, you'll have to use the ``open()`` and ``close()`` 219 methods of the email backend:: 220 221 from django.core import mail 222 223 connection = mail.get_connection() 224 connection.open() 225 email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', 226 ['to1@example.com'], connection=connection) 227 email1.send() 228 email2 = mail.EmailMessage('Hello again', 'Body goes here', 229 'from@example.com', ['to1@example.com'], 230 connection=connection) 231 email2.send() 232 connection.close() 233 207 234 208 235 EmailMessage Objects 209 236 -------------------- … … 227 254 * ``bcc``: A list or tuple of addresses used in the "Bcc" header when 228 255 sending the e-mail. 229 256 230 * ``connection``: An ``SMTPConnection``instance. Use this parameter if257 * ``connection``: An email backend instance. Use this parameter if 231 258 you want to use the same connection for multiple messages. If omitted, a 232 259 new connection is created when ``send()`` is called. 233 260 … … 331 358 msg.content_subtype = "html" # Main content is now text/html 332 359 msg.send() 333 360 334 SMTPConnection Objects 335 ---------------------- 361 362 Email Backends 363 ============== 364 365 .. versionadded:: 1.2 366 367 The actual sending of an email is handled by the email backend. 368 By default the SMTP backend will be used. 369 370 Your email backend preferences goes in the ``EMAIL_BACKEND`` setting in 371 your settings file. Here's an explanation of all available values for 372 ``EMAIL_BACKEND``. 373 374 SMTP backend 375 ------------ 376 377 This is the default backend. Email will be sent through a SMTP server. 378 The server address and authentication credentials are set in the 379 :setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`, 380 :setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your 381 settings file. 382 383 The SMTP backend is configured in the default settings:: 384 385 EMAIL_BACKEND = 'smtp' 386 387 388 SMTPConnection objects 389 ~~~~~~~~~~~~~~~~~~~~~~ 336 390 337 391 .. class:: SMTPConnection 338 392 393 .. note:: 394 395 ``mail.SMTPConnection`` is deprecated, use ``mail.get_connection`` instead. 396 For backwards compatibility ``SMTPConnection`` is still available in 397 ``django.core.mail`` as an alias for the SMTP backend. 398 339 399 The ``SMTPConnection`` class is initialized with the host, port, username and 340 400 password for the SMTP server. If you don't specify one or more of those 341 401 options, they are read from your settings file. 342 402 343 If you're sending lots of messages at once, the ``send_messages()`` method of 344 the ``SMTPConnection`` class is useful. It takes a list of ``EmailMessage`` 345 instances (or subclasses) and sends them over a single connection. For example, 346 if you have a function called ``get_notification_email()`` that returns a 347 list of ``EmailMessage`` objects representing some periodic e-mail you wish to 348 send out, you could send this with:: 403 Console backend (for development) 404 --------------------------------- 349 405 350 connection = SMTPConnection() # Use default settings for connection 351 messages = get_notification_email() 352 connection.send_messages(messages) 406 Instead of sending out real emails the console backend just writes the emails 407 that would be send to the standard output. To use this backend, use 408 in your settings:: 353 409 354 Testing e-mail sending 355 ---------------------- 410 EMAIL_BACKEND = 'console' 411 412 File backend (for development) 413 ------------------------------ 414 415 The file backend writes emails to a file. A new file is created for each 416 new instance of this backend. The directory to which the files are written 417 is either taken from the :setting:`EMAIL_FILE_PATH` setting or from the 418 ``file_path`` keyword when creating a connection with ``get_connection()``. 419 To use this backend, use in your settings:: 420 421 EMAIL_BACKEND = 'file' 422 EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location 423 424 Dummy backend (for development) 425 ------------------------------- 426 427 As the name suggests the dummy backend does nothing with your messages. 428 To enable this backend, use in your settings file:: 429 430 EMAIL_BACKEND = 'dummy' 431 432 This backend can be useful during development, if you even want to suppress 433 the output produced by the console backend. 434 435 Using a custom email backend 436 ---------------------------- 437 438 If you need to change how emails are send you can write your own email 439 backend. The ``EMAIL_BACKEND`` setting in your settings file is then the 440 Python import path for your backend. 441 442 Custom email backends should subclass ``BaseEmailBackend`` that is located 443 in the ``django.core.mail.backends.base`` module. A custom email 444 backend must implement the ``send_messages(email_messages)`` method. This 445 method receives a list of ``EMailMessage`` instances and returns the number 446 of successfully delivered messages. If your backend should be capable of 447 network connection handling, you should implement both the ``open()`` 448 and ``close()`` methods too. Refer to ``SMTPEmailBackend`` for a reference 449 implementation. 450 451 Testing Email Sending 452 ===================== 356 453 357 454 The are times when you do not want Django to send e-mails at all. For example, 358 455 while developing a website, you probably don't want to send out thousands of … … 360 457 people under the right conditions, and that those e-mails will contain the 361 458 correct content. 362 459 363 The easiest way to test your project's use of e-mail is to use a "dumb" e-mail 364 server that receives the e-mails locally and displays them to the terminal, 365 but does not actually send anything. Python has a built-in way to accomplish 366 this with a single command:: 460 The easiest way to test your project's use of e-mail is to change the 461 ``EMAIL_BACKEND`` setting in your settings file to ``'console'`` to use 462 a backend that doesn't send any email but writes them to the 463 terminal. 464 465 Alternatively use a "dumb" e-mail server that receives the e-mails locally 466 and displays them to the terminal, but does not actually send anything. 467 Python has a built-in way to accomplish this with a single command:: 367 468 368 469 python -m smtpd -n -c DebuggingServer localhost:1025 369 470 -
docs/topics/testing.txt
diff -r 1b4df7524b7c docs/topics/testing.txt
a b 1117 1117 contents of each message -- without actually sending the messages. 1118 1118 1119 1119 The test runner accomplishes this by transparently replacing the normal 1120 :class:`~django.core.mail.SMTPConnection` class with a different version.1120 email backend with a testing backend. 1121 1121 (Don't worry -- this has no effect on any other e-mail senders outside of 1122 1122 Django, such as your machine's mail server, if you're running one.) 1123 1123 … … 1129 1129 ``django.core.mail.outbox``. This is a simple list of all 1130 1130 :class:`<~django.core.mail.EmailMessage>` instances that have been sent. 1131 1131 It does not exist under normal execution conditions, i.e., when you're not 1132 running unit tests. The outbox is created during test setup, along with the 1133 dummy :class:`<~django.core.mail.SMTPConnection>`. When the test framework is 1134 torn down, the standard :class:`<~django.core.mail.SMTPConnection>` class is 1132 running unit tests. The outbox is created during test setup. 1133 When the test framework is torn down, the standard mail backend 1135 1134 restored, and the test outbox is destroyed. 1136 1135 1137 1136 The ``outbox`` attribute is a special attribute that is created *only* when -
new file tests/regressiontests/mail/custombackend.py
diff -r 1b4df7524b7c tests/regressiontests/mail/custombackend.py
- + 1 """A custom backend for testing.""" 2 3 from django.core.mail.backends.base import BaseEmailBackend 4 5 6 class EmailBackend(BaseEmailBackend): 7 8 def send_messages(self, email_messages): 9 return len(email_messages) -
tests/regressiontests/mail/tests.py
diff -r 1b4df7524b7c tests/regressiontests/mail/tests.py
a b 1 1 # coding: utf-8 2 2 3 r""" 3 4 # Tests for the django.core.mail. 4 5 6 >>> import os 7 >>> import shutil 8 >>> import tempfile 5 9 >>> from django.conf import settings 6 10 >>> from django.core import mail 7 11 >>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives 12 >>> from django.core.mail.backends.base import BaseEmailBackend 13 >>> from django.core.mail.backends import console, dummy, locmem, file 8 14 >>> from django.utils.translation import ugettext_lazy 9 15 10 16 # Test normal ascii character case: … … 138 144 JVBERi0xLjQuJS4uLg== 139 145 ... 140 146 147 # Make sure that the console backend writes to stdout 148 >>> connection = console.EmailBackend() 149 >>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) 150 >>> connection.send_messages([email]) 151 Content-Type: text/plain; charset="utf-8" 152 MIME-Version: 1.0 153 Content-Transfer-Encoding: quoted-printable 154 Subject: Subject 155 From: from@example.com 156 To: to@example.com 157 Date: ... 158 Message-ID: ... 159 160 Content 161 ... 162 1 163 164 # Make sure that dummy backends returns correct number of sent messages 165 >>> connection = dummy.EmailBackend() 166 >>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) 167 >>> connection.send_messages([email, email, email]) 168 3 169 170 # Make sure that locmen backend populates the outbox 171 >>> mail.outbox = [] 172 >>> connection = locmem.EmailBackend() 173 >>> email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) 174 >>> email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) 175 >>> connection.send_messages([email1, email2]) 176 2 177 >>> len(mail.outbox) 178 2 179 >>> mail.outbox[0].subject 180 'Subject' 181 >>> mail.outbox[1].subject 182 'Subject 2' 183 184 # Make sure that multiple locmem connections share mail.outbox 185 >>> mail.outbox = [] 186 >>> connection1 = locmem.EmailBackend() 187 >>> connection2 = locmem.EmailBackend() 188 >>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) 189 >>> connection1.send_messages([email]) 190 1 191 >>> connection2.send_messages([email]) 192 1 193 >>> len(connection1.outbox) 194 1 195 >>> len(connection2.outbox) 196 1 197 >>> len(mail.outbox) 198 2 199 200 # Make sure that the file backend write to the right location 201 >>> tmp_dir = tempfile.mkdtemp() 202 >>> shutil.rmtree(tmp_dir) 203 >>> connection = file.EmailBackend(file_path=tmp_dir) 204 >>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) 205 >>> connection.send_messages([email]) 206 1 207 >>> len(os.listdir(tmp_dir)) 208 1 209 >>> print open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0])).read() 210 Content-Type: text/plain; charset="utf-8" 211 MIME-Version: 1.0 212 Content-Transfer-Encoding: quoted-printable 213 Subject: Subject 214 From: from@example.com 215 To: to@example.com 216 Date: ... 217 Message-ID: ... 218 219 Content 220 ... 221 >>> connection2 = file.EmailBackend(file_path=tmp_dir) 222 >>> connection2.send_messages([email]) 223 1 224 >>> len(os.listdir(tmp_dir)) 225 2 226 >>> connection.send_messages([email]) 227 1 228 >>> len(os.listdir(tmp_dir)) 229 3 230 >>> email.connection = connection 231 >>> connection_created = connection.open() 232 >>> num_sent = email.send() 233 >>> len(os.listdir(tmp_dir)) 234 4 235 >>> num_sent = email.send() 236 >>> len(os.listdir(tmp_dir)) 237 4 238 >>> connection.close() 239 >>> shutil.rmtree(tmp_dir) 240 241 # Make sure that get_connection() accepts arbitrary keyword that might be 242 # used with custom backends. 243 >>> c = mail.get_connection(fail_silently=True, foo='bar') 244 >>> c.fail_silently 245 True 246 247 # Test custom backend defined in this suite. 248 >>> settings.EMAIL_BACKEND = 'regressiontests.mail.custombackend' 249 >>> conn = mail.get_connection() 250 >>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) 251 >>> conn.send_messages([email]) 252 1 253 >>> settings.EMAIL_BACKEND = 'locmem' 141 254 """