Commits

Luke Plant committed 2f5c7b6

Re-implemented database storage of email data, storing pickled EmailMessage instead of individual fields.

At the same time, the handling of recipient list was corrected - if
send_mail is called with multiple recipients, it should send an e-mail that
has all the recipients listed under 'to', not just one of them.

Related changes to database logging of e-mails were also made.

Comments (0)

Files changed (4)

mailer/__init__.py

 def send_mail(subject, message, from_email, recipient_list, priority="medium",
               fail_silently=False, auth_user=None, auth_password=None):
     from django.utils.encoding import force_unicode
-    from mailer.models import Message
+    from mailer.models import make_message
     
     priority = PRIORITY_MAPPING[priority]
     
     subject = force_unicode(subject)
     message = force_unicode(message)
     
-    if len(subject) > 100:
-        subject = u"%s..." % subject[:97]
-    
-    for to_address in recipient_list:
-        Message(to_address=to_address,
-                from_address=from_email,
-                subject=subject,
-                message_body=message,
-                priority=priority).save()
+    make_message(subject=subject,
+                 body=message,
+                 from_email=from_email,
+                 to=recipient_list,
+                 priority=priority).save()
 
 
 def send_html_mail(subject, message, message_html, from_email, recipient_list,
     Function to queue HTML e-mails
     """
     from django.utils.encoding import force_unicode
-    from mailer.models import Message
+    from django.core.mail import EmailMultiAlternatives
+    from mailer.models import make_message
     
     priority = PRIORITY_MAPPING[priority]
     
     # need to do this in case subject used lazy version of ugettext
     subject = force_unicode(subject)
+    message = force_unicode(message)
     
-    for to_address in recipient_list:
-        Message(to_address=to_address,
-                from_address=from_email,
-                subject=subject,
-                message_body=message,
-                message_body_html=message_html,
-                priority=priority).save()
+    msg = make_message(subject=subject,
+                       body=message,
+                       from_email=from_email,
+                       to=recipient_list,
+                       priority=priority)
+    email = msg.email
+    email = EmailMultiAlternatives(email.subject, email.body, email.from_email, email.to)
+    email.attach_alternative(message_html, "text/html")
+    msg.email = email
+    msg.save()
 
 
 def mail_admins(subject, message, fail_silently=False, priority="medium"):
-    from django.utils.encoding import force_unicode
     from django.conf import settings
-    from mailer.models import Message
-    
-    priority = PRIORITY_MAPPING[priority]
-    
-    subject = settings.EMAIL_SUBJECT_PREFIX + force_unicode(subject)
-    message = force_unicode(message)
-    
-    if len(subject) > 100:
-        subject = u"%s..." % subject[:97]
-    
-    for name, to_address in settings.ADMINS:
-        Message(to_address=to_address,
-                from_address=settings.SERVER_EMAIL,
-                subject=subject,
-                message_body=message,
-                priority=priority).save()
+    from django.utils.encoding import force_unicode
+
+    send_mail(settings.EMAIL_SUBJECT_PREFIX + force_unicode(subject),
+              message,
+              settings.SERVER_EMAIL,
+              [a[1] for a in settings.ADMINS])
 
 
 def mail_managers(subject, message, fail_silently=False, priority="medium"):
-    from django.utils.encoding import force_unicode
     from django.conf import settings
-    from mailer.models import Message
-    
-    priority = PRIORITY_MAPPING[priority]
-    
-    subject = settings.EMAIL_SUBJECT_PREFIX + force_unicode(subject)
-    message = force_unicode(message)
-    
-    if len(subject) > 100:
-        subject = u"%s..." % subject[:97]
-    
-    for name, to_address in settings.MANAGERS:
-        Message(to_address=to_address,
-                from_address=settings.SERVER_EMAIL,
-                subject=subject,
-                message_body=message,
-                priority=priority).save()
+    from django.utils.encoding import force_unicode
+
+    send_mail(settings.EMAIL_SUBJECT_PREFIX + force_unicode(subject),
+              message,
+              settings.SERVER_EMAIL,
+              [a[1] for a in settings.MANAGERS])
 from mailer.models import Message, DontSendEntry, MessageLog
 
 class MessageAdmin(admin.ModelAdmin):
-    list_display = ('id', 'to_address', 'subject', 'when_added', 'priority')
+    list_display = ('id', 'to_addresses', 'subject', 'when_added', 'priority')
 
 class DontSendEntryAdmin(admin.ModelAdmin):
     list_display = ('to_address', 'when_added')
 
 class MessageLogAdmin(admin.ModelAdmin):
-    list_display = ('id', 'to_address', 'subject', 'when_attempted', 'result')
+    list_display = ('id', 'to_addresses', 'subject', 'when_attempted', 'result')
 
 admin.site.register(Message, MessageAdmin)
 admin.site.register(DontSendEntry, DontSendEntryAdmin)
 
 from django.conf import settings
 from django.core.mail import send_mail as core_send_mail
-from django.core.mail import EmailMultiAlternatives
 
 # when queue is empty, how long to wait (in seconds) before checking again
 EMPTY_QUEUE_SLEEP = getattr(settings, "MAILER_EMPTY_QUEUE_SLEEP", 30)
     
     try:
         for message in prioritize():
-            if DontSendEntry.objects.has_address(message.to_address):
-                logging.info("skipping email to %s as on don't send list " % message.to_address.encode("utf-8"))
-                MessageLog.objects.log(message, 2) # @@@ avoid using literal result code
+            try:
+                logging.info("sending message '%s' to %s" % (message.subject.encode("utf-8"), u", ".join(message.to_addresses).encode("utf-8")))
+                email = message.email
+                email.send()
+                MessageLog.objects.log(message, 1) # @@@ avoid using literal result code
                 message.delete()
-                dont_send += 1
-            else:
-                try:
-                    logging.info("sending message '%s' to %s" % (message.subject.encode("utf-8"), message.to_address.encode("utf-8")))
-                    if not message.message_body_html:
-                        core_send_mail(message.subject, message.message_body, message.from_address, [message.to_address])
-                    else:
-                        email = EmailMultiAlternatives(message.subject, message.message_body, message.from_address, [message.to_address])
-                        email.attach_alternative(message.message_body_html, "text/html")
-                        email.send()
-                    MessageLog.objects.log(message, 1) # @@@ avoid using literal result code
-                    message.delete()
-                    sent += 1
-                except (socket_error, smtplib.SMTPSenderRefused, smtplib.SMTPRecipientsRefused, smtplib.SMTPAuthenticationError), err:
-                    message.defer()
-                    logging.info("message deferred due to failure: %s" % err)
-                    MessageLog.objects.log(message, 3, log_message=str(err)) # @@@ avoid using literal result code
-                    deferred += 1
+                sent += 1
+            except (socket_error, smtplib.SMTPSenderRefused, smtplib.SMTPRecipientsRefused, smtplib.SMTPAuthenticationError), err:
+                message.defer()
+                logging.info("message deferred due to failure: %s" % err)
+                MessageLog.objects.log(message, 3, log_message=str(err)) # @@@ avoid using literal result code
+                deferred += 1
     finally:
         logging.debug("releasing lock...")
         lock.release()
         logging.debug("released.")
     
     logging.info("")
-    logging.info("%s sent; %s deferred; %s don't send" % (sent, deferred, dont_send))
+    logging.info("%s sent; %s deferred;" % (sent, deferred))
     logging.info("done in %.2f seconds" % (time.time() - start_time))
 
 def send_loop():
 from datetime import datetime
-
+from django.core.mail import EmailMessage
 from django.db import models
-
+import logging
+import pickle
 
 PRIORITIES = (
     ('1', 'high'),
     
     objects = MessageManager()
     
-    to_address = models.EmailField()
-    from_address = models.EmailField()
-    subject = models.CharField(max_length=100)
-    message_body = models.TextField()
-    message_body_html = models.TextField(blank=True)
+    # The actual data - a pickled EmailMessage
+    message_data = models.TextField()
     when_added = models.DateTimeField(default=datetime.now)
     priority = models.CharField(max_length=1, choices=PRIORITIES, default='2')
     # @@@ campaign?
     # @@@ content_type?
-    
+
     def defer(self):
         self.priority = '4'
         self.save()
         else:
             return False
 
+    def _get_email(self):
+        if self.message_data == "":
+            return None
+        else:
+            return pickle.loads(self.message_data.encode('ascii'))
+
+    def _set_email(self, val):
+        self.message_data = pickle.dumps(val)
+
+    email = property(_get_email, _set_email, doc=
+                     """EmailMessage object. If this is mutated, you will need to
+set the attribute again to cause the underlying serialised data to be updated.""")
+
+    @property
+    def to_addresses(self):
+        email = self.email
+        if email is not None:
+            return email.to
+        else:
+            return []
+
+    @property
+    def subject(self):
+        email = self.email
+        if email is not None:
+            return email.subject
+        else:
+            return ''
+
+
+def filter_recipient_list(lst):
+    if lst is None:
+        return None
+    retval = []
+    for e in lst:
+        if DontSendEntry.objects.has_address(e):
+            logging.info("skipping email to %s as on don't send list " % e.encode("utf-8"))
+        else:
+            retval.append(e)
+    return retval
+
+
+def make_message(subject='', body='', from_email=None, to=None, bcc=None,
+                 attachments=None, headers=None, priority=None):
+    """
+    Creates a simple message for the email parameters supplied.
+    The 'to' and 'bcc' lists are filtered using DontSendEntry.
+
+    If needed, the 'email' attribute can be set to any instance of EmailMessage
+    if e-mails with attachments etc. need to be supported.
+
+    Call 'save()' on the result when it is ready to be sent, and not before.
+    """
+    to = filter_recipient_list(to)
+    bcc = filter_recipient_list(bcc)
+    core_msg = EmailMessage(subject=subject, body=body, from_email=from_email,
+                            to=to, bcc=bcc, attachments=attachments, headers=headers)
+
+    db_msg = Message(priority=priority)
+    db_msg.email = core_msg
+    return db_msg
+
 
 class DontSendEntryManager(models.Manager):
     
         """
         
         message_log = self.create(
-            to_address = message.to_address,
-            from_address = message.from_address,
-            subject = message.subject,
-            message_body = message.message_body,
-            message_body_html = message.message_body_html,
+            message_data = message.message_data,
             when_added = message.when_added,
             priority = message.priority,
             # @@@ other fields from Message
     objects = MessageLogManager()
     
     # fields from Message
-    to_address = models.EmailField()
-    from_address = models.EmailField()
-    subject = models.CharField(max_length=100)
-    message_body = models.TextField()
-    message_body_html = models.TextField(blank=True)
+    message_data = models.TextField()
     when_added = models.DateTimeField()
     priority = models.CharField(max_length=1, choices=PRIORITIES)
     # @@@ campaign?
     result = models.CharField(max_length=1, choices=RESULT_CODES)
     log_message = models.TextField()
     
+    @property
+    def email(self):
+        if self.message_data == "":
+            return None
+        else:
+            return pickle.loads(self.message_data.encode('ascii'))
+
+    @property
+    def to_addresses(self):
+        email = self.email
+        if email is not None:
+            return email.to
+        else:
+            return []
+
+    @property
+    def subject(self):
+        email = self.email
+        if email is not None:
+            return email.subject
+        else:
+            return ''