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 class MailUtilsMixin: """ This mixin takes care of inserting the default data into the Utils or Form. """ object = None mail_fields = {} mail_template = '' html_message = '' message = '' original_recipient = '' def __init__(self, *args, **kwargs): self.pre_validation(*args, **kwargs) super().__init__(*args) def pre_validation(self, *args, **kwargs): """ This method should be called when initiating the object. """ 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] 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] else: mail_to = self.object for attr in entry.split('.'): try: mail_to = getattr(mail_to, attr) if inspect.ismethod(mail_to): mail_to = mail_to() except AttributeError: # Invalid property/mail return [] if not isinstance(mail_to, list): return [mail_to] 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) def validate_recipients(self): # 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() self.validate_bcc_list() 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 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()