from django.db import models from django.utils.crypto import get_random_string from django.core.mail import EmailMessage, get_connection from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ from django.contrib.auth import get_user_model import hashlib def generate_message_token(): # /!\ birthday problem return get_random_string(length=32, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789') def hexdigest_sha256(*args): r = hashlib.sha256() for arg in args: r.update(str(arg).encode('utf-8')) return r.hexdigest() class MessageCorrespondent(models.Model): email = models.EmailField() token = models.CharField(max_length=64, default=generate_message_token, unique=True) class MessageAuthor(models.Model): author_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True) author_id = models.PositiveIntegerField(null=True, blank=True) author = GenericForeignKey('author_type', 'author_id') token = models.CharField(max_length=64, default=generate_message_token, unique=True) def __str__(self): author_class = self.author_type.model_class() if self.author is not None and author_class == get_user_model(): return self.author.get_full_name() else: return str(self.author) class MessageThread(models.Model): created = models.DateTimeField(auto_now_add=True) token = models.CharField(max_length=64, default=generate_message_token, unique=True) class MessageManager(models.Manager): def get_queyset(self): qs = super().get_queryset() # Does not work so well as prefetch_related is limited to one content type for generic foreign keys qs = qs.prefetch_related('author__author') return qs class Message(models.Model): created = models.DateTimeField(auto_now_add=True) thread = models.ForeignKey(MessageThread, on_delete=models.CASCADE) author = models.ForeignKey(MessageAuthor, on_delete=models.PROTECT) in_reply_to = models.ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL) subject = models.CharField(max_length=1000, blank=True) content = models.TextField(blank=True) token = models.CharField(max_length=64, default=generate_message_token, unique=True) objects = MessageManager() class Meta: ordering = ['created'] def send_notification(self, sender, dests, reply_to=None, message_id=None, reference=None, footer=None, subject=None): messages = [] for dest, dest_name, dest_email in dests: dest_type = ContentType.objects.get_for_model(dest) dest, _ = MessageAuthor.objects.get_or_create(author_type=dest_type, author_id=dest.pk) token = self.token + dest.token + hexdigest_sha256(settings.SECRET_KEY, self.token, dest.token)[:16] if reply_to: reply_to_name, reply_to_email = reply_to reply_to_list = ['%s <%s>' % (reply_to_name, reply_to_email.format(token=token))] else: reply_to_list = [] headers = dict() if message_id: headers.update({ 'Message-ID': message_id.format(id=self.token), }) if message_id and reference: headers.update({ 'References': message_id.format(id=reference), }) body = self.content if footer is not None: body += footer messages.append(EmailMessage( subject=subject or self.subject, body=body, from_email='%s <%s>' % sender, to=['%s <%s>' % (dest_name, dest_email)], reply_to=reply_to_list, headers=headers, )) connection = get_connection() connection.send_messages(messages) def __str__(self): return _("Message from %(author)s") % {'author': str(self.author)}