Newer
Older
__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import ArrayField
from .managers import MailLogQuerySet
MAIL_NOT_RENDERED, MAIL_RENDERED = "not_rendered", "rendered"
MAIL_SENT = "sent"
(MAIL_NOT_RENDERED, "Not rendered"),
(MAIL_RENDERED, "Rendered"),
(MAIL_SENT, "Sent"),
class MailLog(models.Model):
"""
The MailLog table is meant as a container of mails.
Mails are not directly sent, but added to this table first.
Using a cronjob, the unsent messages are eventually sent using
A mail can be of two types:
- Single: This log entry represents a single mail (optionally with multiple recipients)
- Bulk: This log entry represents an array of (identical) mails,
each with a single recipient, e.g. announcements or newsletters.
TYPE_SINGLE = "single"
TYPE_BULK = "bulk"
TYPE_CHOICES = (
(TYPE_SINGLE, "Single"),
(TYPE_BULK, "Bulk"),
)
processed = models.BooleanField(default=False)
type = models.CharField(max_length=64, choices=TYPE_CHOICES, default=TYPE_SINGLE)
status = models.CharField(
max_length=16, choices=MAIL_STATUSES, default=MAIL_RENDERED
)
mail_code = models.CharField(max_length=254, blank=True)
body = models.TextField()
body_html = models.TextField(blank=True)
to_recipients = ArrayField(models.EmailField(), blank=True, null=True)
cc_recipients = ArrayField(models.EmailField(), blank=True, null=True)
bcc_recipients = ArrayField(models.EmailField(), blank=True, null=True)
sent_to = ArrayField(models.EmailField(), blank=True, null=True)
from_email = models.CharField(max_length=254, blank=True)
subject = models.CharField(max_length=254, blank=True)
created = models.DateTimeField(auto_now_add=True)
latest_activity = models.DateTimeField(auto_now=True)
objects = MailLogQuerySet.as_manager()
def __str__(self):
nr_recipients = self.get_number_of_recipients()
if self.type == self.TYPE_SINGLE:
recipients_str = f"{nr_recipients} recipients"
elif self.type == self.TYPE_BULK:
nr_sent = len(self.sent_to) if self.sent_to else 0
recipients_str = f"{nr_sent}/{nr_recipients} recipients"
return "{id}. {subject} ({recipients_str})".format(
id=self.id,
subject=self.subject[:30],
def get_number_of_recipients(self):
def sum_optional(*args):
return sum([len(arg) for arg in args if arg])
if self.type == self.TYPE_SINGLE:
return sum_optional(
self.to_recipients, self.cc_recipients, self.bcc_recipients
)
elif self.type == self.TYPE_BULK:
return sum_optional(self.to_recipients)
return 0
def get_full_context(self):
"""Get the full template context needed to render the template."""
if hasattr(self, "_context"):
return self._context
self._context = {}
for relation in self.context.all():
self._context[relation.name] = relation.get_item()
return self._context
class MailLogRelation(models.Model):
"""
A template context item for the MailLog in case the a mail has delayed rendering.
This may be plain text or any relation within the database.
"""
mail = models.ForeignKey(
"mails.MailLog", on_delete=models.CASCADE, related_name="context"
)
name = models.CharField(max_length=254)
content_type = models.ForeignKey(
ContentType, blank=True, null=True, on_delete=models.CASCADE
)
object_id = models.PositiveIntegerField(blank=True, null=True)
content_object = GenericForeignKey("content_type", "object_id")
def get_item(self):
if self.value:
return self.value
elif self.content_object:
return self.content_object
return None