From 883b23362c4400580185fba6ae9ecd97a311e2ef Mon Sep 17 00:00:00 2001 From: William Hector Date: Sun, 12 Jul 2015 01:19:59 +0100 Subject: [PATCH 1/2] Allow Mandrill specific options to be set globally in the settings file. This is useful to set options such as tracking_domain etc per instance when using subaccounts with Mandrill. --- djrill/mail/backends/djrill.py | 13 ++++ djrill/tests/test_mandrill_send.py | 117 +++++++++++++++++++++++++++++ docs/usage/sending_mail.rst | 10 +++ 3 files changed, 140 insertions(+) diff --git a/djrill/mail/backends/djrill.py b/djrill/mail/backends/djrill.py index a2aac59..66969b3 100644 --- a/djrill/mail/backends/djrill.py +++ b/djrill/mail/backends/djrill.py @@ -63,6 +63,12 @@ class DjrillBackend(BaseEmailBackend): self.api_key = getattr(settings, "MANDRILL_API_KEY", None) self.api_url = MANDRILL_API_URL self.session = None + self.global_settings = {} + for setting_key in getattr(settings, "MANDRILL_SETTINGS", {}): + if not isinstance(settings.MANDRILL_SETTINGS, dict): + raise ImproperlyConfigured("MANDRILL_SETTINGS must be a dict " + "in the settings.py file.") + self.global_settings[setting_key] = settings.MANDRILL_SETTINGS[setting_key] self.subaccount = getattr(settings, "MANDRILL_SUBACCOUNT", None) @@ -233,6 +239,8 @@ class DjrillBackend(BaseEmailBackend): 'async', 'ip_pool' ] for attr in mandrill_attrs: + if attr in self.global_settings: + api_params[attr] = self.global_settings[attr] if hasattr(message, attr): api_params[attr] = getattr(message, attr) @@ -270,11 +278,16 @@ class DjrillBackend(BaseEmailBackend): msg_dict['subaccount'] = self.subaccount for attr in mandrill_attrs: + if attr in self.global_settings: + msg_dict[attr] = self.global_settings[attr] if hasattr(message, attr): msg_dict[attr] = getattr(message, attr) # Allow simple python dicts in place of Mandrill # [{name:name, value:value},...] arrays... + if 'global_merge_vars' in self.global_settings: + msg_dict['global_merge_vars'] = self._expand_merge_vars( + self.global_settings['global_merge_vars']) if hasattr(message, 'global_merge_vars'): msg_dict['global_merge_vars'] = \ self._expand_merge_vars(message.global_merge_vars) diff --git a/djrill/tests/test_mandrill_send.py b/djrill/tests/test_mandrill_send.py index 43e9cf4..f547010 100644 --- a/djrill/tests/test_mandrill_send.py +++ b/djrill/tests/test_mandrill_send.py @@ -540,6 +540,123 @@ class DjrillMandrillFeatureTests(DjrillBackendMockAPITestCase): self.message.send() +@override_settings(MANDRILL_SETTINGS={ + 'from_name': 'Djrill Test', + 'important': True, + 'track_opens': True, + 'track_clicks': True, + 'auto_text': True, + 'auto_html': True, + 'inline_css': True, + 'url_strip_qs': True, + 'tags': ['djrill'], + 'preserve_recipients': True, + 'view_content_link': True, + 'tracking_domain': 'example.com', + 'signing_domain': 'example.com', + 'return_path_domain': 'example.com', + 'google_analytics_domains': ['example.com/test'], + 'google_analytics_campaign': ['UA-00000000-1'], + 'metadata': ['djrill'], + 'merge_language': 'mailchimp', + 'global_merge_vars': {'TEST': 'djrill'}, + 'async': False, + 'ip_pool': 'Pool1', + 'invalid': 'invalid', +}) +class DjrillMandrillGlobalFeatureTests(DjrillBackendMockAPITestCase): + """Test Djrill backend support for global ovveride Mandrill-specific features""" + + def setUp(self): + super(DjrillMandrillGlobalFeatureTests, self).setUp() + self.message = mail.EmailMessage('Subject', 'Text Body', + 'from@example.com', ['to@example.com']) + + def test_global_options(self): + """Test that any global settings get passed through + """ + self.message.send() + self.assert_mandrill_called("/messages/send.json") + data = self.get_api_call_data() + self.assertEqual(data['message']['from_name'], 'Djrill Test') + self.assertTrue(data['message']['important'], True) + self.assertTrue(data['message']['track_opens'], True) + self.assertTrue(data['message']['track_clicks'], True) + self.assertTrue(data['message']['auto_text'], True) + self.assertTrue(data['message']['auto_html'], True) + self.assertTrue(data['message']['inline_css'], True) + self.assertTrue(data['message']['url_strip_qs'], True) + self.assertEqual(data['message']['tags'], ['djrill']) + self.assertTrue(data['message']['preserve_recipients'], True) + self.assertTrue(data['message']['view_content_link'], True) + self.assertEqual(data['message']['tracking_domain'], 'example.com') + self.assertEqual(data['message']['signing_domain'], 'example.com') + self.assertEqual(data['message']['return_path_domain'], 'example.com') + self.assertEqual(data['message']['google_analytics_domains'], ['example.com/test']) + self.assertEqual(data['message']['google_analytics_campaign'], ['UA-00000000-1']) + self.assertEqual(data['message']['metadata'], ['djrill']) + self.assertEqual(data['message']['merge_language'], 'mailchimp') + self.assertEqual(data['message']['global_merge_vars'], + [{'name': 'TEST', 'content': 'djrill'}]) + self.assertFalse('merge_vars' in data['message']) + self.assertFalse('recipient_metadata' in data['message']) + # Options at top level of api params (not in message dict): + self.assertFalse('send_at' in data) + self.assertEqual(data['async'], False) + self.assertEqual(data['ip_pool'], 'Pool1') + # Option that shouldn't be added + self.assertFalse('invalid' in data['message']) + + def test_global_options_override(self): + """Test that manually settings options overrides global settings + """ + self.message.important = True + self.message.auto_text = True + self.message.auto_html = True + self.message.inline_css = True + self.message.preserve_recipients = True + self.message.view_content_link = False + self.message.tracking_domain = "click.example.com" + self.message.signing_domain = "example.com" + self.message.return_path_domain = "support.example.com" + self.message.subaccount = "marketing-dept" + self.message.async = True + self.message.ip_pool = "Bulk Pool" + self.message.send() + data = self.get_api_call_data() + self.assertEqual(data['message']['important'], True) + self.assertEqual(data['message']['auto_text'], True) + self.assertEqual(data['message']['auto_html'], True) + self.assertEqual(data['message']['inline_css'], True) + self.assertEqual(data['message']['preserve_recipients'], True) + self.assertEqual(data['message']['view_content_link'], False) + self.assertEqual(data['message']['tracking_domain'], "click.example.com") + self.assertEqual(data['message']['signing_domain'], "example.com") + self.assertEqual(data['message']['return_path_domain'], "support.example.com") + self.assertEqual(data['message']['subaccount'], "marketing-dept") + self.assertEqual(data['async'], True) + self.assertEqual(data['ip_pool'], "Bulk Pool") + + def test_global_options_override_tracking(self): + """Test that manually settings options overrides global settings + """ + self.message.track_opens = False + self.message.track_clicks = False + self.message.url_strip_qs = False + self.message.send() + data = self.get_api_call_data() + self.assertEqual(data['message']['track_opens'], False) + self.assertEqual(data['message']['track_clicks'], False) + self.assertEqual(data['message']['url_strip_qs'], False) + + def test_global_merge(self): + self.message.global_merge_vars = {'GREETING': "Hello"} + self.message.send() + data = self.get_api_call_data() + self.assertEqual(data['message']['global_merge_vars'], + [{'name': "GREETING", 'content': "Hello"}]) + + @override_settings(EMAIL_BACKEND="djrill.mail.backends.djrill.DjrillBackend") class DjrillImproperlyConfiguredTests(TestCase): """Test Djrill backend without Djrill-specific settings in place""" diff --git a/docs/usage/sending_mail.rst b/docs/usage/sending_mail.rst index 0104082..a7e51c0 100644 --- a/docs/usage/sending_mail.rst +++ b/docs/usage/sending_mail.rst @@ -103,11 +103,21 @@ Some notes and limitations: Mandrill-Specific Options ------------------------- +.. setting:: MANDRILL_SETTINGS + Most of the options from the Mandrill `messages/send API `_ `message` struct can be set directly on an :class:`~django.core.mail.EmailMessage` (or subclass) object: +Most of these options can be globally set in your project's :file:`settings.py` +using :setting:`MANDRILL_SETTINGS`. For Example:: + + MANDRILL_SETTINGS = { + 'tracking_domain': 'example.com', + 'track_opens': True, + } + .. These attributes are in the same order as they appear in the Mandrill API docs... .. attribute:: important From 7179734a088f58c3352b5ced0c62789775dbaadd Mon Sep 17 00:00:00 2001 From: William Hector Date: Tue, 14 Jul 2015 05:57:12 +0100 Subject: [PATCH 2/2] Allow global_merge_vars to be merged in with the per message dict, with keys in the latter taking precedent. Update the docs accordingly. --- AUTHORS.txt | 1 + djrill/mail/backends/djrill.py | 11 ++++++++--- djrill/tests/test_mandrill_send.py | 12 +++++++++++- docs/usage/sending_mail.rst | 8 ++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index b774736..d83829f 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -17,3 +17,4 @@ Sameer Al-Sakran Kyle Gibson Wes Winham nikolay-saskovets +William Hector diff --git a/djrill/mail/backends/djrill.py b/djrill/mail/backends/djrill.py index 66969b3..da233ca 100644 --- a/djrill/mail/backends/djrill.py +++ b/djrill/mail/backends/djrill.py @@ -285,12 +285,17 @@ class DjrillBackend(BaseEmailBackend): # Allow simple python dicts in place of Mandrill # [{name:name, value:value},...] arrays... + + # Allow merge of global and per message global_merge_var, the former taking precedent + global_merge_vars = {} if 'global_merge_vars' in self.global_settings: - msg_dict['global_merge_vars'] = self._expand_merge_vars( - self.global_settings['global_merge_vars']) + global_merge_vars.update(self.global_settings['global_merge_vars']) if hasattr(message, 'global_merge_vars'): + global_merge_vars.update(message.global_merge_vars) + if global_merge_vars: msg_dict['global_merge_vars'] = \ - self._expand_merge_vars(message.global_merge_vars) + self._expand_merge_vars(global_merge_vars) + if hasattr(message, 'merge_vars'): # For testing reproducibility, we sort the recipients msg_dict['merge_vars'] = [ diff --git a/djrill/tests/test_mandrill_send.py b/djrill/tests/test_mandrill_send.py index f547010..80cabc5 100644 --- a/djrill/tests/test_mandrill_send.py +++ b/djrill/tests/test_mandrill_send.py @@ -650,11 +650,21 @@ class DjrillMandrillGlobalFeatureTests(DjrillBackendMockAPITestCase): self.assertEqual(data['message']['url_strip_qs'], False) def test_global_merge(self): + # Test that global settings merge in self.message.global_merge_vars = {'GREETING': "Hello"} self.message.send() data = self.get_api_call_data() self.assertEqual(data['message']['global_merge_vars'], - [{'name': "GREETING", 'content': "Hello"}]) + [{'name': "GREETING", 'content': "Hello"}, + {'name': 'TEST', 'content': 'djrill'}]) + + def test_global_merge_overwrite(self): + # Test that global merge settings are overwritten + self.message.global_merge_vars = {'TEST': "Hello"} + self.message.send() + data = self.get_api_call_data() + self.assertEqual(data['message']['global_merge_vars'], + [{'name': 'TEST', 'content': 'Hello'}]) @override_settings(EMAIL_BACKEND="djrill.mail.backends.djrill.DjrillBackend") diff --git a/docs/usage/sending_mail.rst b/docs/usage/sending_mail.rst index a7e51c0..deca0dd 100644 --- a/docs/usage/sending_mail.rst +++ b/docs/usage/sending_mail.rst @@ -118,6 +118,10 @@ using :setting:`MANDRILL_SETTINGS`. For Example:: 'track_opens': True, } +.. note:: + ``merge_vars`` and ``recipient_metadata`` cannot be set globally. ``global_merge_vars`` is merged + (see :attribute:`global_merge_vars`) + .. These attributes are in the same order as they appear in the Mandrill API docs... .. attribute:: important @@ -211,6 +215,10 @@ using :setting:`MANDRILL_SETTINGS`. For Example:: Merge data must be strings or other JSON-serializable types. (See :ref:`formatting-merge-data` for details.) + .. note:: + + If using :setting:`MANDRILL_SETTINGS` then the message ``dict`` will be merged and overwrite any duplicates. + .. attribute:: merge_vars ``dict``: per-recipient merge variables (most useful with :ref:`mandrill-templates`). The keys