__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"
import time
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
[docs]class MailUtilsMixin:
"""
This mixin takes care of inserting the default data into the Utils or Form.
DEPRECATED
"""
instance = None
mail_data = {}
mail_template = ''
html_message = ''
message = ''
original_recipient = ''
mail_sent = False
delayed_processing = False
def __init__(self, *args, **kwargs):
"""Init an instance for a specific mail_code.
Arguments:
-- mail_code (str)
-- subject (str)
-- to (str): Email address or relation on the `instance`. Separated by comma.
-- bcc_to (str, optional): Email address or relation on the `instance`. Separated by comma.
-- instance: Instance of central object in email.
-- from (str, optional): Plain email address.
-- from_name (str, optional): Display name for from address.
"""
self.pre_validation(*args, **kwargs)
super().__init__(*args)
[docs] def pre_validation(self, *args, **kwargs):
"""Validate the incoming data to initiate a specific mail."""
self.mail_code = kwargs.pop('mail_code')
self.instance = kwargs.pop('instance', None)
kwargs['object'] = self.instance # Similar template nomenclature as Django.
self.mail_data = {
'subject': kwargs.pop('subject', ''),
'to_address': kwargs.pop('to', ''),
'bcc_to': kwargs.pop('bcc', ''),
'from_address_name': kwargs.pop('from_name', 'SciPost'),
'from_address': kwargs.pop('from', 'no-reply@scipost.org'),
}
# Gather meta data
json_location = '%s/templates/email/%s.json' % (settings.BASE_DIR, self.mail_code)
try:
self.mail_data.update(json.loads(open(json_location).read()))
except OSError:
if not self.mail_data['subject']:
raise NotImplementedError(('You did not create a valid .html and .json file '
'for mail_code: %s' % self.mail_code))
# Save central object/instance if not already
self.instance = self.get_object(**kwargs)
# Digest the templates
if not self.delayed_processing:
mail_template = loader.get_template('email/%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) # Damn slow.
# Gather Recipients data
try:
self.original_recipient = self._validate_single_entry(self.mail_data.get('to_address'))[0]
except IndexError:
self.original_recipient = ''
self.subject = self.mail_data['subject']
[docs] def get_object(self, **kwargs):
if self.instance:
return self.instance
if self.mail_data.get('context_object'):
return kwargs.get(self.mail_data['context_object'], None)
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.instance:
if re.match("[^@]+@[^@]+\.[^@]+", entry):
# Email string
return [entry]
else:
mail_to = self.instance
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]
else:
return []
[docs] 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)
[docs] 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
[docs] def validate_message(self):
if not self.html_message:
self.html_message = self.mail_template
handler = HTML2Text()
self.message = handler.handle(self.html_message)
[docs] def validate(self):
"""Execute different validation methods.
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()
[docs] def save_mail_data(self):
"""Save mail validated mail data; update default values of mail data."""
self.mail_data.update({
'subject': self.subject,
'message': self.message,
'html_message': self.html_message,
'recipients': self.recipients,
'bcc_list': self.bcc_list,
})
[docs] def set_alternative_sender(self, from_name, from_address):
"""TODO: REMOVE; DEPRECATED
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
[docs] def send(self):
"""Send the mail assuming `mail_data` is validated and complete."""
if self.mail_sent:
# Prevent double sending when using a Django form.
return
email = EmailMultiAlternatives(
self.mail_data['subject'],
self.mail_data['message'],
'%s <%s>' % (self.mail_data['from_address_name'], self.mail_data['from_address']),
self.mail_data['recipients'],
bcc=self.mail_data['bcc_list'],
reply_to=[self.mail_data['from_address']],
headers={
'delayed_processing': self.delayed_processing,
'content_object': self.get_object(),
'mail_code': self.mail_code,
})
# Send html version if available
if 'html_message' in self.mail_data:
email.attach_alternative(self.mail_data['html_message'], 'text/html')
email.send(fail_silently=False)
self.mail_sent = True
if self.instance and hasattr(self.instance, 'mail_sent'):
self.instance.mail_sent()