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.db.models import Sum
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from registrasion.contrib.mail import send_email
|
||||||
|
|
||||||
from registrasion.models import commerce
|
from registrasion.models import commerce
|
||||||
from registrasion.models import conditions
|
from registrasion.models import conditions
|
||||||
from registrasion.models import people
|
from registrasion.models import people
|
||||||
|
@ -149,6 +151,8 @@ class InvoiceController(ForId, object):
|
||||||
|
|
||||||
invoice.save()
|
invoice.save()
|
||||||
|
|
||||||
|
cls.email_on_invoice_creation(invoice)
|
||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
def can_view(self, user=None, access_code=None):
|
def can_view(self, user=None, access_code=None):
|
||||||
|
@ -241,6 +245,12 @@ class InvoiceController(ForId, object):
|
||||||
if residual != 0:
|
if residual != 0:
|
||||||
CreditNoteController.generate_from_invoice(self.invoice, residual)
|
CreditNoteController.generate_from_invoice(self.invoice, residual)
|
||||||
|
|
||||||
|
self.email_on_invoice_change(
|
||||||
|
self.invoice,
|
||||||
|
old_status,
|
||||||
|
self.invoice.status,
|
||||||
|
)
|
||||||
|
|
||||||
def _mark_paid(self):
|
def _mark_paid(self):
|
||||||
''' Marks the invoice as paid, and updates the attached cart if
|
''' Marks the invoice as paid, and updates the attached cart if
|
||||||
necessary. '''
|
necessary. '''
|
||||||
|
@ -314,3 +324,44 @@ class InvoiceController(ForId, object):
|
||||||
|
|
||||||
CreditNoteController.generate_from_invoice(self.invoice, amount)
|
CreditNoteController.generate_from_invoice(self.invoice, amount)
|
||||||
self.update_status()
|
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 django.utils import timezone
|
||||||
|
|
||||||
|
from registrasion.contrib import mail
|
||||||
|
|
||||||
class SetTimeMixin(object):
|
class SetTimeMixin(object):
|
||||||
''' Patches timezone.now() for the duration of a test case. Allows us to
|
''' 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):
|
def new_timezone_now(self):
|
||||||
return self.now
|
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 registrasion.controllers.product import ProductController
|
||||||
|
|
||||||
from controller_helpers import TestingCartController
|
from controller_helpers import TestingCartController
|
||||||
from patch_datetime import SetTimeMixin
|
from patches import MixInPatches
|
||||||
|
|
||||||
UTC = pytz.timezone('UTC')
|
UTC = pytz.timezone('UTC')
|
||||||
|
|
||||||
|
|
||||||
class RegistrationCartTestCase(SetTimeMixin, TestCase):
|
class RegistrationCartTestCase(MixInPatches, TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(RegistrationCartTestCase, self).setUp()
|
super(RegistrationCartTestCase, self).setUp()
|
||||||
|
|
|
@ -533,3 +533,40 @@ class InvoiceTestCase(RegistrationCartTestCase):
|
||||||
# Now that we don't have CAT_1, we can't checkout this cart
|
# Now that we don't have CAT_1, we can't checkout this cart
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
invoice = TestingInvoiceController.for_cart(cart.cart)
|
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