From 095b96b041b3e273322d500a878b2e24dbfd0351 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 6 Feb 2015 13:09:56 -0500 Subject: [PATCH] Fix unicode error when sending confirmation email in shopping cart Wrap send email operation in try/except to avoid rolling back the transaction once a user has paid. --- lms/djangoapps/shoppingcart/models.py | 20 +++++++++---- .../shoppingcart/tests/test_models.py | 28 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index 32562455d1..819a8327b7 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -481,10 +481,18 @@ class Order(models.Model): log.exception('Exception at creating pdf file.') pdf_file = None - self.send_confirmation_emails( - orderitems, self.order_type == OrderTypes.BUSINESS, - csv_file, pdf_file, site_name, courses_info - ) + try: + self.send_confirmation_emails( + orderitems, self.order_type == OrderTypes.BUSINESS, + csv_file, pdf_file, site_name, courses_info + ) + except Exception: # pylint: disable=broad-except + # Catch all exceptions here, since the Django view implicitly + # wraps this in a transaction. If the order completes successfully, + # we don't want to roll back just because we couldn't send + # the confirmation email. + log.exception('Error occurred while sending payment confirmation email') + self._emit_order_event('Completed Order', orderitems) def refund(self): @@ -1460,7 +1468,9 @@ class CertificateItem(OrderItem): "If you haven't verified your identity yet, please start the verification process ({verification_url})." ).format(verification_url=verification_url) - return "{verification_reminder} {refund_reminder}".format( + # Need this to be unicode in case the reminder strings + # have been translated and contain non-ASCII unicode + return u"{verification_reminder} {refund_reminder}".format( verification_reminder=verification_reminder, refund_reminder=refund_reminder ) diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py index 63d0703b76..c8c4e9e0cd 100644 --- a/lms/djangoapps/shoppingcart/tests/test_models.py +++ b/lms/djangoapps/shoppingcart/tests/test_models.py @@ -375,6 +375,34 @@ class OrderTest(ModuleStoreTestCase): cart.generate_receipt_instructions() mock_gen_inst.assert_called_with() + def test_confirmation_email_error(self): + CourseMode.objects.create( + course_id=self.course_key, + mode_slug="verified", + mode_display_name="Verified", + min_price=self.cost + ) + + cart = Order.get_cart_for_user(self.user) + CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified') + + # Simulate an error when sending the confirmation + # email. This should NOT raise an exception. + # If it does, then the implicit view-level + # transaction could cause a roll-back, effectively + # reversing order fulfillment. + with patch.object(mail.message.EmailMessage, 'send') as mock_send: + mock_send.side_effect = Exception("Kaboom!") + cart.purchase() + + # Verify that the purchase completed successfully + self.assertEqual(cart.status, 'purchased') + + # Verify that the user is enrolled as "verified" + mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_key) + self.assertTrue(is_active) + self.assertEqual(mode, 'verified') + class OrderItemTest(TestCase): def setUp(self):