1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +00:00

boulder-oracle-sprint: Merged to [5147]

git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@5148 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Boulder Sprinters 2007-05-03 19:26:47 +00:00
parent 750d2e8c3c
commit 32683de27c
8 changed files with 259 additions and 55 deletions

View File

@ -119,6 +119,7 @@ EMAIL_PORT = 25
# Optional SMTP authentication information for EMAIL_HOST. # Optional SMTP authentication information for EMAIL_HOST.
EMAIL_HOST_USER = '' EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = '' EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
# List of strings representing installed apps. # List of strings representing installed apps.
INSTALLED_APPS = () INSTALLED_APPS = ()

View File

@ -1,14 +1,22 @@
# Use this module for e-mailing. """
Tools for sending email.
"""
from django.conf import settings from django.conf import settings
from email.MIMEText import MIMEText from email.MIMEText import MIMEText
from email.Header import Header from email.Header import Header
from email.Utils import formatdate from email.Utils import formatdate
from email import Charset
import os
import smtplib import smtplib
import socket import socket
import time import time
import random import random
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
# some spam filters.
Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
# seconds, which slows down the restart of the server. # seconds, which slows down the restart of the server.
class CachedDnsName(object): class CachedDnsName(object):
@ -22,6 +30,28 @@ class CachedDnsName(object):
DNS_NAME = CachedDnsName() DNS_NAME = CachedDnsName()
# Copied from Python standard library and modified to used the cached hostname
# for performance.
def make_msgid(idstring=None):
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
<20020201195627.33539.96671@nightshade.la.mastaler.com>
Optional idstring if given is a string used to strengthen the
uniqueness of the message id.
"""
timeval = time.time()
utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
pid = os.getpid()
randint = random.randrange(100000)
if idstring is None:
idstring = ''
else:
idstring = '.' + idstring
idhost = DNS_NAME
msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
return msgid
class BadHeaderError(ValueError): class BadHeaderError(ValueError):
pass pass
@ -34,6 +64,131 @@ class SafeMIMEText(MIMEText):
val = Header(val, settings.DEFAULT_CHARSET) val = Header(val, settings.DEFAULT_CHARSET)
MIMEText.__setitem__(self, name, val) MIMEText.__setitem__(self, name, val)
class SMTPConnection(object):
"""
A wrapper that manages the SMTP network connection.
"""
def __init__(self, host=None, port=None, username=None, password=None,
use_tls=None, fail_silently=False):
self.host = host or settings.EMAIL_HOST
self.port = port or settings.EMAIL_PORT
self.username = username or settings.EMAIL_HOST_USER
self.password = password or settings.EMAIL_HOST_PASSWORD
self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
self.fail_silently = fail_silently
self.connection = None
def open(self):
"""
Ensure we have a connection to the email server. Returns whether or not
a new connection was required.
"""
if self.connection:
# Nothing to do if the connection is already open.
return False
try:
self.connection = smtplib.SMTP(self.host, self.port)
if self.use_tls:
self.connection.ehlo()
self.connection.starttls()
self.connection.ehlo()
if self.username and self.password:
self.connection.login(self.username, self.password)
return True
except:
if not self.fail_silently:
raise
def close(self):
"""Close the connection to the email server."""
try:
try:
self.connection.quit()
except socket.sslerror:
# This happens when calling quit() on a TLS connection
# sometimes.
self.connection.close()
except:
if self.fail_silently:
return
raise
finally:
self.connection = None
def send_messages(self, email_messages):
"""
Send one or more EmailMessage objects and return the number of email
messages sent.
"""
if not email_messages:
return
new_conn_created = self.open()
if not self.connection:
# We failed silently on open(). Trying to send would be pointless.
return
num_sent = 0
for message in email_messages:
sent = self._send(message)
if sent:
num_sent += 1
if new_conn_created:
self.close()
return num_sent
def _send(self, email_message):
"""A helper method that does the actual sending."""
if not email_message.to:
return False
try:
self.connection.sendmail(email_message.from_email,
email_message.recipients(),
email_message.message().as_string())
except:
if not self.fail_silently:
raise
return False
return True
class EmailMessage(object):
"""
A container for email information.
"""
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, connection=None):
self.to = to or []
self.bcc = bcc or []
self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
self.subject = subject
self.body = body
self.connection = connection
def get_connection(self, fail_silently=False):
if not self.connection:
self.connection = SMTPConnection(fail_silently=fail_silently)
return self.connection
def message(self):
msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
msg['Subject'] = self.subject
msg['From'] = self.from_email
msg['To'] = ', '.join(self.to)
msg['Date'] = formatdate()
msg['Message-ID'] = make_msgid()
if self.bcc:
msg['Bcc'] = ', '.join(self.bcc)
return msg
def recipients(self):
"""
Returns a list of all recipients of the email (includes direct
addressees as well as Bcc entries).
"""
return self.to + self.bcc
def send(self, fail_silently=False):
"""Send the email message."""
return self.get_connection(fail_silently).send_messages([self])
def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None): def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None):
""" """
Easy wrapper for sending a single message to a recipient list. All members Easy wrapper for sending a single message to a recipient list. All members
@ -41,8 +196,13 @@ def send_mail(subject, message, from_email, recipient_list, fail_silently=False,
If auth_user is None, the EMAIL_HOST_USER setting is used. If auth_user is None, the EMAIL_HOST_USER setting is used.
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
NOTE: This method is deprecated. It exists for backwards compatibility.
New code should use the EmailMessage class directly.
""" """
return send_mass_mail([[subject, message, from_email, recipient_list]], fail_silently, auth_user, auth_password) connection = SMTPConnection(username=auth_user, password=auth_password,
fail_silently=fail_silently)
return EmailMessage(subject, message, from_email, recipient_list, connection=connection).send()
def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None): def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None):
""" """
@ -53,52 +213,24 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password
If auth_user and auth_password are set, they're used to log in. If auth_user and auth_password are set, they're used to log in.
If auth_user is None, the EMAIL_HOST_USER setting is used. If auth_user is None, the EMAIL_HOST_USER setting is used.
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
NOTE: This method is deprecated. It exists for backwards compatibility.
New code should use the EmailMessage class directly.
""" """
if auth_user is None: connection = SMTPConnection(username=auth_user, password=auth_password,
auth_user = settings.EMAIL_HOST_USER fail_silently=fail_silently)
if auth_password is None: messages = [EmailMessage(subject, message, sender, recipient) for subject, message, sender, recipient in datatuple]
auth_password = settings.EMAIL_HOST_PASSWORD return connection.send_messages(messages)
try:
server = smtplib.SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT)
if auth_user and auth_password:
server.login(auth_user, auth_password)
except:
if fail_silently:
return
raise
num_sent = 0
for subject, message, from_email, recipient_list in datatuple:
if not recipient_list:
continue
from_email = from_email or settings.DEFAULT_FROM_EMAIL
msg = SafeMIMEText(message, 'plain', settings.DEFAULT_CHARSET)
msg['Subject'] = subject
msg['From'] = from_email
msg['To'] = ', '.join(recipient_list)
msg['Date'] = formatdate()
try:
random_bits = str(random.getrandbits(64))
except AttributeError: # Python 2.3 doesn't have random.getrandbits().
random_bits = ''.join([random.choice('1234567890') for i in range(19)])
msg['Message-ID'] = "<%d.%s@%s>" % (time.time(), random_bits, DNS_NAME)
try:
server.sendmail(from_email, recipient_list, msg.as_string())
num_sent += 1
except:
if not fail_silently:
raise
try:
server.quit()
except:
if fail_silently:
return
raise
return num_sent
def mail_admins(subject, message, fail_silently=False): def mail_admins(subject, message, fail_silently=False):
"Sends a message to the admins, as defined by the ADMINS setting." "Sends a message to the admins, as defined by the ADMINS setting."
send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], fail_silently) EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
settings.SERVER_EMAIL, [a[1] for a in
settings.ADMINS]).send(fail_silently=fail_silently)
def mail_managers(subject, message, fail_silently=False): def mail_managers(subject, message, fail_silently=False):
"Sends a message to the managers, as defined by the MANAGERS setting." "Sends a message to the managers, as defined by the MANAGERS setting."
send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], fail_silently) EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
settings.SERVER_EMAIL, [a[1] for a in
settings.MANAGERS]).send(fail_silently=fail_silently)

View File

@ -94,12 +94,10 @@ Formatting
The text documentation is written in ReST (ReStructured Text) format. That The text documentation is written in ReST (ReStructured Text) format. That
means it's easy to read but is also formatted in a way that makes it easy to means it's easy to read but is also formatted in a way that makes it easy to
convert into other formats, such as HTML. If you're interested, the script that convert into other formats, such as HTML. If you have the `reStructuredText`_
converts the ReST text docs into djangoproject.com's HTML lives at library installed, you can use ``rst2html`` to generate your own HTML files.
`djangoproject.com/django_website/apps/docs/parts/build_documentation.py`_ in
the Django Subversion repository.
.. _djangoproject.com/django_website/apps/docs/parts/build_documentation.py: http://code.djangoproject.com/browser/djangoproject.com/django_website/apps/docs/parts/build_documentation.py .. _reStructuredText: http://docutils.sourceforge.net/rst.html
Differences between versions Differences between versions
============================ ============================

View File

@ -22,7 +22,8 @@ In two lines::
Mail will be sent using the SMTP host and port specified in the `EMAIL_HOST`_ Mail will be sent using the SMTP host and port specified in the `EMAIL_HOST`_
and `EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_ and `EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_
settings, if set, will be used to authenticate to the SMTP server. settings, if set, will be used to authenticate to the SMTP server and the
`EMAIL_USE_TLS`_ settings will control whether a secure connection is used.
.. note:: .. note::
@ -34,6 +35,7 @@ settings, if set, will be used to authenticate to the SMTP server.
.. _EMAIL_PORT: ../settings/#email-port .. _EMAIL_PORT: ../settings/#email-port
.. _EMAIL_HOST_USER: ../settings/#email-host-user .. _EMAIL_HOST_USER: ../settings/#email-host-user
.. _EMAIL_HOST_PASSWORD: ../settings/#email-host-password .. _EMAIL_HOST_PASSWORD: ../settings/#email-host-password
.. _EMAIL_USE_TLS: ../settings/#email-use-tls
send_mail() send_mail()
@ -183,3 +185,65 @@ from the request's POST data, sends that to admin@example.com and redirects to
return HttpResponse('Make sure all fields are entered and valid.') return HttpResponse('Make sure all fields are entered and valid.')
.. _Header injection: http://securephp.damonkohler.com/index.php/Email_Injection .. _Header injection: http://securephp.damonkohler.com/index.php/Email_Injection
The EmailMessage and SMTPConnection classes
===========================================
**New in Django development version**
Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
in ``django.mail``. If you ever need to customize the way Django sends email,
you can subclass these two classes to suit your needs.
.. note::
Not all features of the ``EmailMessage`` class are available through the
``send_mail()`` and related wrapper functions. If you wish to use advanced
features such as including BCC recipients or multi-part email, you will
need to create ``EmailMessage`` instances directly.
In general, ``EmailMessage`` is responsible for creating the email message
itself. ``SMTPConnection`` is responsible for the network connection side of
the operation. This means you can reuse the same connection (an
``SMTPConnection`` instance) for multiple messages.
The ``EmailMessage`` class is initialised as follows::
email = EmailMessage(subject, body, from_email, to, bcc, connection)
All of these parameters are optional. If ``from_email`` is omitted, the value
from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc``
parameters are lists of addresses.
The class has the following methods that you can use:
* ``send()`` sends the message, using either the connection that is specified
in the ``connection`` attribute, or creating a new connection if none already
exists.
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the
message to be sent. If you ever need to extend the `EmailMessage` class,
you will probably want to override this method to put the content you wish
into the MIME object.
* ``recipients()`` returns a lists of all the recipients of the message,
whether they are recorded in the ``to`` or ``bcc`` attributes. This is
another method you need to possibly override when sub-classing, since the
SMTP server needs to be told the full list of recipients when the message
is sent. If you add another way to specify recipients in your class, they
need to be returned from this method as well.
The ``SMTPConnection`` class is initialized with the host, port, username and
password for the SMTP server. If you don't specify one or more of those
options, they are read from your settings file.
If you are sending lots of messages at once, the ``send_messages()`` method of
the ``SMTPConnection`` class will be useful. It takes a list of ``EmailMessage``
instances (or sub-classes) and sends them over a single connection. For
example, if you have a function called ``get_notification_email()`` that returns a
list of ``EmailMessage`` objects representing some periodic email you wish to
send out, you could send this with::
connection = SMTPConnection() # Use default settings for connection
messages = get_notification_email()
connection.send_messages(messages)

View File

@ -107,7 +107,7 @@ posts a comment. It doesn't let a user post a comment more than once::
This simplistic view logs in a "member" of the site:: This simplistic view logs in a "member" of the site::
def login(request): def login(request):
m = members.get_object(username__exact=request.POST['username']) m = Member.objects.get(username=request.POST['username'])
if m.password == request.POST['password']: if m.password == request.POST['password']:
request.session['member_id'] = m.id request.session['member_id'] = m.id
return HttpResponse("You're logged in.") return HttpResponse("You're logged in.")

View File

@ -428,6 +428,15 @@ Subject-line prefix for e-mail messages sent with ``django.core.mail.mail_admins
or ``django.core.mail.mail_managers``. You'll probably want to include the or ``django.core.mail.mail_managers``. You'll probably want to include the
trailing space. trailing space.
EMAIL_USE_TLS
-------------
**New in Django development version**
Default: ``False``
Whether to use a TLS (secure) connection when talking to the SMTP server.
FIXTURE_DIRS FIXTURE_DIRS
------------- -------------

View File

@ -646,15 +646,15 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas
def item_enclosure_mime_type(self, item): def item_enclosure_mime_type(self, item):
""" """
Takes an item, as returned by items(), and returns the item's Takes an item, as returned by items(), and returns the item's
enclosure mime type. enclosure MIME type.
""" """
def item_enclosure_mime_type(self): def item_enclosure_mime_type(self):
""" """
Returns the enclosure length, in bytes, for every item in the feed. Returns the enclosure MIME type for every item in the feed.
""" """
item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure mime-type. item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure MIME type.
# ITEM PUBDATE -- It's optional to use one of these three. This is a # ITEM PUBDATE -- It's optional to use one of these three. This is a
# hook that specifies how to get the pubdate for a given item. # hook that specifies how to get the pubdate for a given item.

View File

@ -345,7 +345,7 @@ If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
``request.user.get_and_delete_messages()`` for every request. That method ``request.user.get_and_delete_messages()`` for every request. That method
collects the user's messages and deletes them from the database. collects the user's messages and deletes them from the database.
Note that messages are set with ``user.add_message()``. See the Note that messages are set with ``user.message_set.create``. See the
`message docs`_ for more. `message docs`_ for more.
* ``perms`` -- An instance of * ``perms`` -- An instance of