DRY out email sending code and normalize behavior

This refactor removes duplicated logic for deciding whom the messages get sent to.
It also normalizes behavior ensuring that all CCed addresses are sent to in all cases that CCed individuals should be notified.
This commit is contained in:
Timothy Hobbs
2018-10-31 16:24:57 +01:00
parent 9a45d28c95
commit 6c37d73d4e
7 changed files with 249 additions and 361 deletions

View File

@ -9,142 +9,22 @@ lib.py - Common functions (eg multipart e-mail)
import logging
import mimetypes
import os
from smtplib import SMTPException
from django.conf import settings
from django.db.models import Q
from django.utils import six
from django.utils.encoding import smart_text
from django.utils.safestring import mark_safe
from helpdesk.models import Attachment, EmailTemplate
import six
from model_utils import Choices
if six.PY3:
from base64 import encodebytes as b64encode
from base64 import decodebytes as b64decode
else:
from base64 import urlsafe_b64encode as b64encode
from base64 import urlsafe_b64decode as b64decode
from base64 import encodebytes as b64encode
from base64 import decodebytes as b64decode
logger = logging.getLogger('helpdesk')
def send_templated_mail(template_name,
context,
recipients,
sender=None,
bcc=None,
fail_silently=False,
files=None):
"""
send_templated_mail() is a wrapper around Django's e-mail routines that
allows us to easily send multipart (text/plain & text/html) e-mails using
templates that are stored in the database. This lets the admin provide
both a text and a HTML template for each message.
template_name is the slug of the template to use for this message (see
models.EmailTemplate)
context is a dictionary to be used when rendering the template
recipients can be either a string, eg 'a@b.com', or a list of strings.
sender should contain a string, eg 'My Site <me@z.com>'. If you leave it
blank, it'll use settings.DEFAULT_FROM_EMAIL as a fallback.
bcc is an optional list of addresses that will receive this message as a
blind carbon copy.
fail_silently is passed to Django's mail routine. Set to 'True' to ignore
any errors at send time.
files can be a list of tuples. Each tuple should be a filename to attach,
along with the File objects to be read. files can be blank.
"""
from django.core.mail import EmailMultiAlternatives
from django.template import engines
from_string = engines['django'].from_string
from helpdesk.models import EmailTemplate
from helpdesk.settings import HELPDESK_EMAIL_SUBJECT_TEMPLATE, \
HELPDESK_EMAIL_FALLBACK_LOCALE
locale = context['queue'].get('locale') or HELPDESK_EMAIL_FALLBACK_LOCALE
try:
t = EmailTemplate.objects.get(template_name__iexact=template_name, locale=locale)
except EmailTemplate.DoesNotExist:
try:
t = EmailTemplate.objects.get(template_name__iexact=template_name, locale__isnull=True)
except EmailTemplate.DoesNotExist:
logger.warning('template "%s" does not exist, no mail sent', template_name)
return # just ignore if template doesn't exist
subject_part = from_string(
HELPDESK_EMAIL_SUBJECT_TEMPLATE % {
"subject": t.subject
}).render(context).replace('\n', '').replace('\r', '')
footer_file = os.path.join('helpdesk', locale, 'email_text_footer.txt')
text_part = from_string(
"%s{%% include '%s' %%}" % (t.plain_text, footer_file)
).render(context)
email_html_base_file = os.path.join('helpdesk', locale, 'email_html_base.html')
# keep new lines in html emails
if 'comment' in context:
context['comment'] = mark_safe(context['comment'].replace('\r\n', '<br>'))
html_part = from_string(
"{%% extends '%s' %%}{%% block title %%}"
"%s"
"{%% endblock %%}{%% block content %%}%s{%% endblock %%}" %
(email_html_base_file, t.heading, t.html)
).render(context)
if isinstance(recipients, str):
if recipients.find(','):
recipients = recipients.split(',')
elif type(recipients) != list:
recipients = [recipients]
msg = EmailMultiAlternatives(subject_part, text_part,
sender or settings.DEFAULT_FROM_EMAIL,
recipients, bcc=bcc)
msg.attach_alternative(html_part, "text/html")
if files:
for filename, filefield in files:
mime = mimetypes.guess_type(filename)
if mime[0] is not None and mime[0] == "text/plain":
with open(filefield.path, 'r') as attachedfile:
content = attachedfile.read()
msg.attach(filename, content)
else:
if six.PY3:
msg.attach_file(filefield.path)
else:
with open(filefield.path, 'rb') as attachedfile:
content = attachedfile.read()
msg.attach(filename, content)
logger.debug('Sending email to: {!r}'.format(recipients))
try:
return msg.send()
except SMTPException as e:
logger.exception('SMTPException raised while sending email to {}'.format(recipients))
if not fail_silently:
raise e
return 0
def query_to_dict(results, descriptions):
"""
Replacement method for cursor.dictfetchall() as that method no longer