Ticket #10355: t10355-r11706.diff
File t10355-r11706.diff, 72.1 KB (added by , 15 years ago) |
---|
-
django/conf/global_settings.py
diff -r 230a12f723d8 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 can be specified by providing a Python path 138 # to a module that defines an EmailBackend class. 139 EMAIL_BACKEND = 'smtp' 140 134 141 # Host for sending e-mail. 135 142 EMAIL_HOST = 'localhost' 136 143 -
deleted file django/core/mail.py
diff -r 230a12f723d8 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 230a12f723d8 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.utils.importlib import import_module 8 9 # Imported for backwards compatibility, and for the sake 10 # of a cleaner namespace. These symbols used to be in 11 # django/core/mail.py before the introduction of email 12 # backends and the subsequent reorganization (See #10355) 13 from django.core.mail.utils import CachedDnsName, DNS_NAME 14 from django.core.mail.message import \ 15 EmailMessage, EmailMultiAlternatives, \ 16 SafeMIMEText, SafeMIMEMultipart, \ 17 DEFAULT_ATTACHMENT_MIME_TYPE, make_msgid, \ 18 BadHeaderError, forbid_multi_line_headers 19 from django.core.mail.backends.smtp import EmailBackend as _SMTPConnection 20 21 # Shortcuts for builtin email backends. The dictionary maps backend names 22 # to module names. If settings.EMAIL_BACKEND isn't in this dictionary 23 # it's treated as a Python import path. 24 BACKENDS = { 25 'console': 'console', 26 'dummy': 'dummy', 27 'file': 'filebased', 28 'locmem': 'locmem', 29 'smtp': 'smtp', 30 } 31 32 def get_connection(backend=None, fail_silently=False, **kwds): 33 """Load an e-mail backend and return an instance of it. 34 35 If backend is None (default) settings.EMAIL_BACKEND is used. 36 37 Both fail_silently and other keyword arguments are used in the 38 constructor of the backend. 39 """ 40 path = backend or settings.EMAIL_BACKEND 41 if path in BACKENDS: 42 path = 'django.core.mail.backends.%s' % BACKENDS.get(path) 43 try: 44 mod = import_module(path) 45 except ImportError, e: 46 raise ImproperlyConfigured(('Error importing email backend %s: "%s"' 47 % (path, e))) 48 try: 49 cls = getattr(mod, 'EmailBackend') 50 except AttributeError: 51 raise ImproperlyConfigured(('Module "%s" does not define a ' 52 '"EmailBackend" class' % path)) 53 return cls(fail_silently=fail_silently, **kwds) 54 55 56 def send_mail(subject, message, from_email, recipient_list, 57 fail_silently=False, auth_user=None, auth_password=None): 58 """ 59 Easy wrapper for sending a single message to a recipient list. All members 60 of the recipient list will see the other recipients in the 'To' field. 61 62 If auth_user is None, the EMAIL_HOST_USER setting is used. 63 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. 64 65 Note: The API for this method is frozen. New code wanting to extend the 66 functionality should use the EmailMessage class directly. 67 """ 68 connection = get_connection(username=auth_user, password=auth_password, 69 fail_silently=fail_silently) 70 return EmailMessage(subject, message, from_email, recipient_list, 71 connection=connection).send() 72 73 74 def send_mass_mail(datatuple, fail_silently=False, auth_user=None, 75 auth_password=None): 76 """ 77 Given a datatuple of (subject, message, from_email, recipient_list), sends 78 each message to each recipient list. Returns the number of e-mails sent. 79 80 If from_email is None, the DEFAULT_FROM_EMAIL setting is used. 81 If auth_user and auth_password are set, they're used to log in. 82 If auth_user is None, the EMAIL_HOST_USER setting is used. 83 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. 84 85 Note: The API for this method is frozen. New code wanting to extend the 86 functionality should use the EmailMessage class directly. 87 """ 88 connection = get_connection(username=auth_user, password=auth_password, 89 fail_silently=fail_silently) 90 messages = [EmailMessage(subject, message, sender, recipient) 91 for subject, message, sender, recipient in datatuple] 92 return connection.send_messages(messages) 93 94 95 def mail_admins(subject, message, fail_silently=False): 96 """Sends a message to the admins, as defined by the ADMINS setting.""" 97 if not settings.ADMINS: 98 return 99 EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, 100 settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS] 101 ).send(fail_silently=fail_silently) 102 103 104 def mail_managers(subject, message, fail_silently=False): 105 """Sends a message to the managers, as defined by the MANAGERS setting.""" 106 if not settings.MANAGERS: 107 return 108 EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, 109 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS] 110 ).send(fail_silently=fail_silently) 111 112 113 class SMTPConnection(_SMTPConnection): 114 def __init__(self, *args, **kwds): 115 import warnings 116 warnings.warn( 117 'mail.SMTPConnection is deprectated; use mail.get_connection() instead.', 118 DeprecationWarning 119 ) 120 super(SMTPConnection, self).__init__(*args, **kwds) -
new file django/core/mail/backends/__init__.py
diff -r 230a12f723d8 django/core/mail/backends/__init__.py
- + 1 # Mail backends shipped with Django. -
new file django/core/mail/backends/base.py
diff -r 230a12f723d8 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, **kwargs): 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 230a12f723d8 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 230a12f723d8 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/filebased.py
diff -r 230a12f723d8 django/core/mail/backends/filebased.py
- + 1 """Email backend that writes messages to a file.""" 2 3 import datetime 4 import os 5 import threading 6 7 from django.conf import settings 8 from django.core.exceptions import ImproperlyConfigured 9 from django.core.mail.backends.base import BaseEmailBackend 10 11 12 class EmailBackend(BaseEmailBackend): 13 14 def __init__(self, *args, **kwargs): 15 self.stream = None 16 self._lock = threading.RLock() 17 self._counter = 0 18 self._fname = None 19 if 'file_path' in kwargs: 20 self.file_path = kwargs.pop('file_path') 21 else: 22 self.file_path = getattr(settings, 'EMAIL_FILE_PATH',None) 23 # Make sure self.file_path is a string. 24 if not isinstance(self.file_path, basestring): 25 raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.file_path) 26 self.file_path = os.path.abspath(self.file_path) 27 # Make sure that self.file_path is an directory if it exists. 28 if os.path.exists(self.file_path) and not os.path.isdir(self.file_path): 29 raise ImproperlyConfigured('Path for saving email messages exists, but is not a directory: %s' % self.file_path) 30 # Try to create it, if it not exists. 31 elif not os.path.exists(self.file_path): 32 try: 33 os.makedirs(self.file_path) 34 except OSError, err: 35 raise ImproperlyConfigured('Could not create directory for saving email messages: %s (%s)' % (self.file_path, err)) 36 # Make sure that self.file_path is writable. 37 if not os.access(self.file_path, os.W_OK): 38 raise ImproperlyConfigured('Could not write to directory: %s' % self.file_path) 39 # Finally, call super(). 40 super(EmailBackend, self).__init__(*args, **kwargs) 41 42 def _get_filename(self): 43 """Return a unique file name.""" 44 if self._fname is None: 45 timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") 46 fname = "%s-%s.log" % (timestamp, abs(id(self))) 47 self._fname = os.path.join(self.file_path, fname) 48 return self._fname 49 50 def open(self): 51 if self.stream is None: 52 self.stream = open(self._get_filename(), 'a') 53 return True 54 return False 55 56 def close(self): 57 try: 58 if self.stream is not None: 59 self.stream.close() 60 finally: 61 self.stream = None 62 63 def send_messages(self, email_messages): 64 """Write all messages to the file in a thread-safe way.""" 65 if not email_messages: 66 return 67 self._lock.acquire() 68 try: 69 stream_created = self.open() 70 for message in email_messages: 71 self.stream.write('%s\n' % message.message().as_string()) 72 self.stream.write('-'*79) 73 self.stream.write('\n') 74 self.stream.flush() # flush after each message 75 if stream_created: 76 self.close() 77 except: 78 if not self.fail_silently: 79 raise 80 finally: 81 self._lock.release() 82 return len(email_messages) -
new file django/core/mail/backends/locmem.py
diff -r 230a12f723d8 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, **kwargs): 19 super(EmailBackend, self).__init__(*args, **kwargs) 20 if not hasattr(mail, 'outbox'): 21 mail.outbox = [] 22 23 def send_messages(self, messages): 24 """Redirect messages to the dummy outbox""" 25 mail.outbox.extend(messages) 26 return len(messages) -
new file django/core/mail/backends/smtp.py
diff -r 230a12f723d8 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, **kwargs): 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 230a12f723d8 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 230a12f723d8 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 230a12f723d8 django/test/utils.py
a b 2 2 from django.conf import settings 3 3 from django.db import connection 4 4 from django.core import mail 5 from django.core.mail.backends import locmem 5 6 from django.test import signals 6 7 from django.template import Template 7 8 from django.utils.translation import deactivate … … 28 29 signals.template_rendered.send(sender=self, template=self, context=context) 29 30 return self.nodelist.render(context) 30 31 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 32 50 33 def setup_test_environment(): 51 34 """Perform any global pre-test setup. This involves: 52 35 53 36 - Installing the instrumented test renderer 54 - Diverting the email sending functions to a test buffer37 - Set the email backend to the locmem email backend. 55 38 - Setting the active locale to match the LANGUAGE_CODE setting. 56 39 """ 57 40 Template.original_render = Template.render 58 41 Template.render = instrumented_test_render 59 42 60 43 mail.original_SMTPConnection = mail.SMTPConnection 61 mail.SMTPConnection = TestSMTPConnection 44 mail.SMTPConnection = locmem.EmailBackend 45 46 settings.EMAIL_BACKEND = 'locmem' 47 mail.original_email_backend = settings.EMAIL_BACKEND 62 48 63 49 mail.outbox = [] 64 50 … … 77 63 mail.SMTPConnection = mail.original_SMTPConnection 78 64 del mail.original_SMTPConnection 79 65 66 settings.EMAIL_BACKEND = mail.original_email_backend 67 del mail.original_email_backend 68 80 69 del mail.outbox 81 70 82 83 71 def get_runner(settings): 84 72 test_path = settings.TEST_RUNNER.split('.') 85 73 # Allow for Python 2.5 relative paths -
docs/internals/deprecation.txt
diff -r 230a12f723d8 docs/internals/deprecation.txt
a b 22 22 * The old imports for CSRF functionality (``django.contrib.csrf.*``), 23 23 which moved to core in 1.2, will be removed. 24 24 25 * ``SMTPConnection``. The 1.2 release deprecated the ``SMTPConnection`` 26 class in favor of a generic E-mail backend API. 27 25 28 * 2.0 26 29 * ``django.views.defaults.shortcut()``. This function has been moved 27 30 to ``django.contrib.contenttypes.views.shortcut()`` as part of the -
docs/ref/settings.txt
diff -r 230a12f723d8 docs/ref/settings.txt
a b 424 424 This is only used if ``CommonMiddleware`` is installed (see 425 425 :ref:`topics-http-middleware`). 426 426 427 .. setting:: EMAIL_BACKEND 428 429 EMAIL_BACKEND 430 ------------- 431 432 .. versionadded:: 1.2 433 434 Default: ``'smtp'`` 435 436 The backend to use for sending emails. For the list of available backends see 437 :ref:`topics-email`. 438 439 .. setting:: EMAIL_FILE_PATH 440 441 EMAIL_FILE_PATH 442 --------------- 443 444 .. versionadded:: 1.2 445 446 Default: ``None`` 447 448 The directory used by the ``file`` email backend to store output files. 449 427 450 .. setting:: EMAIL_HOST 428 451 429 452 EMAIL_HOST -
docs/topics/email.txt
diff -r 230a12f723d8 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 :class:`~django.core.mail.EmailMessage` 188 class. 189 190 Not all features of the :class:`~django.core.mail.EmailMessage` class are 191 available through the ``send_mail()`` and related wrapper functions. If you 192 wish to use advanced features, such as BCC'ed recipients, file attachments, or 193 multi-part e-mail, you'll need to create 194 :class:`~django.core.mail.EmailMessage` instances directly. 190 195 191 196 .. 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 197 197 This is a design feature. ``send_mail()`` and related functions were 198 198 originally the only interface Django provided. However, the list of 199 199 parameters they accepted was slowly growing over time. It made sense to 200 200 move to a more object-oriented design for e-mail messages and retain the 201 201 original functions only for backwards compatibility. 202 202 203 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 :class:`~django.core.mail.EmailMessage` is responsible for creating the e-mail 204 message itself. The :ref:`e-mail backend <topic-email-backends>` is then 205 responsible for sending the e-mail. 206 207 For convenience, :class:`~django.core.mail.EmailMessage` provides a simple 208 ``send()`` method for sending a single email. If you need to send multiple 209 messages, the email backend API :ref:`provides an alternative 210 <topics-sending-multiple-emails>`. 207 211 208 212 EmailMessage Objects 209 213 -------------------- 210 214 211 .. class:: EmailMessage215 .. class:: django.core.mail.EmailMessage 212 216 213 The ``EmailMessage`` class is initialized with the following parameters (in 214 the given order, if positional arguments are used). All parameters are 215 optional and can be set at any time prior to calling the ``send()`` method. 217 The :class:`~django.core.mail.EmailMessage` class is initialized with the 218 following parameters (in the given order, if positional arguments are used). 219 All parameters are optional and can be set at any time prior to calling the 220 ``send()`` method. 216 221 217 222 * ``subject``: The subject line of the e-mail. 218 223 … … 227 232 * ``bcc``: A list or tuple of addresses used in the "Bcc" header when 228 233 sending the e-mail. 229 234 230 * ``connection``: An ``SMTPConnection``instance. Use this parameter if235 * ``connection``: An e-mail backend instance. Use this parameter if 231 236 you want to use the same connection for multiple messages. If omitted, a 232 237 new connection is created when ``send()`` is called. 233 238 … … 248 253 249 254 The class has the following methods: 250 255 251 * ``send(fail_silently=False)`` sends the message , using either252 the connection that is specified in the ``connection``253 attribute, or creating a new connection if none already254 exists. If the keyword argument ``fail_silently`` is ``True``,255 exceptionsraised while sending the message will be quashed.256 * ``send(fail_silently=False)`` sends the message. If a connection was 257 specified when the email was constructed, that connection will be used. 258 Otherwise, an instance of the default backend will be instantiated and 259 used. If the keyword argument ``fail_silently`` is ``True``, exceptions 260 raised while sending the message will be quashed. 256 261 257 262 * ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a 258 263 subclass of Python's ``email.MIMEText.MIMEText`` class) or a 259 ``django.core.mail.SafeMIMEMultipart`` object holding the 260 message to be sent. If you ever need to extend the ``EmailMessage`` class,261 you'll probably want to override this method to put the content you want262 into the MIME object.264 ``django.core.mail.SafeMIMEMultipart`` object holding the message to be 265 sent. If you ever need to extend the 266 :class:`~django.core.mail.EmailMessage` class, you'll probably want to 267 override this method to put the content you want into the MIME object. 263 268 264 269 * ``recipients()`` returns a list of all the recipients of the message, 265 270 whether they're recorded in the ``to`` or ``bcc`` attributes. This is … … 299 304 Sending alternative content types 300 305 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 301 306 302 It can be useful to include multiple versions of the content in an e-mail; 303 theclassic example is to send both text and HTML versions of a message. With307 It can be useful to include multiple versions of the content in an e-mail; the 308 classic example is to send both text and HTML versions of a message. With 304 309 Django's e-mail library, you can do this using the ``EmailMultiAlternatives`` 305 class. This subclass of ``EmailMessage`` has an ``attach_alternative()`` method306 for including extra versions of the message body in the e-mail. All the other 307 methods (including the class initialization) are inherited directly from 308 ``EmailMessage``.310 class. This subclass of :class:`~django.core.mail.EmailMessage` has an 311 ``attach_alternative()`` method for including extra versions of the message 312 body in the e-mail. All the other methods (including the class initialization) 313 are inherited directly from :class:`~django.core.mail.EmailMessage`. 309 314 310 315 To send a text and HTML combination, you could write:: 311 316 … … 318 323 msg.attach_alternative(html_content, "text/html") 319 324 msg.send() 320 325 321 By default, the MIME type of the ``body`` parameter in an ``EmailMessage`` is 322 ``"text/plain"``. It is good practice to leave this alone, because it 323 guarantees that any recipient will be able to read the e-mail, regardless of 324 their mail client. However, if you are confident that your recipients can 325 handle an alternative content type, you can use the ``content_subtype`` 326 attribute on the ``EmailMessage`` class to change the main content type. The 327 major type will always be ``"text"``, but you can change it to the subtype. For 328 example:: 326 By default, the MIME type of the ``body`` parameter in an 327 :class:`~django.core.mail.EmailMessage` is ``"text/plain"``. It is good 328 practice to leave this alone, because it guarantees that any recipient will be 329 able to read the e-mail, regardless of their mail client. However, if you are 330 confident that your recipients can handle an alternative content type, you can 331 use the ``content_subtype`` attribute on the 332 :class:`~django.core.mail.EmailMessage` class to change the main content type. 333 The major type will always be ``"text"``, but you can change it to the 334 subtype. For example:: 329 335 330 336 msg = EmailMessage(subject, html_content, from_email, [to]) 331 337 msg.content_subtype = "html" # Main content is now text/html 332 338 msg.send() 333 339 334 SMTPConnection Objects 335 ---------------------- 340 .. _topic-email-backends: 336 341 337 .. class:: SMTPConnection 342 E-Mail Backends 343 =============== 338 344 339 The ``SMTPConnection`` class is initialized with the host, port, username and 340 password for the SMTP server. If you don't specify one or more of those 341 options, they are read from your settings file. 345 .. versionadded:: 1.2 342 346 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:: 347 The actual sending of an e-mail is handled by the e-mail backend. 349 348 350 connection = SMTPConnection() # Use default settings for connection 349 The e-mail backend class has the following methods: 350 351 * ``open()`` instantiates an long-lived email-sending connection. 352 353 * ``close()`` closes the current email-sending connection. 354 355 * ``send_messages(email_messages)`` sends a list of 356 :class:`~django.core.mail.EmailMessage` objects. If the connection 357 is not open, this call will implicitly open the connection, and 358 close the connection afterwards. 359 360 Obtaining an instance of an e-mail backend 361 ------------------------------------------ 362 363 The :meth:`get_connection` function in ``django.core.mail`` returns an 364 instance of the e-mail backend that you can use. 365 366 .. currentmodule:: django.core.mail 367 368 .. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs) 369 370 By default, a call to ``get_connection()`` will return an instance of the 371 email backend specified in :setting:`EMAIL_BACKEND`. If you specify the 372 ``backend`` argument, an instance of that backend will be instantiated. 373 374 The ``fail_silently`` argument controls how the backend should handle errors. 375 If ``fail_silently`` is True, exceptions during the email sending process 376 will be silently ignored. 377 378 All other arguments are passed directly to the constructor of the 379 e-mail backend. 380 381 Django ships with several e-mail sending backends. With the exception of the 382 SMTP backend (which is the default), these backends are only useful during 383 testing and development. If you have special email sending requirements, you 384 can :ref:`write your own email backend <topic-custom-email-backend>`. 385 386 SMTP backend 387 ~~~~~~~~~~~~ 388 389 This is the default backend. E-mail will be sent through a SMTP server. 390 The server address and authentication credentials are set in the 391 :setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`, 392 :setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your 393 settings file. 394 395 The SMTP backend is configured in the default settings:: 396 397 EMAIL_BACKEND = 'smtp' 398 399 .. admonition:: SMTPConnection objects 400 401 Prior to version 1.2, Django provided a ``mail.SMTPConnection`` class. This 402 class provided a way to directly control the use of SMTP to send email. 403 This class has been deprecated in favor of the generic email backend API. 404 405 For backwards compatibility ``SMTPConnection`` is still available in 406 ``django.core.mail`` as an alias for the SMTP backend. New code should 407 use :meth:`~django.core.mail.get_connection` instead. 408 409 Console backend 410 ~~~~~~~~~~~~~~~ 411 412 Instead of sending out real e-mails the console backend just writes the e-mails 413 that would be send to the standard output. To use this backend, use 414 in your settings:: 415 416 EMAIL_BACKEND = 'console' 417 418 This backend is not intended for use in production -- it is provided as a 419 convenience that can be used during development. 420 421 File backend 422 ~~~~~~~~~~~~ 423 424 The file backend writes e-mails to a file. A new file is created for each new 425 instance of this backend. The directory to which the files are written is 426 either taken from the :setting:`EMAIL_FILE_PATH` setting or from the 427 ``file_path`` keyword when creating a connection with 428 :meth:`~django.core.mail.get_connection`. To use this backend, use in your 429 settings:: 430 431 EMAIL_BACKEND = 'file' 432 EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location 433 434 This backend is not intended for use in production -- it is provided as a 435 convenience that can be used during development. 436 437 In-memory backend 438 ~~~~~~~~~~~~~~~~~ 439 440 The ``'locmem'`` backend stores messages in a special attribute of the 441 ``django.core.mail`` module. The ``outbox`` attribute is created when the 442 first message is send. It's a list with an 443 :class:`~django.core.mail.EmailMessage` instance for each message that would 444 be send. To use this backend, use in your settings:: 445 446 EMAIL_BACKEND = 'locmem' 447 448 This backend is not intended for use in production -- it is provided as a 449 convenience that can be used during development and testing. 450 451 Dummy backend 452 ~~~~~~~~~~~~~ 453 454 As the name suggests the dummy backend does nothing with your messages. 455 To enable this backend, use in your settings file:: 456 457 EMAIL_BACKEND = 'dummy' 458 459 This backend is not intended for use in production -- it is provided as a 460 convenience that can be used during development. 461 462 .. _topic-custom-email-backend: 463 464 Defining a custom e-mail backend 465 -------------------------------- 466 467 If you need to change how e-mails are send you can write your own e-mail 468 backend. The ``EMAIL_BACKEND`` setting in your settings file is then the 469 Python import path for your backend. 470 471 Custom e-mail backends should subclass ``BaseEmailBackend`` that is located in 472 the ``django.core.mail.backends.base`` module. A custom e-mail backend must 473 implement the ``send_messages(email_messages)`` method. This method receives a 474 list of :class:`~django.core.mail.EmailMessage` instances and returns the 475 number of successfully delivered messages. If your backend should be capable 476 of network connection handling, you should implement both the ``open()`` and 477 ``close()`` methods too. Refer to ``SMTPEmailBackend`` for a reference 478 implementation. 479 480 .. _topics-sending-multiple-emails: 481 482 Sending multiple emails 483 ----------------------- 484 485 Establishing and closing an SMTP connection (or any other network connection, 486 for that matter) is an expensive process. If you have a lot of emails to send, 487 it makes sense to reuse an SMTP connection, rather than creating and 488 destroying a connection every time you want to send an email. 489 490 There are two ways you tell an email backend to reuse a connection. 491 492 Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes 493 a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses), 494 and sends them all using a single connection. 495 496 For example, if you have a function called ``get_notification_email()`` that 497 returns a list of :class:`~django.core.mail.EmailMessage` objects representing 498 some periodic e-mail you wish to send out, you could send these emails using 499 a single call to send_messages:: 500 501 from django.core import mail 502 connection = mail.get_connection() # Use default email connection 351 503 messages = get_notification_email() 352 504 connection.send_messages(messages) 353 505 506 In this example, the call to ``send_messages()`` opens a connection on the 507 backend, sends the list of messages, and then closes the connection again. 508 509 The second approach is to use the ``open()`` and ``close()`` methods on the 510 email backend to manually control the connection. ``send_messages()`` will not 511 manually open or close the connection if it is already open, so if you 512 manually open the connection, you can control when it is closed. For example:: 513 514 from django.core import mail 515 connection = mail.get_connection() 516 517 # Manually open the connection 518 connection.open() 519 520 # Construct an email message that uses the connection 521 email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', 522 ['to1@example.com'], connection=connection) 523 email1.send() # Send the email 524 525 # Construct two more messages 526 email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', 527 ['to2@example.com']) 528 email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', 529 ['to3@example.com']) 530 531 # Send the two emails in a single call - 532 connection.send_messages([email2, email3]) 533 # The connection was already open so send_messages() doesn't close it. 534 # We need to manually close the connection. 535 connection.close() 536 537 354 538 Testing e-mail sending 355 ---------------------- 539 ====================== 356 540 357 541 The are times when you do not want Django to send e-mails at all. For example, 358 542 while developing a website, you probably don't want to send out thousands of … … 360 544 people under the right conditions, and that those e-mails will contain the 361 545 correct content. 362 546 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:: 547 The easiest way to test your project's use of e-mail is to use the ``console`` 548 email backend. This backend redirects all email to stdout, allowing you to 549 inspect the content of mail. 550 551 The ``file`` email backend can also be useful during development -- this backend 552 dumps the contents of every SMTP connection to a file that can be inspected 553 at your leisure. 554 555 Another approach is to use a "dumb" SMTP server that receives the e-mails 556 locally and displays them to the terminal, but does not actually send 557 anything. Python has a built-in way to accomplish this with a single command:: 367 558 368 559 python -m smtpd -n -c DebuggingServer localhost:1025 369 560 370 561 This command will start a simple SMTP server listening on port 1025 of 371 localhost. This server simply prints to standard output all e mail headers and372 the e mail body. You then only need to set the :setting:`EMAIL_HOST` and562 localhost. This server simply prints to standard output all e-mail headers and 563 the e-mail body. You then only need to set the :setting:`EMAIL_HOST` and 373 564 :setting:`EMAIL_PORT` accordingly, and you are set. 374 565 375 For more entailed testing and processing of e-mails locally, see the Python376 documentation on the `SMTP Server`_.566 For a more detailed discussion of testing and processing of e-mails locally, 567 see the Python documentation on the `SMTP Server`_. 377 568 378 569 .. _SMTP Server: http://docs.python.org/library/smtpd.html -
docs/topics/testing.txt
diff -r 230a12f723d8 docs/topics/testing.txt
a b 1104 1104 ``target_status_code`` will be the url and status code for the final 1105 1105 point of the redirect chain. 1106 1106 1107 .. _topics-testing-email: 1108 1107 1109 E-mail services 1108 1110 --------------- 1109 1111 … … 1117 1119 contents of each message -- without actually sending the messages. 1118 1120 1119 1121 The test runner accomplishes this by transparently replacing the normal 1120 :class:`~django.core.mail.SMTPConnection` class with a different version.1122 email backend with a testing backend. 1121 1123 (Don't worry -- this has no effect on any other e-mail senders outside of 1122 1124 Django, such as your machine's mail server, if you're running one.) 1123 1125 … … 1128 1130 During test running, each outgoing e-mail is saved in 1129 1131 ``django.core.mail.outbox``. This is a simple list of all 1130 1132 :class:`~django.core.mail.EmailMessage` instances that have been sent. 1131 It does not exist under normal execution conditions, i.e., when you're not1132 running unit tests. The outbox is created during test setup, along with the1133 dummy :class:`~django.core.mail.SMTPConnection`. When the test framework is1134 torn down, the standard :class:`~django.core.mail.SMTPConnection` class is1135 restored, and the test outbox is destroyed.1136 1137 1133 The ``outbox`` attribute is a special attribute that is created *only* when 1138 the tests are run. It doesn't normally exist as part of the1134 the ``locmem`` e-mail backend is used. It doesn't normally exist as part of the 1139 1135 :mod:`django.core.mail` module and you can't import it directly. The code 1140 1136 below shows how to access this attribute correctly. 1141 1137 -
new file tests/regressiontests/mail/custombackend.py
diff -r 230a12f723d8 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 __init__(self, *args, **kwargs): 9 super(EmailBackend, self).__init__(*args, **kwargs) 10 self.test_outbox = [] 11 12 def send_messages(self, email_messages): 13 # Messages are stored in a instance variable for testing. 14 self.test_outbox.extend(email_messages) 15 return len(email_messages) -
tests/regressiontests/mail/tests.py
diff -r 230a12f723d8 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, filebased, smtp 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(mail.outbox) 194 2 195 196 # Make sure that the file backend write to the right location 197 >>> tmp_dir = tempfile.mkdtemp() 198 >>> connection = filebased.EmailBackend(file_path=tmp_dir) 199 >>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) 200 >>> connection.send_messages([email]) 201 1 202 >>> len(os.listdir(tmp_dir)) 203 1 204 >>> print open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0])).read() 205 Content-Type: text/plain; charset="utf-8" 206 MIME-Version: 1.0 207 Content-Transfer-Encoding: quoted-printable 208 Subject: Subject 209 From: from@example.com 210 To: to@example.com 211 Date: ... 212 Message-ID: ... 213 214 Content 215 ... 216 >>> connection2 = filebased.EmailBackend(file_path=tmp_dir) 217 >>> connection2.send_messages([email]) 218 1 219 >>> len(os.listdir(tmp_dir)) 220 2 221 >>> connection.send_messages([email]) 222 1 223 >>> len(os.listdir(tmp_dir)) 224 2 225 >>> email.connection = filebased.EmailBackend(file_path=tmp_dir) 226 >>> connection_created = connection.open() 227 >>> num_sent = email.send() 228 >>> len(os.listdir(tmp_dir)) 229 3 230 >>> num_sent = email.send() 231 >>> len(os.listdir(tmp_dir)) 232 3 233 >>> connection.close() 234 >>> shutil.rmtree(tmp_dir) 235 236 # Make sure that get_connection() accepts arbitrary keyword that might be 237 # used with custom backends. 238 >>> c = mail.get_connection(fail_silently=True, foo='bar') 239 >>> c.fail_silently 240 True 241 242 # Test custom backend defined in this suite. 243 >>> conn = mail.get_connection('regressiontests.mail.custombackend') 244 >>> hasattr(conn, 'test_outbox') 245 True 246 >>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) 247 >>> conn.send_messages([email]) 248 1 249 >>> len(conn.test_outbox) 250 1 251 252 # Test backend argument of mail.get_connection() 253 >>> isinstance(mail.get_connection('smtp'), smtp.EmailBackend) 254 True 255 >>> isinstance(mail.get_connection('locmem'), locmem.EmailBackend) 256 True 257 >>> isinstance(mail.get_connection('dummy'), dummy.EmailBackend) 258 True 259 >>> isinstance(mail.get_connection('console'), console.EmailBackend) 260 True 261 >>> tmp_dir = tempfile.mkdtemp() 262 >>> isinstance(mail.get_connection('file', file_path=tmp_dir), filebased.EmailBackend) 263 True 264 >>> shutil.rmtree(tmp_dir) 265 >>> isinstance(mail.get_connection(), locmem.EmailBackend) 266 True 267 141 268 """