Repository URL to install this package:
|
Version:
2.1 ▾
|
django-mailer
/
models.py
|
|---|
from __future__ import unicode_literals
import base64
import logging
import pickle
import datetime
import six
try:
from django.utils.encoding import python_2_unicode_compatible
except ImportError:
def python_2_unicode_compatible(c):
return c
from django.utils.timezone import now as datetime_now
from django.core.mail import EmailMessage
from django.db import models
if six.PY2:
from django.utils.translation import ugettext_lazy as _
else:
from django.utils.translation import gettext_lazy as _
PRIORITY_HIGH = 1
PRIORITY_MEDIUM = 2
PRIORITY_LOW = 3
PRIORITY_DEFERRED = 4
PRIORITIES = [
(PRIORITY_HIGH, "high"),
(PRIORITY_MEDIUM, "medium"),
(PRIORITY_LOW, "low"),
(PRIORITY_DEFERRED, "deferred"),
]
PRIORITY_MAPPING = dict((label, v) for (v, label) in PRIORITIES)
logger = logging.getLogger(__name__)
def get_message_id(msg):
# From django.core.mail.message: Email header names are case-insensitive
# (RFC 2045), so we have to accommodate that when doing comparisons.
for key, value in msg.extra_headers.items():
if key.lower() == 'message-id':
return value
class MessageManager(models.Manager):
def high_priority(self):
"""
the high priority messages in the queue
"""
return self.filter(priority=PRIORITY_HIGH)
def medium_priority(self):
"""
the medium priority messages in the queue
"""
return self.filter(priority=PRIORITY_MEDIUM)
def low_priority(self):
"""
the low priority messages in the queue
"""
return self.filter(priority=PRIORITY_LOW)
def non_deferred(self):
"""
the messages in the queue not deferred
"""
return self.exclude(priority=PRIORITY_DEFERRED)
def deferred(self):
"""
the deferred messages in the queue
"""
return self.filter(priority=PRIORITY_DEFERRED)
def retry_deferred(self, new_priority=PRIORITY_MEDIUM):
return self.deferred().update(priority=new_priority)
base64_encode = base64.encodebytes if hasattr(base64, 'encodebytes') else base64.encodestring
base64_decode = base64.decodebytes if hasattr(base64, 'decodebytes') else base64.decodestring
def email_to_db(email):
# pickle.dumps returns essentially binary data which we need to base64
# encode to store in a unicode field. finally we encode back to make sure
# we only try to insert unicode strings into the db, since we use a
# TextField
return base64_encode(pickle.dumps(email)).decode('ascii')
def db_to_email(data):
if data == "":
return None
else:
try:
data = data.encode("ascii")
except AttributeError:
pass
try:
return pickle.loads(base64_decode(data))
except (TypeError, pickle.UnpicklingError, base64.binascii.Error, AttributeError):
try:
# previous method was to just do pickle.dumps(val)
return pickle.loads(data)
except (TypeError, pickle.UnpicklingError, AttributeError):
return None
@python_2_unicode_compatible
class Message(models.Model):
"""
The email stored for later sending.
"""
# The actual data - a pickled EmailMessage
message_data = models.TextField()
when_added = models.DateTimeField(default=datetime_now)
priority = models.PositiveSmallIntegerField(choices=PRIORITIES, default=PRIORITY_MEDIUM)
objects = MessageManager()
class Meta:
verbose_name = _("message")
verbose_name_plural = _("messages")
def __str__(self):
try:
email = self.email
return "On {0}, \"{1}\" to {2}".format(self.when_added,
email.subject,
", ".join(email.to))
except Exception:
return "<Message repr unavailable>"
def defer(self):
self.priority = PRIORITY_DEFERRED
self.save()
def _get_email(self):
return db_to_email(self.message_data)
def _set_email(self, val):
self.message_data = email_to_db(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):
logger.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):
def has_address(self, address):
"""
is the given address on the don't send list?
"""
queryset = self.filter(to_address__iexact=address)
return queryset.exists()
class DontSendEntry(models.Model):
to_address = models.EmailField(max_length=254)
when_added = models.DateTimeField()
objects = DontSendEntryManager()
class Meta:
verbose_name = _("don't send entry")
verbose_name_plural = _("don't send entries")
RESULT_SUCCESS = "1"
RESULT_DONT_SEND = "2"
RESULT_FAILURE = "3"
RESULT_CODES = (
(RESULT_SUCCESS, "success"),
(RESULT_DONT_SEND, "don't send"),
(RESULT_FAILURE, "failure"),
# @@@ other types of failure?
)
class MessageLogManager(models.Manager):
def log(self, message, result_code, log_message=""):
"""
create a log entry for an attempt to send the given message and
record the given result and (optionally) a log message
"""
return self.create(
message_data=message.message_data,
message_id=get_message_id(message.email),
when_added=message.when_added,
priority=message.priority,
result=result_code,
log_message=log_message,
)
def purge_old_entries(self, days, result_codes=None):
if result_codes is None:
# retro-compatibility with previous versions
result_codes = [RESULT_SUCCESS]
limit = datetime_now() - datetime.timedelta(days=days)
query = self.filter(when_attempted__lt=limit, result__in=result_codes)
count = query.count()
query.delete()
return count
@python_2_unicode_compatible
class MessageLog(models.Model):
"""
A log entry which stores the result (and optionally a log message) for an
attempt to send a Message.
"""
# fields from Message
message_data = models.TextField()
message_id = models.TextField(editable=False, null=True)
when_added = models.DateTimeField(db_index=True)
priority = models.PositiveSmallIntegerField(choices=PRIORITIES, db_index=True)
# additional logging fields
when_attempted = models.DateTimeField(default=datetime_now)
result = models.CharField(max_length=1, choices=RESULT_CODES)
log_message = models.TextField()
objects = MessageLogManager()
class Meta:
verbose_name = _("message log")
verbose_name_plural = _("message logs")
def __str__(self):
try:
email = self.email
return "On {0}, \"{1}\" to {2}".format(self.when_attempted,
email.subject,
", ".join(email.to))
except Exception:
return "<MessageLog repr unavailable>"
@property
def email(self):
return db_to_email(self.message_data)
@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 ""