tuomas2/serviceform

View on GitHub
serviceform/serviceform/models/email.py

Summary

Maintainability
A
35 mins
Test Coverage
D
65%
# -*- coding: utf-8 -*-
# (c) 2017 Tuomas Airaksinen
#
# This file is part of Serviceform.
#
# Serviceform is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Serviceform is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Serviceform.  If not, see <http://www.gnu.org/licenses/>.
import json
import logging
from typing import TYPE_CHECKING

from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.db import models
from django.template import Context, Template
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from raven.contrib.django.raven_compat.models import client

logger = logging.getLogger(__name__)
from .mixins import CopyMixin

if TYPE_CHECKING:
    from .serviceform import ServiceForm

class EmailMessage(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    last_modified = models.DateTimeField(auto_now=True)
    template = models.ForeignKey('serviceform.EmailTemplate', null=True, on_delete=models.SET_NULL)
    from_address = models.CharField(max_length=256)
    to_address = models.CharField(max_length=256)
    subject = models.CharField(max_length=256)
    content = models.TextField()
    sent_at = models.DateTimeField(null=True)
    context = models.TextField(default="{}")  # JSONified context variables

    def __str__(self):
        return '<EmailMessage %s to %s>' % (self.pk, self.to_address)

    @cached_property
    def context_dict(self) -> Context:
        return Context(json.loads(self.context))

    def content_display(self) -> str:
        return Template(self.content).render(self.context_dict)

    content_display.short_description = _('Content')

    def subject_display(self) -> str:
        return Template(self.subject).render(self.context_dict)

    subject_display.short_description = _('Subject')

    def _cleanup_context(self) -> None:
        context = json.loads(self.context)
        if 'url' in context:
            # Remove URL from email message, as it contains password
            context['url'] = 'http://***password*removed***'
            self.context = json.dumps(context)
            self.save(update_fields=['context'])

    def send(self) -> None:
        logger.info('Sending email to %s', self.to_address)
        body = self.content_display()
        html_body = render_to_string('serviceform/email.html', context={'body': body})
        headers = {'List-Unsubscribe': '<%s>' % self.context_dict['list_unsubscribe']}
        mail = EmailMultiAlternatives(subject=self.subject_display(),
                                      body=body,
                                      from_email=settings.SERVER_EMAIL,
                                      headers=headers,
                                      to=[self.to_address])
        mail.attach_alternative(html_body, 'text/html')
        try:
            emails = mail.send()
        except Exception:
            client.captureException()
            logger.exception('Problem in email sending to %s. Will try again later', self.to_address)
            emails = 0

        if emails == 1:
            self.sent_at = timezone.now()
            self.save(update_fields=['sent_at'])
            self._cleanup_context()
        else:
            logger.error('Email message to %s could not be sent', self)

    @classmethod
    def make(cls, template: 'EmailTemplate', context_dict: dict, address: str,
             send: bool=False) -> 'EmailMessage':
        logger.info('Creating email to %s', address)
        msg = cls.objects.create(template=template, to_address=address,
                                 from_address=settings.SERVER_EMAIL,
                                 subject=template.subject, content=template.content,
                                 context=json.dumps(context_dict))
        if send:
            msg.send()
        return msg


class EmailTemplate(CopyMixin, models.Model):
    class Meta:
        verbose_name = _('Email template')
        verbose_name_plural = _('Email templates')

    def __str__(self):
        return self.name

    name = models.CharField(_('Template name'), max_length=256)
    subject = models.CharField(_('Subject'), max_length=256)
    content = models.TextField(_('Content'), help_text=_(
        'Following context may (depending on topic) be available for both subject and content: '
        '{{responsible}}, {{participant}}, {{last_modified}}, {{form}}, {{url}}, {{contact}}'))
    form = models.ForeignKey('serviceform.ServiceForm', on_delete=models.CASCADE)

    @classmethod
    def make(cls, name: str, form: 'ServiceForm', content: str, subject: str):
        return cls.objects.create(name=name, form=form, subject=subject, content=content)