Ticket #10355: email_backends.diff
File email_backends.diff, 59.2 KB (added by , 15 years ago) |
---|
-
django/conf/global_settings.py
Vergleiche mit /storage/devel/django/django-trunk Suche nach Änderungen Änderung: 7296:c00b6982431e Nutzer: Andi Albrecht <albrecht.andi@gmail.com> Datum: Mon Aug 24 06:34:37 2009 +0200 Zusammenfassung: Refactored django.core.email diff -r 4d79b1a3175f -r c00b6982431e 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. Possible values are 'smtp' (default), 'dummy', 135 # 'log', 'test' or any Python import path pointing to a BaseEmailBackend 136 # implementation. 137 EMAIL_BACKEND = 'smtp' 138 139 # The email backend to use for testing. 140 TEST_EMAIL_BACKEND = 'testenv' 141 134 142 # Host for sending e-mail. 135 143 EMAIL_HOST = 'localhost' 136 144 -
deleted file django/core/mail.py
diff -r 4d79b1a3175f -r c00b6982431e 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 4d79b1a3175f -r c00b6982431e 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', 'log', 'testenv') 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.EmailBackend' % path 28 i = path.rfind('.') 29 module, attr = path[:i], path[i+1:] 30 try: 31 mod = import_module(module) 32 except ImportError, e: 33 raise ImproperlyConfigured(('Error importing email backend %s: "%s"' 34 % (module, e))) 35 try: 36 cls = getattr(mod, attr) 37 except AttributeError: 38 raise ImproperlyConfigured(('Module "%s" does not define a "%s" ' 39 'email backend' % (module, attr))) 40 return cls(fail_silently=fail_silently, **kwds) 41 42 43 def send_mail(subject, message, from_email, recipient_list, 44 fail_silently=False, auth_user=None, auth_password=None): 45 """ 46 Easy wrapper for sending a single message to a recipient list. All members 47 of the recipient list will see the other recipients in the 'To' field. 48 49 If auth_user is None, the EMAIL_HOST_USER setting is used. 50 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. 51 52 Note: The API for this method is frozen. New code wanting to extend the 53 functionality should use the EmailMessage class directly. 54 """ 55 connection = get_connection(username=auth_user, password=auth_password, 56 fail_silently=fail_silently) 57 return EmailMessage(subject, message, from_email, recipient_list, 58 connection=connection).send() 59 60 61 def send_mass_mail(datatuple, fail_silently=False, auth_user=None, 62 auth_password=None): 63 """ 64 Given a datatuple of (subject, message, from_email, recipient_list), sends 65 each message to each recipient list. Returns the number of e-mails sent. 66 67 If from_email is None, the DEFAULT_FROM_EMAIL setting is used. 68 If auth_user and auth_password are set, they're used to log in. 69 If auth_user is None, the EMAIL_HOST_USER setting is used. 70 If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. 71 72 Note: The API for this method is frozen. New code wanting to extend the 73 functionality should use the EmailMessage class directly. 74 """ 75 connection = get_connection(username=auth_user, password=auth_password, 76 fail_silently=fail_silently) 77 messages = [EmailMessage(subject, message, sender, recipient) 78 for subject, message, sender, recipient in datatuple] 79 return connection.send_messages(messages) 80 81 82 def mail_admins(subject, message, fail_silently=False): 83 """Sends a message to the admins, as defined by the ADMINS setting.""" 84 if not settings.ADMINS: 85 return 86 EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, 87 settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS] 88 ).send(fail_silently=fail_silently) 89 90 91 def mail_managers(subject, message, fail_silently=False): 92 """Sends a message to the managers, as defined by the MANAGERS setting.""" 93 if not settings.MANAGERS: 94 return 95 EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, 96 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS] 97 ).send(fail_silently=fail_silently) -
new file django/core/mail/backends/base.py
diff -r 4d79b1a3175f -r c00b6982431e 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. There's no guarantee that this method 19 is called before an attempt to send one or more emails is made. 20 But this method can be called by applications to force a single 21 network connection to be used when sending mails. See the 22 send_messages() method of the SMTP backend for a reference 23 implementation. 24 25 The default implementation does nothing. 26 """ 27 pass 28 29 def close(self): 30 """Close a network connection.""" 31 pass 32 33 def send_messages(self, email_messages): 34 """ 35 Sends one or more EmailMessage objects and returns the number of email 36 messages sent. 37 """ 38 raise NotImplementedError -
new file django/core/mail/backends/dummy.py
diff -r 4d79b1a3175f -r c00b6982431e 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/log.py
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/log.py
- + 1 """ 2 Email backend that logs messages instead of sending them. 3 """ 4 5 import logging 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 logging.info('Sending email:\n%s' % message.message().as_string()) 15 return len(email_messages) -
new file django/core/mail/backends/smtp.py
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/smtp.py
- + 1 """SMTP email backend class.""" 2 3 import smtplib 4 import socket 5 6 from django.conf import settings 7 from django.core.mail.backends.base import BaseEmailBackend 8 from django.core.mail.utils import DNS_NAME 9 10 11 class EmailBackend(BaseEmailBackend): 12 """ 13 A wrapper that manages the SMTP network connection. 14 """ 15 16 def __init__(self, host=None, port=None, username=None, password=None, 17 use_tls=None, fail_silently=False, **kwds): 18 super(EmailBackend, self).__init__(fail_silently=fail_silently) 19 self.host = host or settings.EMAIL_HOST 20 self.port = port or settings.EMAIL_PORT 21 self.username = username or settings.EMAIL_HOST_USER 22 self.password = password or settings.EMAIL_HOST_PASSWORD 23 self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS 24 self.connection = None 25 26 def open(self): 27 """ 28 Ensures we have a connection to the email server. Returns whether or 29 not a new connection was required (True or False). 30 """ 31 if self.connection: 32 # Nothing to do if the connection is already open. 33 return False 34 try: 35 # If local_hostname is not specified, socket.getfqdn() gets used. 36 # For performance, we use the cached FQDN for local_hostname. 37 self.connection = smtplib.SMTP(self.host, self.port, 38 local_hostname=DNS_NAME.get_fqdn()) 39 if self.use_tls: 40 self.connection.ehlo() 41 self.connection.starttls() 42 self.connection.ehlo() 43 if self.username and self.password: 44 self.connection.login(self.username, self.password) 45 return True 46 except: 47 if not self.fail_silently: 48 raise 49 50 def close(self): 51 """Closes the connection to the email server.""" 52 try: 53 try: 54 self.connection.quit() 55 except socket.sslerror: 56 # This happens when calling quit() on a TLS connection 57 # sometimes. 58 self.connection.close() 59 except: 60 if self.fail_silently: 61 return 62 raise 63 finally: 64 self.connection = None 65 66 def send_messages(self, email_messages): 67 """ 68 Sends one or more EmailMessage objects and returns the number of email 69 messages sent. 70 """ 71 if not email_messages: 72 return 73 new_conn_created = self.open() 74 if not self.connection: 75 # We failed silently on open(). Trying to send would be pointless. 76 return 77 num_sent = 0 78 for message in email_messages: 79 sent = self._send(message) 80 if sent: 81 num_sent += 1 82 if new_conn_created: 83 self.close() 84 return num_sent 85 86 def _send(self, email_message): 87 """A helper method that does the actual sending.""" 88 if not email_message.recipients(): 89 return False 90 try: 91 self.connection.sendmail(email_message.from_email, 92 email_message.recipients(), 93 email_message.message().as_string()) 94 except: 95 if not self.fail_silently: 96 raise 97 return False 98 return True -
new file django/core/mail/backends/testenv.py
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/testenv.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 substitute SMTP connection for use during test sessions. 11 The test connection stores email messages in a dummy outbox, 12 rather than sending them out on the wire. 13 14 """ 15 16 def send_messages(self, messages): 17 "Redirect messages to the dummy outbox" 18 mail.outbox.extend(messages) 19 return len(messages) -
new file django/core/mail/message.py
diff -r 4d79b1a3175f -r c00b6982431e 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 4d79b1a3175f -r c00b6982431e 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 4d79b1a3175f -r c00b6982431e 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 """ 57 39 Template.original_render = Template.render 58 40 Template.render = instrumented_test_render 59 41 60 mail.original_ SMTPConnection = mail.SMTPConnection61 mail.SMTPConnection = TestSMTPConnection42 mail.original_email_backend = settings.EMAIL_BACKEND 43 settings.EMAIL_BACKEND = settings.TEST_EMAIL_BACKEND 62 44 63 45 mail.outbox = [] 64 46 … … 74 56 Template.render = Template.original_render 75 57 del Template.original_render 76 58 77 mail.SMTPConnection = mail.original_SMTPConnection78 del mail.original_ SMTPConnection59 settings.EMAIL_BACKEND = mail.original_email_backend 60 del mail.original_email_backend 79 61 80 62 del mail.outbox 81 63 -
tests/modeltests/test_client/views.py
diff -r 4d79b1a3175f -r c00b6982431e tests/modeltests/test_client/views.py
a b 1 1 from xml.dom.minidom import parseString 2 2 3 from django.core import mail 3 4 from django.core.mail import EmailMessage, SMTPConnection 4 5 from django.template import Context, Template 5 6 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound … … 201 202 'from@example.com', 202 203 ['second@example.com', 'third@example.com']) 203 204 204 c = SMTPConnection() 205 c = mail.get_connection() 206 c.open() 205 207 c.send_messages([m1,m2]) 208 c.close() 206 209 207 210 return HttpResponse("Mail sent") -
tests/regressiontests/mail/tests.py
diff -r 4d79b1a3175f -r c00b6982431e tests/regressiontests/mail/tests.py
a b 5 5 >>> from django.conf import settings 6 6 >>> from django.core import mail 7 7 >>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives 8 >>> from django.core.mail.backends.base import BaseEmailBackend 8 9 >>> from django.utils.translation import ugettext_lazy 9 10 10 11 # Test normal ascii character case: … … 138 139 JVBERi0xLjQuJS4uLg== 139 140 ... 140 141 142 # Make sure all builtin email backends load correctly. 143 >>> failed = [] 144 >>> for name in mail.BACKENDS: 145 ... settings.EMAIL_BACKEND = name 146 ... conn = mail.get_connection(foo='bar') 147 ... if not isinstance(conn, BaseEmailBackend): 148 ... failed.append(name) 149 >>> failed 150 [] 151 >>> settings.EMAIL_BACKEND = 'testenv' # reset test backend 152 153 # Make sure that get_connection() accepts arbitrary keyword that might be 154 # used with custom backends. 155 >>> c = mail.get_connection(fail_silently=True, foo='bar') 156 >>> c.fail_silently 157 True 141 158 """ -
docs/ref/settings.txt
Änderung: 7297:54962e5dce18 Nutzer: Andi Albrecht <albrecht.andi@gmail.com> Datum: Mon Aug 24 06:46:46 2009 +0200 Zusammenfassung: Document email backend settings. diff -r c00b6982431e -r 54962e5dce18 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 … … 1098 1110 1099 1111 See :ref:`topics-testing`. 1100 1112 1113 .. setting:: TEST_EMAIL_BACKEND 1114 1115 TEST_EMAIL_BACKEND 1116 ------------------ 1117 1118 .. versionadded:: 1.2 1119 1120 Default: ``'testenv'`` 1121 1122 The email backend to use when running the test suite. 1123 1124 See :ref:`topics-email`. 1125 1101 1126 .. setting:: TEST_RUNNER 1102 1127 1103 1128 TEST_RUNNER -
django/core/mail/backends/log.py
Änderung: 7298:fab52dcbb802 Nutzer: Andi Albrecht <albrecht.andi@gmail.com> Datum: Mon Aug 24 09:53:09 2009 +0200 Zusammenfassung: Change log message. diff -r 54962e5dce18 -r fab52dcbb802 django/core/mail/backends/log.py
a b 11 11 12 12 def send_messages(self, email_messages): 13 13 for message in email_messages: 14 logging.info('Sending email:\n%s' % message.message().as_string()) 14 logging.info(message.message().as_string()) 15 logging.info('This email is just logged. No real email was sent.') 15 16 return len(email_messages) -
django/conf/global_settings.py
Änderung: 7299:864d5ad60910 Nutzer: Andi Albrecht <albrecht.andi@gmail.com> Datum: Mon Aug 24 13:39:11 2009 +0200 Zusammenfassung: Docs and cleanup. diff -r fab52dcbb802 -r 864d5ad60910 django/conf/global_settings.py
a b 137 137 EMAIL_BACKEND = 'smtp' 138 138 139 139 # The email backend to use for testing. 140 TEST_EMAIL_BACKEND = 'test env'140 TEST_EMAIL_BACKEND = 'test' 141 141 142 142 # Host for sending e-mail. 143 143 EMAIL_HOST = 'localhost' -
django/core/mail/__init__.py
diff -r fab52dcbb802 -r 864d5ad60910 django/core/mail/__init__.py
a b 13 13 14 14 # Shortcuts for builtin email backends. If settings.EMAIL_BACKEND isn't in 15 15 # this list it's treated as a Python import path. 16 BACKENDS = ('smtp', 'dummy', 'log', 'test env')16 BACKENDS = ('smtp', 'dummy', 'log', 'test') 17 17 18 18 19 19 def get_connection(fail_silently=False, **kwds): -
new file django/core/mail/backends/test.py
diff -r fab52dcbb802 -r 864d5ad60910 django/core/mail/backends/test.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 substitute SMTP connection for use during test sessions. 11 The test connection stores email messages in a dummy outbox, 12 rather than sending them out on the wire. 13 14 """ 15 16 def send_messages(self, messages): 17 "Redirect messages to the dummy outbox" 18 mail.outbox.extend(messages) 19 return len(messages) -
deleted file django/core/mail/backends/testenv.py
diff -r fab52dcbb802 -r 864d5ad60910 django/core/mail/backends/testenv.py
+ - 1 """2 Backend for test environment.3 """4 5 from django.core import mail6 from django.core.mail.backends.base import BaseEmailBackend7 8 9 class EmailBackend(BaseEmailBackend):10 """A substitute SMTP connection for use during test sessions.11 The test connection stores email messages in a dummy outbox,12 rather than sending them out on the wire.13 14 """15 16 def send_messages(self, messages):17 "Redirect messages to the dummy outbox"18 mail.outbox.extend(messages)19 return len(messages) -
docs/ref/settings.txt
diff -r fab52dcbb802 -r 864d5ad60910 docs/ref/settings.txt
a b 1117 1117 1118 1118 .. versionadded:: 1.2 1119 1119 1120 Default: ``'test env'``1120 Default: ``'test'`` 1121 1121 1122 1122 The email backend to use when running the test suite. 1123 1123 -
docs/topics/email.txt
diff -r fab52dcbb802 -r 864d5ad60910 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 This is a design feature. ``send_mail()`` and related functions were 198 originally the only interface Django provided. However, the list of 199 parameters they accepted was slowly growing over time. It made sense to 200 move to a more object-oriented design for e-mail messages and retain the 201 original functions only for backwards compatibility. 196 This is a design feature. ``send_mail()`` and related functions 197 were originally the only interface Django provided. However, the 198 list of parameters they accepted was slowly growing over time. It 199 made sense to move to a more object-oriented design for e-mail 200 messages and retain the original functions only for backwards 201 compatibility. 202 202 203 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. 204 itself. The email backend is responsible for sending the email. By default 205 the SMTP backend is used to send emails using a SMTP server. 207 206 208 207 EmailMessage Objects 209 208 -------------------- … … 227 226 * ``bcc``: A list or tuple of addresses used in the "Bcc" header when 228 227 sending the e-mail. 229 228 230 * ``connection``: An ``SMTPConnection``instance. Use this parameter if229 * ``connection``: An email backend instance. Use this parameter if 231 230 you want to use the same connection for multiple messages. If omitted, a 232 231 new connection is created when ``send()`` is called. 233 232 … … 331 330 msg.content_subtype = "html" # Main content is now text/html 332 331 msg.send() 333 332 334 SMTPConnection Objects 335 ---------------------- 333 334 Email Backends 335 ============== 336 337 .. versionadded:: 1.2 338 339 The actual sending of an email is handled by the email backend. 340 By default the SMTP backend will be used. 341 342 Your email backend preferences goes in the ``EMAIL_BACKEND`` setting in 343 your settings file. Here's an explanation of all available values for 344 ``EMAIL_BACKEND``. 345 346 SMTP backend 347 ------------ 348 349 This is the default backend. Email will be sent through a SMTP server. 350 The server address and authentication credentials are set in the 351 :setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`, 352 :setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your 353 settings file. 354 355 The SMTP is configured in the default settings:: 356 357 EMAIL_BACKEND = 'smtp' 358 359 360 SMTPConnection objects 361 ~~~~~~~~~~~~~~~~~~~~~~ 336 362 337 363 .. class:: SMTPConnection 338 364 365 For backwards compatibility the SMTP backend is available in 366 ``django.core.mail`` as the ``SMTPConnection`` class. 367 339 368 The ``SMTPConnection`` class is initialized with the host, port, username and 340 369 password for the SMTP server. If you don't specify one or more of those 341 370 options, they are read from your settings file. … … 351 380 messages = get_notification_email() 352 381 connection.send_messages(messages) 353 382 354 Testing e-mail sending 355 ---------------------- 383 If you want to resuse the same connection for multiple messages, but can't 384 use ``send_messages()``, you'll have to use the ``open()`` and ``close()`` 385 methods of the email backend:: 386 387 from django.core import mail 388 389 connection = mail.get_connection() 390 connection.open() 391 email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', 392 ['to1@example.com'], connection=connection) 393 email1.send() 394 email2 = mail.EmailMessage('Hello again', 'Body goes here', 395 'from@example.com', ['to1@example.com'], 396 connection=connection) 397 email2.send() 398 connection.close() 399 400 401 Logging backend (for development) 402 --------------------------------- 403 404 Instead of sending out real emails the logging backend just logs the emails 405 that would be send out to the standard output. To use this backend, use 406 in your settings:: 407 408 EMAIL_BACKEND = 'log' 409 410 Dummy backend (for development) 411 ------------------------------- 412 413 As the name suggests the dummy backend does nothing with your emails. 414 To enable this backend, use in your settings file:: 415 416 EMAIL_BACKEND = 'dummy' 417 418 This backend can be useful during development, if you even want to suppress 419 the log entries written by the logging backend. 420 421 Using a custom email backend 422 ---------------------------- 423 424 If you need to change how emails are send you can write your own email 425 backend. The ``EMAIL_BACKEND`` setting in your settings file is then the 426 Python import path pointing to your custom backend class. 427 428 Custom email backends should subclass ``BaseEmailBackend`` that is located 429 in the ``django.core.mail.backends.base`` module. A custom email 430 backend must implement the ``send_messages(email_messages)`` method. This 431 method receives a list of ``EMailMessage`` instances and returns the number 432 of successfully delivered messages. If your backend should be capable of 433 network connection handling, you should implement both the ``open()`` 434 and ``close()`` methods too. Refer to ``SMTPEmailBackend`` for a reference 435 implementation of those methods. 436 437 .. note:: 438 439 If you write test suites for your email backend you should use the 440 Python import path pointing to your email backend both for the 441 ``EMAIL_BACKEND`` and ``TEST_EMAIL_BACKEND`` in your settings file. 442 This will change the default email backend when running test suites. 443 444 445 Testing Email Sending 446 ===================== 356 447 357 448 The are times when you do not want Django to send e-mails at all. For example, 358 449 while developing a website, you probably don't want to send out thousands of … … 360 451 people under the right conditions, and that those e-mails will contain the 361 452 correct content. 362 453 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:: 454 The easiest way to test your project's use of e-mail is to change the 455 ``EMAIL_BACKEND`` setting in your settings file to ``'log'`` to use the 456 logging backend that doesn't send any email but writes them to the 457 terminal. 458 459 Alternatively use a "dumb" e-mail server that receives the e-mails locally 460 and displays them to the terminal, but does not actually send anything. 461 Python has a built-in way to accomplish this with a single command:: 367 462 368 463 python -m smtpd -n -c DebuggingServer localhost:1025 369 464 -
tests/regressiontests/mail/tests.py
diff -r fab52dcbb802 -r 864d5ad60910 tests/regressiontests/mail/tests.py
a b 142 142 # Make sure all builtin email backends load correctly. 143 143 >>> failed = [] 144 144 >>> for name in mail.BACKENDS: 145 ... settings. EMAIL_BACKEND = name145 ... settings.TEST_EMAIL_BACKEND = name 146 146 ... conn = mail.get_connection(foo='bar') 147 147 ... if not isinstance(conn, BaseEmailBackend): 148 148 ... failed.append(name) 149 149 >>> failed 150 150 [] 151 >>> settings. EMAIL_BACKEND = 'testenv' # reset test backend151 >>> settings.TEST_EMAIL_BACKEND = 'test' # reset test backend 152 152 153 153 # Make sure that get_connection() accepts arbitrary keyword that might be 154 154 # used with custom backends. -
django/core/mail/backends/base.py
Änderung: 7300:f480711a4050 Marke: tip Nutzer: Andi Albrecht <albrecht.andi@gmail.com> Datum: Mon Aug 24 14:39:16 2009 +0200 Zusammenfassung: Minor fixes after personal review. diff -r 864d5ad60910 -r f480711a4050 django/core/mail/backends/base.py
a b 15 15 """Open a network connection. 16 16 17 17 This method can be overwritten by backend implementations to 18 open a network connection. There's no guarantee that this method 19 is called before an attempt to send one or more emails is made. 20 But this method can be called by applications to force a single 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 21 24 network connection to be used when sending mails. See the 22 25 send_messages() method of the SMTP backend for a reference 23 26 implementation. -
django/core/mail/backends/test.py
diff -r 864d5ad60910 -r f480711a4050 django/core/mail/backends/test.py
a b 7 7 8 8 9 9 class EmailBackend(BaseEmailBackend): 10 """A substitute SMTP connection for use during test sessions. 10 """A email backend for use during test sessions. 11 11 12 The test connection stores email messages in a dummy outbox, 12 13 rather than sending them out on the wire. 13 14 15 The backend requires the 'django.core.mail' module to provide 16 a 'outbox' attribute (a list). This attribute is set during the setup 17 of the test environment and removed when the test environment is 18 teared down. 14 19 """ 15 20 16 21 def send_messages(self, messages): -
tests/modeltests/test_client/views.py
diff -r 864d5ad60910 -r f480711a4050 tests/modeltests/test_client/views.py
a b 203 203 ['second@example.com', 'third@example.com']) 204 204 205 205 c = mail.get_connection() 206 c.open()207 206 c.send_messages([m1,m2]) 208 c.close()209 207 210 208 return HttpResponse("Mail sent") -
tests/regressiontests/mail/tests.py
diff -r 864d5ad60910 -r f480711a4050 tests/regressiontests/mail/tests.py
a b 142 142 # Make sure all builtin email backends load correctly. 143 143 >>> failed = [] 144 144 >>> for name in mail.BACKENDS: 145 ... settings. TEST_EMAIL_BACKEND = name145 ... settings.EMAIL_BACKEND = name 146 146 ... conn = mail.get_connection(foo='bar') 147 147 ... if not isinstance(conn, BaseEmailBackend): 148 148 ... failed.append(name) 149 149 >>> failed 150 150 [] 151 >>> settings. TEST_EMAIL_BACKEND = 'test' # reset test backend151 >>> settings.EMAIL_BACKEND = 'test' # reset test backend 152 152 153 153 # Make sure that get_connection() accepts arbitrary keyword that might be 154 154 # used with custom backends.