Merge branch 'chrisjrn/email_invoices'
This commit is contained in:
commit
64e897919e
6 changed files with 178 additions and 3 deletions
0
registrasion/contrib/__init__.py
Normal file
0
registrasion/contrib/__init__.py
Normal file
63
registrasion/contrib/mail.py
Normal file
63
registrasion/contrib/mail.py
Normal 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()
|
|
@ -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")
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"])
|
||||
|
|
Loading…
Reference in a new issue