From 1901fd444ecb51677a54587f661a6953ddc14755 Mon Sep 17 00:00:00 2001 From: medmunds Date: Thu, 15 Nov 2012 15:49:08 -0800 Subject: [PATCH 1/3] Allow extra headers from any EmailMessage (not just DjrillMessage) Also adds test case for basic functionality on django.core.mail.EmailMessage. Cherry-picked from: medmunds/Djrill@f0503783f --- djrill/mail/backends/djrill.py | 22 ++++++++++++---------- djrill/tests.py | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/djrill/mail/backends/djrill.py b/djrill/mail/backends/djrill.py index d9997cf..dbb6ed5 100644 --- a/djrill/mail/backends/djrill.py +++ b/djrill/mail/backends/djrill.py @@ -96,16 +96,26 @@ class DjrillBackend(BaseEmailBackend): use by default. Standard text email messages sent through Django will still work through Mandrill. """ - return { + msg_dict = { "text": message.body, "subject": message.subject, "from_email": self.sender, "to": self.recipients } + if message.extra_headers: + accepted_headers = {} + for k in message.extra_headers.keys(): + if k.startswith("X-") or k == "Reply-To": + accepted_headers.update( + {"%s" % k: message.extra_headers[k]}) + msg_dict.update({"headers": accepted_headers}) + + return msg_dict + def _build_advanced_message_dict(self, message): """ - Builds advanced message dict and attaches any accepted extra headers. + Builds advanced message dict """ self.msg_dict.update({ "from_name": message.from_name, @@ -113,14 +123,6 @@ class DjrillBackend(BaseEmailBackend): "track_opens": message.track_opens, }) - if message.extra_headers: - accepted_headers = {} - - for k in message.extra_headers.keys(): - if k.startswith("X-") or k == "Reply-To": - accepted_headers.update( - {"%s" % k: message.extra_headers[k]}) - self.msg_dict.update({"headers": accepted_headers}) def _add_alternatives(self, message): """ diff --git a/djrill/tests.py b/djrill/tests.py index eebba74..82c8b87 100644 --- a/djrill/tests.py +++ b/djrill/tests.py @@ -65,6 +65,33 @@ class DjrillBackendTests(DjrillBackendMockAPITestCase): mail.send_mail('Subject', 'Message', 'from@example.com', ['to@example.com']) + def test_email_message(self): + email = mail.EmailMessage('Subject', 'Body goes here', + 'from@example.com', + ['to1@example.com', 'Also To '], + bcc=['bcc1@example.com', 'Also BCC '], + cc=['cc1@example.com', 'Also CC '], + headers={'Reply-To': 'another@example.com', + 'X-MyHeader': 'my value', + 'Errors-To': 'silently stripped'}) + email.send() + data = self.get_api_call_data() + self.assertEqual(data['message']['subject'], "Subject") + self.assertEqual(data['message']['text'], "Body goes here") + self.assertEqual(data['message']['from_email'], "from@example.com") + self.assertEqual(data['message']['headers'], + { 'Reply-To': 'another@example.com', 'X-MyHeader': 'my value' }) + # Mandrill doesn't have a notion of cc, and only allows a single bcc. + # Djrill just treats cc and bcc as though they were "to" addresses, + # which may or may not be what you want. + self.assertEqual(len(data['message']['to']), 6) + self.assertEqual(data['message']['to'][0]['email'], "to1@example.com") + self.assertEqual(data['message']['to'][1]['email'], "to2@example.com") + self.assertEqual(data['message']['to'][2]['email'], "cc1@example.com") + self.assertEqual(data['message']['to'][3]['email'], "cc2@example.com") + self.assertEqual(data['message']['to'][4]['email'], "bcc1@example.com") + self.assertEqual(data['message']['to'][5]['email'], "bcc2@example.com") + class DjrillMessageTests(TestCase): def setUp(self): From c23696a590f0669cfca800be1fd2261918b017da Mon Sep 17 00:00:00 2001 From: medmunds Date: Thu, 15 Nov 2012 16:17:16 -0800 Subject: [PATCH 2/3] Allow html from any EmailMultiAlternatives (not just DjrillMessage) Includes type-checking on alternative message part, and switches to ValueError (rather than ImproperlyConfigured) for unacceptable alternatives. [Also fixes bug where fail_silently=True wasn't respected.] Cherry-picked from: medmunds/Djrill@faf53a1a0 (and parts of medmunds/Djrill@62d48c5f) --- djrill/mail/backends/djrill.py | 29 ++++++++++++++++++--------- djrill/tests.py | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/djrill/mail/backends/djrill.py b/djrill/mail/backends/djrill.py index dbb6ed5..3046921 100644 --- a/djrill/mail/backends/djrill.py +++ b/djrill/mail/backends/djrill.py @@ -26,7 +26,7 @@ class DjrillBackend(BaseEmailBackend): Mandrill API Email Backend """ - def __init__(self, fail_silently=False, **kwargs): + def __init__(self, **kwargs): """ Set the API key, API url and set the action url. """ @@ -71,8 +71,13 @@ class DjrillBackend(BaseEmailBackend): if getattr(message, "alternative_subtype", None): if message.alternative_subtype == "mandrill": self._build_advanced_message_dict(message) - if message.alternatives: - self._add_alternatives(message) + try: + if getattr(message, 'alternatives', None): + self._add_alternatives(message) + except ValueError: + if not self.fail_silently: + raise + return False djrill_it = requests.post(self.api_action, data=json.dumps({ "key": self.api_key, @@ -121,23 +126,29 @@ class DjrillBackend(BaseEmailBackend): "from_name": message.from_name, "tags": message.tags, "track_opens": message.track_opens, + "track_clicks": message.track_clicks }) def _add_alternatives(self, message): """ - There can be only one! ... alternative attachment. + There can be only one! ... alternative attachment, and it must be text/html. Since mandrill does not accept image attachments or anything other than HTML, the assumption is the only thing you are attaching is the HTML output for your email. """ if len(message.alternatives) > 1: - raise ImproperlyConfigured( - "Mandrill only accepts plain text and html emails. Please " - "check the alternatives you have attached to your message.") + raise ValueError( + "Too many alternatives attached to the message. " + "Mandrill only accepts plain text and html emails.") + + (content, mimetype) = message.alternatives[0] + if mimetype != 'text/html': + raise ValueError("Invalid alternative mimetype '%s'. " + "Mandrill only accepts plain text and html emails." + % mimetype) self.msg_dict.update({ - "html": message.alternatives[0][0], - "track_clicks": message.track_clicks + "html": message.alternatives[0][0] }) diff --git a/djrill/tests.py b/djrill/tests.py index 82c8b87..e3ee158 100644 --- a/djrill/tests.py +++ b/djrill/tests.py @@ -92,6 +92,42 @@ class DjrillBackendTests(DjrillBackendMockAPITestCase): self.assertEqual(data['message']['to'][4]['email'], "bcc1@example.com") self.assertEqual(data['message']['to'][5]['email'], "bcc2@example.com") + def test_html_message(self): + text_content = 'This is an important message.' + html_content = '

This is an important message.

' + email = mail.EmailMultiAlternatives('Subject', text_content, + 'from@example.com', ['to@example.com']) + email.attach_alternative(html_content, "text/html") + email.send() + data = self.get_api_call_data() + self.assertEqual(data['message']['text'], text_content) + self.assertEqual(data['message']['html'], html_content) + + def test_alternative_errors(self): + # Multiple alternatives not allowed + email = mail.EmailMultiAlternatives('Subject', 'Body', + 'from@example.com', ['to@example.com']) + email.attach_alternative("

First html is OK

", "text/html") + email.attach_alternative("

But not second html

", "text/html") + with self.assertRaises(ValueError): + email.send() + + # Only html alternatives allowed + email = mail.EmailMultiAlternatives('Subject', 'Body', + 'from@example.com', ['to@example.com']) + email.attach_alternative("{'not': 'allowed'}", "application/json") + with self.assertRaises(ValueError): + email.send() + + # Make sure fail_silently is respected + email = mail.EmailMultiAlternatives('Subject', 'Body', + 'from@example.com', ['to@example.com']) + email.attach_alternative("{'not': 'allowed'}", "application/json") + sent = email.send(fail_silently=True) + self.assertFalse(self.mock_post.called, + msg="Mandrill API should not be called when send fails silently") + self.assertEqual(sent, 0) + class DjrillMessageTests(TestCase): def setUp(self): From c78bbc6ced9d2bf48a04921221987bf363dbc035 Mon Sep 17 00:00:00 2001 From: medmunds Date: Fri, 16 Nov 2012 11:07:15 -0800 Subject: [PATCH 3/3] [minor cleanup] Allow html from any EmailMultiAlternatives (not just DjrillMessage) --- djrill/mail/backends/djrill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djrill/mail/backends/djrill.py b/djrill/mail/backends/djrill.py index 3046921..e59a98e 100644 --- a/djrill/mail/backends/djrill.py +++ b/djrill/mail/backends/djrill.py @@ -150,5 +150,5 @@ class DjrillBackend(BaseEmailBackend): % mimetype) self.msg_dict.update({ - "html": message.alternatives[0][0] + "html": content })