Merge branch 'chrisjrn/email_invoices'

This commit is contained in:
Christopher Neugebauer 2016-08-22 09:28:44 +10:00
commit 64e897919e
6 changed files with 178 additions and 3 deletions

View file

View file

@ -0,0 +1,63 @@
import os
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.contrib.sites.models import Site
class Sender(object):
''' Class for sending e-mails under a templete prefix. '''
def __init__(self, template_prefix):
self.template_prefix = template_prefix
def send_email(self, to, kind, **kwargs):
''' Sends an e-mail to the given address.
to: The address
kind: the ID for an e-mail kind; it should point to a subdirectory of
self.template_prefix containing subject.txt and message.html, which
are django templates for the subject and HTML message respectively.
context: a context for rendering the e-mail.
'''
return __send_email__(self.template_prefix, to, kind, **kwargs)
send_email = Sender("registrasion/emails").send_email
def __send_email__(template_prefix, to, kind, **kwargs):
current_site = Site.objects.get_current()
ctx = {
"current_site": current_site,
"STATIC_URL": settings.STATIC_URL,
}
ctx.update(kwargs.get("context", {}))
subject_template = os.path.join(template_prefix, "%s/subject.txt" % kind)
message_template = os.path.join(template_prefix, "%s/message.html" % kind)
subject = "[%s] %s" % (
current_site.name,
render_to_string(subject_template, ctx).strip()
)
message_html = render_to_string(message_template, ctx)
message_plaintext = strip_tags(message_html)
from_email = settings.DEFAULT_FROM_EMAIL
try:
bcc_email = settings.ENVELOPE_BCC_LIST
except AttributeError:
bcc_email = None
email = EmailMultiAlternatives(subject, message_plaintext, from_email, to, bcc=bcc_email)
email.attach_alternative(message_html, "text/html")
email.send()

View file

@ -5,6 +5,8 @@ from django.db import transaction
from django.db.models import Sum
from django.utils import timezone
from registrasion.contrib.mail import send_email
from registrasion.models import commerce
from registrasion.models import conditions
from registrasion.models import people
@ -149,6 +151,8 @@ class InvoiceController(ForId, object):
invoice.save()
cls.email_on_invoice_creation(invoice)
return invoice
def can_view(self, user=None, access_code=None):
@ -241,6 +245,12 @@ class InvoiceController(ForId, object):
if residual != 0:
CreditNoteController.generate_from_invoice(self.invoice, residual)
self.email_on_invoice_change(
self.invoice,
old_status,
self.invoice.status,
)
def _mark_paid(self):
''' Marks the invoice as paid, and updates the attached cart if
necessary. '''
@ -314,3 +324,44 @@ class InvoiceController(ForId, object):
CreditNoteController.generate_from_invoice(self.invoice, amount)
self.update_status()
@classmethod
def email(cls, invoice, kind):
''' Sends out an e-mail notifying the user about something to do
with that invoice. '''
context = {
"invoice": invoice,
}
send_email([invoice.user.email], kind, context=context)
@classmethod
def email_on_invoice_creation(cls, invoice):
''' Sends out an e-mail notifying the user that an invoice has been
created. '''
cls.email(invoice, "invoice_created")
@classmethod
def email_on_invoice_change(cls, invoice, old_status, new_status):
''' Sends out all of the necessary notifications that the status of the
invoice has changed to:
- Invoice is now paid
- Invoice is now refunded
'''
# The statuses that we don't care about.
silent_status = [
commerce.Invoice.STATUS_VOID,
commerce.Invoice.STATUS_UNPAID,
]
if old_status == new_status:
return
if False and new_status in silent_status:
pass
cls.email(invoice, "invoice_updated")

View file

@ -1,5 +1,6 @@
from django.utils import timezone
from registrasion.contrib import mail
class SetTimeMixin(object):
''' Patches timezone.now() for the duration of a test case. Allows us to
@ -23,3 +24,26 @@ class SetTimeMixin(object):
def new_timezone_now(self):
return self.now
class SendEmailMixin(object):
def setUp(self):
super(SendEmailMixin, self).setUp()
self._old_sender = mail.__send_email__
mail.__send_email__ = self._send_email
self.emails = []
def _send_email(self, template_prefix, to, kind, **kwargs):
args = {"to": to, "kind": kind}
args.update(kwargs)
self.emails.append(args)
def tearDown(self):
mail.__send_email__ = self._old_sender
super(SendEmailMixin, self).tearDown()
class MixInPatches(SetTimeMixin, SendEmailMixin):
pass

View file

@ -16,12 +16,12 @@ from registrasion.controllers.batch import BatchController
from registrasion.controllers.product import ProductController
from controller_helpers import TestingCartController
from patch_datetime import SetTimeMixin
from patches import MixInPatches
UTC = pytz.timezone('UTC')
class RegistrationCartTestCase(SetTimeMixin, TestCase):
class RegistrationCartTestCase(MixInPatches, TestCase):
def setUp(self):
super(RegistrationCartTestCase, self).setUp()
@ -377,7 +377,7 @@ class BasicCartTests(RegistrationCartTestCase):
# Memoise the cart
same_cart = TestingCartController.for_user(self.USER_1)
# Do nothing on exit
rev_1 = self.reget(cart.cart).revision
self.assertEqual(rev_0, rev_1)

View file

@ -533,3 +533,40 @@ class InvoiceTestCase(RegistrationCartTestCase):
# Now that we don't have CAT_1, we can't checkout this cart
with self.assertRaises(ValidationError):
invoice = TestingInvoiceController.for_cart(cart.cart)
def test_sends_email_on_invoice_creation(self):
invoice = self._invoice_containing_prod_1(1)
self.assertEquals(1, len(self.emails))
email = self.emails[0]
self.assertEquals([self.USER_1.email], email["to"])
self.assertEquals("invoice_created", email["kind"])
self.assertEquals(invoice.invoice, email["context"]["invoice"])
def test_sends_first_change_email_on_invoice_fully_paid(self):
invoice = self._invoice_containing_prod_1(1)
self.assertEquals(1, len(self.emails))
invoice.pay("Partial", invoice.invoice.value - 1)
# Should have an "invoice_created" email and nothing else.
self.assertEquals(1, len(self.emails))
invoice.pay("Remainder", 1)
self.assertEquals(2, len(self.emails))
email = self.emails[1]
self.assertEquals([self.USER_1.email], email["to"])
self.assertEquals("invoice_updated", email["kind"])
self.assertEquals(invoice.invoice, email["context"]["invoice"])
def test_sends_email_when_invoice_refunded(self):
invoice = self._invoice_containing_prod_1(1)
self.assertEquals(1, len(self.emails))
invoice.pay("Payment", invoice.invoice.value)
self.assertEquals(2, len(self.emails))
invoice.refund()
self.assertEquals(3, len(self.emails))
email = self.emails[2]
self.assertEquals([self.USER_1.email], email["to"])
self.assertEquals("invoice_updated", email["kind"])
self.assertEquals(invoice.invoice, email["context"]["invoice"])