SciPost Code Repository

Skip to content
Snippets Groups Projects
mixins.py 6.45 KiB
Newer Older
Jorran de Wit's avatar
Jorran de Wit committed
import re
import json
import inspect
from html2text import HTML2Text

from django.core.mail import EmailMultiAlternatives
from django.contrib.auth import get_user_model
from django.conf import settings
from django.template import loader

from scipost.models import Contributor
Jorran de Wit's avatar
Jorran de Wit committed

Jorran de Wit's avatar
Jorran de Wit committed
class MailUtilsMixin:
    """
    This mixin takes care of inserting the default data into the Utils or Form.
    """
Jorran de Wit's avatar
Jorran de Wit committed
    object = None
    mail_fields = {}
    mail_template = ''
    html_message = ''
    message = ''
Jorran de Wit's avatar
Jorran de Wit committed
    original_recipient = ''
Jorran de Wit's avatar
Jorran de Wit committed

    def __init__(self, *args, **kwargs):
Jorran de Wit's avatar
Jorran de Wit committed
        self.pre_validation(*args, **kwargs)
        super().__init__(*args)

    def pre_validation(self, *args, **kwargs):
        """
        This method should be called when initiating the object.
        """
Jorran de Wit's avatar
Jorran de Wit committed
        self.mail_code = kwargs.pop('mail_code')
        self.instance = kwargs.pop('instance', None)

        # Gather meta data
        json_location = '%s/mails/templates/mail_templates/%s.json' % (settings.BASE_DIR,
                                                                       self.mail_code)
        try:
            self.mail_data = json.loads(open(json_location).read())
        except OSError:
            raise NotImplementedError(('You did not create a valid .html and .json file '
                                       'for mail_code: %s' % self.mail_code))

        # Save central object/instance
        self.object = self.get_object(**kwargs)

        # Digest the templates
        mail_template = loader.get_template('mail_templates/%s.html' % self.mail_code)
        if self.instance and self.mail_data.get('context_object'):
            kwargs[self.mail_data['context_object']] = self.instance
        self.mail_template = mail_template.render(kwargs)

        # Gather Recipients data
        self.original_recipient = self._validate_single_entry(self.mail_data.get('to_address'))[0]
Jorran de Wit's avatar
Jorran de Wit committed

        self.subject = self.mail_data['subject']

    def _validate_single_entry(self, entry):
        """
        entry -- raw email string or path or properties leading to email mail field

        Returns a list of email addresses found.
        """
        if entry and self.object:
            if re.match("[^@]+@[^@]+\.[^@]+", entry):
                # Email string
                return [entry]
Jorran de Wit's avatar
Jorran de Wit committed
            else:
                mail_to = self.object
                for attr in entry.split('.'):
                        mail_to = getattr(mail_to, attr)
                        if inspect.ismethod(mail_to):
                            mail_to = mail_to()
                    except AttributeError:
                        # Invalid property/mail
Jorran de Wit's avatar
Jorran de Wit committed

                if not isinstance(mail_to, list):
                    return [mail_to]
Jorran de Wit's avatar
Jorran de Wit committed
                else:
                    return mail_to
        elif re.match("[^@]+@[^@]+\.[^@]+", entry):
            return [entry]

    def validate_bcc_list(self):
        """
        bcc_to in the .json file may contain multiple raw email addreses or property paths to
        an email field. The different entries need to be comma separated.
        """
        # Get recipients list. Try to send through BCC to prevent privacy issues!
        self.bcc_list = []
        if self.mail_data.get('bcc_to'):
            for bcc_entry in self.mail_data['bcc_to'].split(','):
                self.bcc_list += self._validate_single_entry(bcc_entry)
Jorran de Wit's avatar
Jorran de Wit committed

Jorran de Wit's avatar
Jorran de Wit committed
    def validate_recipients(self):
Jorran de Wit's avatar
Jorran de Wit committed
        # Check the send list
        if isinstance(self.original_recipient, list):
            recipients = self.original_recipient
        elif not isinstance(self.original_recipient, str):
            try:
                recipients = list(self.original_recipient)
            except TypeError:
                recipients = [self.original_recipient]
        else:
            recipients = [self.original_recipient]
        recipients = list(recipients)

        # Check if email needs to be taken from an instance
        _recipients = []
        for recipient in recipients:
            if isinstance(recipient, Contributor):
                _recipients.append(recipient.user.email)
            elif isinstance(recipient, get_user_model()):
                _recipients.append(recipient.email)
            elif isinstance(recipient, str):
                _recipients.append(recipient)
        self.recipients = _recipients

    def validate_message(self):
        if not self.html_message:
            self.html_message = self.mail_template
        handler = HTML2Text()
        self.message = handler.handle(self.html_message)

    def validate(self):
        """
        Ease workflow by called this wrapper validation method.

        Only to be used when the default data is used, eg. not in the EmailTemplateForm.
        """
        self.validate_message()
Jorran de Wit's avatar
Jorran de Wit committed
        self.validate_bcc_list()
Jorran de Wit's avatar
Jorran de Wit committed
        self.validate_recipients()
        self.save_mail_data()

    def save_mail_data(self):
        self.mail_fields = {
            'subject': self.subject,
            'message': self.message,
            'html_message': self.html_message,
            'recipients': self.recipients,
            'bcc_list': self.bcc_list,
        }

    def set_alternative_sender(self, from_name, from_address):
        """
        Set an alternative from address/name from the default values received from the json
        config file. The arguments only take raw string data, no methods/properties!
        """
        self.mail_data['from_address_name'] = from_name
        self.mail_data['from_address'] = from_address

Jorran de Wit's avatar
Jorran de Wit committed
    def get_object(self, **kwargs):
        if self.object:
            return self.object
        if self.instance:
            return self.instance

        if self.mail_data.get('context_object'):
            return kwargs.get(self.mail_data['context_object'], None)

    def send(self):
        # Send the mail
        email = EmailMultiAlternatives(
            self.mail_fields['subject'],
            self.mail_fields['message'],
            '%s <%s>' % (self.mail_data.get('from_address_name', 'SciPost'),
                         self.mail_data.get('from_address', 'no-reply@scipost.org')),  # From
            self.mail_fields['recipients'],  # To
            bcc=self.mail_fields['bcc_list'],
            reply_to=[self.mail_data.get('from_address', 'no-reply@scipost.org')])
        email.attach_alternative(self.mail_fields['html_message'], 'text/html')
        email.send(fail_silently=False)
        if self.object and hasattr(self.object, 'mail_sent'):
            self.object.mail_sent()