Django

Code

root/django/branches/gis/django/core/mail.py

Revision 8215, 13.6 kB (checked in by jbronn, 4 months ago)

gis: Merged revisions 7981-8001,8003-8011,8013-8033,8035-8036,8038-8039,8041-8063,8065-8076,8078-8139,8141-8154,8156-8214 via svnmerge from trunk.

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedRevision
Line 
1 """
2 Tools for sending email.
3 """
4
5 import mimetypes
6 import os
7 import smtplib
8 import socket
9 import time
10 import random
11 from email import Charset, Encoders
12 from email.MIMEText import MIMEText
13 from email.MIMEMultipart import MIMEMultipart
14 from email.MIMEBase import MIMEBase
15 from email.Header import Header
16 from email.Utils import formatdate, parseaddr, formataddr
17
18 from django.conf import settings
19 from django.utils.encoding import smart_str, force_unicode
20
21 # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
22 # 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 given
26 # 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 of
30 # 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._fqdn
39
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 the
51     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 = 1
60     randint = random.randrange(100000)
61     if idstring is None:
62         idstring = ''
63     else:
64         idstring = '.' + idstring
65     idhost = DNS_NAME
66     msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
67     return msgid
68
69 class BadHeaderError(ValueError):
70     pass
71
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     return name, val
90
91 class SafeMIMEText(MIMEText):
92     def __setitem__(self, name, val):
93         name, val = forbid_multi_line_headers(name, val)
94         MIMEText.__setitem__(self, name, val)
95
96 class SafeMIMEMultipart(MIMEMultipart):
97     def __setitem__(self, name, val):
98         name, val = forbid_multi_line_headers(name, val)
99         MIMEMultipart.__setitem__(self, name, val)
100
101 class SMTPConnection(object):
102     """
103     A wrapper that manages the SMTP network connection.
104     """
105
106     def __init__(self, host=None, port=None, username=None, password=None,
107                  use_tls=None, fail_silently=False):
108         self.host = host or settings.EMAIL_HOST
109         self.port = port or settings.EMAIL_PORT
110         self.username = username or settings.EMAIL_HOST_USER
111         self.password = password or settings.EMAIL_HOST_PASSWORD
112         self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
113         self.fail_silently = fail_silently
114         self.connection = None
115
116     def open(self):
117         """
118         Ensures we have a connection to the email server. Returns whether or
119         not a new connection was required (True or False).
120         """
121         if self.connection:
122             # Nothing to do if the connection is already open.
123             return False
124         try:
125             # If local_hostname is not specified, socket.getfqdn() gets used.
126             # For performance, we use the cached FQDN for local_hostname.
127             self.connection = smtplib.SMTP(self.host, self.port,
128                                            local_hostname=DNS_NAME.get_fqdn())
129             if self.use_tls:
130                 self.connection.ehlo()
131                 self.connection.starttls()
132                 self.connection.ehlo()
133             if self.username and self.password:
134                 self.connection.login(self.username, self.password)
135             return True
136         except:
137             if not self.fail_silently:
138                 raise
139
140     def close(self):
141         """Closes the connection to the email server."""
142         try:
143             try:
144                 self.connection.quit()
145             except socket.sslerror:
146                 # This happens when calling quit() on a TLS connection
147                 # sometimes.
148                 self.connection.close()
149             except:
150                 if self.fail_silently:
151                     return
152                 raise
153         finally:
154             self.connection = None
155
156     def send_messages(self, email_messages):
157         """
158         Sends one or more EmailMessage objects and returns the number of email
159         messages sent.
160         """
161         if not email_messages:
162             return
163         new_conn_created = self.open()
164         if not self.connection:
165             # We failed silently on open(). Trying to send would be pointless.
166             return
167         num_sent = 0
168         for message in email_messages:
169             sent = self._send(message)
170             if sent:
171                 num_sent += 1
172         if new_conn_created:
173             self.close()
174         return num_sent
175
176     def _send(self, email_message):
177         """A helper method that does the actual sending."""
178         if not email_message.recipients():
179             return False
180         try:
181             self.connection.sendmail(email_message.from_email,
182                     email_message.recipients(),
183                     email_message.message().as_string())
184         except:
185             if not self.fail_silently:
186                 raise
187             return False
188         return True
189
190 class EmailMessage(object):
191     """
192     A container for email information.
193     """
194     content_subtype = 'plain'
195     multipart_subtype = 'mixed'
196     encoding = None     # None => use settings default
197
198     def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
199             connection=None, attachments=None, headers=None):
200         """
201         Initialize a single email message (which can be sent to multiple
202         recipients).
203
204         All strings used to create the message can be unicode strings (or UTF-8
205         bytestrings). The SafeMIMEText class will handle any necessary encoding
206         conversions.
207         """
208         if to:
209             assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
210             self.to = list(to)
211         else:
212             self.to = []
213         if bcc:
214             assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
215             self.bcc = list(bcc)
216         else:
217             self.bcc = []
218         self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
219         self.subject = subject
220         self.body = body
221         self.attachments = attachments or []
222         self.extra_headers = headers or {}
223         self.connection = connection
224
225     def get_connection(self, fail_silently=False):
226         if not self.connection:
227             self.connection = SMTPConnection(fail_silently=fail_silently)
228         return self.connection
229
230     def message(self):
231         encoding = self.encoding or settings.DEFAULT_CHARSET
232         msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
233                            self.content_subtype, encoding)
234         if self.attachments:
235             body_msg = msg
236             msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
237             if self.body:
238                 msg.attach(body_msg)
239             for attachment in self.attachments:
240                 if isinstance(attachment, MIMEBase):
241                     msg.attach(attachment)
242                 else:
243                     msg.attach(self._create_attachment(*attachment))
244         msg['Subject'] = self.subject
245         msg['From'] = self.from_email
246         msg['To'] = ', '.join(self.to)
247         msg['Date'] = formatdate()
248         msg['Message-ID'] = make_msgid()
249         for name, value in self.extra_headers.items():
250             msg[name] = value
251         return msg
252
253     def recipients(self):
254         """
255         Returns a list of all recipients of the email (includes direct
256         addressees as well as Bcc entries).
257         """
258         return self.to + self.bcc
259
260     def send(self, fail_silently=False):
261         """Sends the email message."""
262         return self.get_connection(fail_silently).send_messages([self])
263
264     def attach(self, filename=None, content=None, mimetype=None):
265         """
266         Attaches a file with the given filename and content. The filename can
267         be omitted (useful for multipart/alternative messages) and the mimetype
268         is guessed, if not provided.
269
270         If the first parameter is a MIMEBase subclass it is inserted directly
271         into the resulting message attachments.
272         """
273         if isinstance(filename, MIMEBase):
274             assert content == mimetype == None
275             self.attachments.append(filename)
276         else:
277             assert content is not None
278             self.attachments.append((filename, content, mimetype))
279
280     def attach_file(self, path, mimetype=None):
281         """Attaches a file from the filesystem."""
282         filename = os.path.basename(path)
283         content = open(path, 'rb').read()
284         self.attach(filename, content, mimetype)
285
286     def _create_attachment(self, filename, content, mimetype=None):
287         """
288         Converts the filename, content, mimetype triple into a MIME attachment
289         object.
290         """
291         if mimetype is None:
292             mimetype, _ = mimetypes.guess_type(filename)
293             if mimetype is None:
294                 mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
295         basetype, subtype = mimetype.split('/', 1)
296         if basetype == 'text':
297             attachment = SafeMIMEText(smart_str(content,
298                 settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
299         else:
300             # Encode non-text attachments with base64.
301             attachment = MIMEBase(basetype, subtype)
302             attachment.set_payload(content)
303             Encoders.encode_base64(attachment)
304         if filename:
305             attachment.add_header('Content-Disposition', 'attachment',
306                                   filename=filename)
307         return attachment
308
309 class EmailMultiAlternatives(EmailMessage):
310     """
311     A version of EmailMessage that makes it easy to send multipart/alternative
312     messages. For example, including text and HTML versions of the text is
313     made easier.
314     """
315     multipart_subtype = 'alternative'
316
317     def attach_alternative(self, content, mimetype=None):
318         """Attach an alternative content representation."""
319         self.attach(content=content, mimetype=mimetype)
320
321 def send_mail(subject, message, from_email, recipient_list,
322               fail_silently=False, auth_user=None, auth_password=None):
323     """
324     Easy wrapper for sending a single message to a recipient list. All members
325     of the recipient list will see the other recipients in the 'To' field.
326
327     If auth_user is None, the EMAIL_HOST_USER setting is used.
328     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
329
330     Note: The API for this method is frozen. New code wanting to extend the
331     functionality should use the EmailMessage class directly.
332     """
333     connection = SMTPConnection(username=auth_user, password=auth_password,
334                                 fail_silently=fail_silently)
335     return EmailMessage(subject, message, from_email, recipient_list,
336                         connection=connection).send()
337
338 def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
339                    auth_password=None):
340     """
341     Given a datatuple of (subject, message, from_email, recipient_list), sends
342     each message to each recipient list. Returns the number of e-mails sent.
343
344     If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
345     If auth_user and auth_password are set, they're used to log in.
346     If auth_user is None, the EMAIL_HOST_USER setting is used.
347     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
348
349     Note: The API for this method is frozen. New code wanting to extend the
350     functionality should use the EmailMessage class directly.
351     """
352     connection = SMTPConnection(username=auth_user, password=auth_password,
353                                 fail_silently=fail_silently)
354     messages = [EmailMessage(subject, message, sender, recipient)
355                 for subject, message, sender, recipient in datatuple]
356     return connection.send_messages(messages)
357
358 def mail_admins(subject, message, fail_silently=False):
359     """Sends a message to the admins, as defined by the ADMINS setting."""
360     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
361                  settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
362                  ).send(fail_silently=fail_silently)
363
364 def mail_managers(subject, message, fail_silently=False):
365     """Sends a message to the managers, as defined by the MANAGERS setting."""
366     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
367                  settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
368                  ).send(fail_silently=fail_silently)
Note: See TracBrowser for help on using the browser.