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() | ||||
|  |  | |||
|  | @ -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…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer