# HG changeset patch # User Brian Neal # Date 1269203613 0 # Node ID aef00df9116510808db715368cb4ecaaaf183eb9 # Parent 70b2e307c8665f5d99642862437b737034265e7c Implement #63, add a queued email facility. diff -r 70b2e307c866 -r aef00df91165 gpp/core/functions.py --- a/gpp/core/functions.py Wed Mar 17 03:18:53 2010 +0000 +++ b/gpp/core/functions.py Sun Mar 21 20:33:33 2010 +0000 @@ -13,7 +13,9 @@ log all emails. """ - if settings.GPP_SEND_EMAIL: + if settings.MAILER_ENQUEUE_MAIL: + mailer.enqueue_mail(subject, message, from_email, recipient_list) + elif settings.GPP_SEND_EMAIL: django.core.mail.send_mail(subject, message, from_email, recipient_list, fail_silently, auth_user, auth_password) diff -r 70b2e307c866 -r aef00df91165 gpp/mailer/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/mailer/__init__.py Sun Mar 21 20:33:33 2010 +0000 @@ -0,0 +1,52 @@ +from socket import error as socket_error +import smtplib +import time + +import django.core.mail + +import mailer.models + + +def enqueue_mail(subject, message, from_email, recipient_list): + """Creates mailer mail queue entries for the given email.""" + + if len(subject) > mailer.models.MAX_SUBJECT: + subject = u'%s...' % subject[:mailer.models.MAX_SUBJECT - 3] + + for recipient in recipient_list: + mailer.models.Message( + from_address=from_email, + to_address=recipient, + subject=subject, + body=message).save() + + +def send_queued_mail(): + """Reads the queued messages from the database, sends them, and removes them + from the queue.""" + + sent, errors = 0, 0 + + import logging + logging.debug("Sending queued mail...") + start_time = time.time() + + msgs = mailer.models.Message.objects.all() + for msg in msgs: + try: + django.core.mail.send_mail( + msg.subject, + msg.body, + msg.from_address, + [msg.to_address], + fail_silently=False) + except (socket_error, smtplib.SMTPException), e: + errors += 1 + logging.error("Error sending queued mail: %s" % e) + else: + sent += 1 + msg.delete() + + end_time = time.time() + logging.debug("Sent queued mail: %d successful, %d error(s); elapsed time: %.2f" % ( + sent, errors, end_time - start_time)) diff -r 70b2e307c866 -r aef00df91165 gpp/mailer/admin.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/mailer/admin.py Sun Mar 21 20:33:33 2010 +0000 @@ -0,0 +1,13 @@ +"""This file contains the automatic admin site definitions for the mailer +application.""" +from django.contrib import admin + +from mailer.models import Message + + +class MessageAdmin(admin.ModelAdmin): + list_display = ('from_address', 'to_address', 'subject', 'creation_date') + list_display_links = ('subject', ) + + +admin.site.register(Message, MessageAdmin) diff -r 70b2e307c866 -r aef00df91165 gpp/mailer/management/commands/send_mail.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/mailer/management/commands/send_mail.py Sun Mar 21 20:33:33 2010 +0000 @@ -0,0 +1,15 @@ +"""send_mail is a custom manage.py command for the mailer application. +It is intended to be called from a cron job periodically to send out queued +email in bulk. This avoids doing the mailing on the HTTP request processing. +""" +from django.core.management.base import NoArgsCommand + +import mailer + + +class Command(NoArgsCommand): + help = "Run periodically to send out queued email." + + def handle_noargs(self, **options): + mailer.send_queued_mail() + diff -r 70b2e307c866 -r aef00df91165 gpp/mailer/models.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/mailer/models.py Sun Mar 21 20:33:33 2010 +0000 @@ -0,0 +1,27 @@ +"""Models for the mailer application.""" +import datetime + +from django.db import models + + +MAX_SUBJECT = 120 + +class Message(models.Model): + """The model to represent stored emails in the database.""" + from_address = models.EmailField() + to_address = models.EmailField() + subject = models.CharField(max_length=MAX_SUBJECT) + body = models.TextField() + creation_date = models.DateTimeField() + + class Meta: + ordering = ('creation_date', ) + + def __unicode__(self): + return u'From: %s, To: %s, Subj: %s' % ( + self.from_address, self.to_address, self.subject) + + def save(self, *args, **kwargs): + if self.id is None: + self.creation_date = datetime.datetime.now() + super(Message, self).save(*args, **kwargs) diff -r 70b2e307c866 -r aef00df91165 gpp/settings.py --- a/gpp/settings.py Wed Mar 17 03:18:53 2010 +0000 +++ b/gpp/settings.py Sun Mar 21 20:33:33 2010 +0000 @@ -117,6 +117,7 @@ 'forums', 'gcalendar', 'irc', + 'mailer', 'membermap', 'messages', 'news', @@ -141,6 +142,12 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' ####################################################################### +# Email +####################################################################### +EMAIL_HOST = local_settings.EMAIL_HOST +EMAIL_PORT = local_settings.EMAIL_PORT + +####################################################################### # Caching ####################################################################### if local_settings.USE_CACHE: @@ -159,7 +166,7 @@ # GPP Specific Settings ####################################################################### GPP_LOG_LEVEL = 0 -GPP_SEND_EMAIL = local_settings.GPP_SEND_EMAIL +GPP_SEND_EMAIL = local_settings.GPP_SEND_EMAIL # see MAILER_ENQUEUE_MAIL GPP_NO_REPLY_EMAIL = 'no_reply' AVATAR_DIR = 'avatars' MAX_AVATAR_SIZE_BYTES = 2 * 1024 * 1024 @@ -176,6 +183,16 @@ DONATIONS_ITEM_NUM = '500' # donation w/name listed DONATIONS_ITEM_ANON_NUM = '501' # donation listed as anonymous +# If MAILER_ENQUEUE_MAIL is True, all emails will be stored in the +# mailer application's mail queue (database table). It is then expected +# that a daemon or cron job will actually send the mail out. If +# MAILER_ENQUEUE_MAIL is False, then email will only be sent if +# the setting GPP_SEND_EMAIL (above) is True. In any event, emails +# will be logged via the Python logger if the Python logger filter +# DEBUG is active. + +MAILER_ENQUEUE_MAIL = True + ####################################################################### # Configure Logging #######################################################################