Clean up global MANDRILL_SETTINGS

* Clean up Djrill backend __init__
* Fold MANDRILL_SUBACCOUNT into
  global_settings logic
* Add some missing override tests
* Update docs
This commit is contained in:
medmunds
2015-12-02 15:58:23 -08:00
parent 5c39e40ea1
commit aa46fadb48
6 changed files with 141 additions and 121 deletions

View File

@@ -26,29 +26,33 @@ class DjrillBackend(BaseEmailBackend):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
""" """Init options from Django settings"""
Set the API key, API url and set the action url.
"""
super(DjrillBackend, self).__init__(**kwargs) super(DjrillBackend, self).__init__(**kwargs)
self.api_key = getattr(settings, "MANDRILL_API_KEY", None)
try:
self.api_key = settings.MANDRILL_API_KEY
except AttributeError:
raise ImproperlyConfigured("Set MANDRILL_API_KEY in settings.py to use Djrill")
self.api_url = getattr(settings, "MANDRILL_API_URL", "https://mandrillapp.com/api/1.0") self.api_url = getattr(settings, "MANDRILL_API_URL", "https://mandrillapp.com/api/1.0")
if not self.api_url.endswith("/"): if not self.api_url.endswith("/"):
self.api_url += "/" self.api_url += "/"
self.session = None
self.global_settings = {} self.global_settings = {}
for setting_key in getattr(settings, "MANDRILL_SETTINGS", {}): try:
if not isinstance(settings.MANDRILL_SETTINGS, dict): self.global_settings.update(settings.MANDRILL_SETTINGS)
raise ImproperlyConfigured("MANDRILL_SETTINGS must be a dict " except AttributeError:
"in the settings.py file.") pass # no MANDRILL_SETTINGS setting
self.global_settings[setting_key] = settings.MANDRILL_SETTINGS[setting_key] except (TypeError, ValueError): # e.g., not enumerable
raise ImproperlyConfigured("MANDRILL_SETTINGS must be a dict or mapping")
try:
self.global_settings["subaccount"] = settings.MANDRILL_SUBACCOUNT
except AttributeError:
pass # no MANDRILL_SUBACCOUNT setting
self.subaccount = getattr(settings, "MANDRILL_SUBACCOUNT", None)
self.ignore_recipient_status = getattr(settings, "MANDRILL_IGNORE_RECIPIENT_STATUS", False) self.ignore_recipient_status = getattr(settings, "MANDRILL_IGNORE_RECIPIENT_STATUS", False)
self.session = None
if not self.api_key:
raise ImproperlyConfigured("You have not set your mandrill api key "
"in the settings.py file.")
def open(self): def open(self):
""" """
@@ -298,6 +302,7 @@ class DjrillBackend(BaseEmailBackend):
# Mandrill attributes that require conversion: # Mandrill attributes that require conversion:
if hasattr(message, 'send_at'): if hasattr(message, 'send_at'):
api_params['send_at'] = self.encode_date_for_mandrill(message.send_at) api_params['send_at'] = self.encode_date_for_mandrill(message.send_at)
# setting send_at in global_settings wouldn't make much sense
def _make_mandrill_to_list(self, message, recipients, recipient_type="to"): def _make_mandrill_to_list(self, message, recipients, recipient_type="to"):
"""Create a Mandrill 'to' field from a list of emails. """Create a Mandrill 'to' field from a list of emails.
@@ -324,9 +329,6 @@ class DjrillBackend(BaseEmailBackend):
'google_analytics_domains', 'google_analytics_campaign', 'google_analytics_domains', 'google_analytics_campaign',
'metadata'] 'metadata']
if self.subaccount:
msg_dict['subaccount'] = self.subaccount
for attr in mandrill_attrs: for attr in mandrill_attrs:
if attr in self.global_settings: if attr in self.global_settings:
msg_dict[attr] = self.global_settings[attr] msg_dict[attr] = self.global_settings[attr]
@@ -336,7 +338,8 @@ class DjrillBackend(BaseEmailBackend):
# Allow simple python dicts in place of Mandrill # Allow simple python dicts in place of Mandrill
# [{name:name, value:value},...] arrays... # [{name:name, value:value},...] arrays...
# Allow merge of global and per message global_merge_var, the former taking precedent # Merge global and per message global_merge_vars
# (in conflicts, per-message vars win)
global_merge_vars = {} global_merge_vars = {}
if 'global_merge_vars' in self.global_settings: if 'global_merge_vars' in self.global_settings:
global_merge_vars.update(self.global_settings['global_merge_vars']) global_merge_vars.update(self.global_settings['global_merge_vars'])

View File

@@ -617,6 +617,7 @@ class DjrillRecipientsRefusedTests(DjrillBackendMockAPITestCase):
'tags': ['djrill'], 'tags': ['djrill'],
'preserve_recipients': True, 'preserve_recipients': True,
'view_content_link': True, 'view_content_link': True,
'subaccount': 'example-subaccount',
'tracking_domain': 'example.com', 'tracking_domain': 'example.com',
'signing_domain': 'example.com', 'signing_domain': 'example.com',
'return_path_domain': 'example.com', 'return_path_domain': 'example.com',
@@ -625,7 +626,7 @@ class DjrillRecipientsRefusedTests(DjrillBackendMockAPITestCase):
'metadata': ['djrill'], 'metadata': ['djrill'],
'merge_language': 'mailchimp', 'merge_language': 'mailchimp',
'global_merge_vars': {'TEST': 'djrill'}, 'global_merge_vars': {'TEST': 'djrill'},
'async': False, 'async': True,
'ip_pool': 'Pool1', 'ip_pool': 'Pool1',
'invalid': 'invalid', 'invalid': 'invalid',
}) })
@@ -644,16 +645,17 @@ class DjrillMandrillGlobalFeatureTests(DjrillBackendMockAPITestCase):
self.assert_mandrill_called("/messages/send.json") self.assert_mandrill_called("/messages/send.json")
data = self.get_api_call_data() data = self.get_api_call_data()
self.assertEqual(data['message']['from_name'], 'Djrill Test') self.assertEqual(data['message']['from_name'], 'Djrill Test')
self.assertTrue(data['message']['important'], True) self.assertTrue(data['message']['important'])
self.assertTrue(data['message']['track_opens'], True) self.assertTrue(data['message']['track_opens'])
self.assertTrue(data['message']['track_clicks'], True) self.assertTrue(data['message']['track_clicks'])
self.assertTrue(data['message']['auto_text'], True) self.assertTrue(data['message']['auto_text'])
self.assertTrue(data['message']['auto_html'], True) self.assertTrue(data['message']['auto_html'])
self.assertTrue(data['message']['inline_css'], True) self.assertTrue(data['message']['inline_css'])
self.assertTrue(data['message']['url_strip_qs'], True) self.assertTrue(data['message']['url_strip_qs'])
self.assertEqual(data['message']['tags'], ['djrill']) self.assertEqual(data['message']['tags'], ['djrill'])
self.assertTrue(data['message']['preserve_recipients'], True) self.assertTrue(data['message']['preserve_recipients'])
self.assertTrue(data['message']['view_content_link'], True) self.assertTrue(data['message']['view_content_link'])
self.assertEqual(data['message']['subaccount'], 'example-subaccount')
self.assertEqual(data['message']['tracking_domain'], 'example.com') self.assertEqual(data['message']['tracking_domain'], 'example.com')
self.assertEqual(data['message']['signing_domain'], 'example.com') self.assertEqual(data['message']['signing_domain'], 'example.com')
self.assertEqual(data['message']['return_path_domain'], 'example.com') self.assertEqual(data['message']['return_path_domain'], 'example.com')
@@ -666,8 +668,7 @@ class DjrillMandrillGlobalFeatureTests(DjrillBackendMockAPITestCase):
self.assertFalse('merge_vars' in data['message']) self.assertFalse('merge_vars' in data['message'])
self.assertFalse('recipient_metadata' in data['message']) self.assertFalse('recipient_metadata' in data['message'])
# Options at top level of api params (not in message dict): # Options at top level of api params (not in message dict):
self.assertFalse('send_at' in data) self.assertTrue(data['async'])
self.assertEqual(data['async'], False)
self.assertEqual(data['ip_pool'], 'Pool1') self.assertEqual(data['ip_pool'], 'Pool1')
# Option that shouldn't be added # Option that shouldn't be added
self.assertFalse('invalid' in data['message']) self.assertFalse('invalid' in data['message'])
@@ -675,44 +676,53 @@ class DjrillMandrillGlobalFeatureTests(DjrillBackendMockAPITestCase):
def test_global_options_override(self): def test_global_options_override(self):
"""Test that manually settings options overrides global settings """Test that manually settings options overrides global settings
""" """
self.message.important = True self.message.from_name = "override"
self.message.auto_text = True self.message.important = False
self.message.auto_html = True self.message.track_opens = False
self.message.inline_css = True self.message.track_clicks = False
self.message.preserve_recipients = True self.message.auto_text = False
self.message.auto_html = False
self.message.inline_css = False
self.message.url_strip_qs = False
self.message.tags = ['override']
self.message.preserve_recipients = False
self.message.view_content_link = False self.message.view_content_link = False
self.message.tracking_domain = "click.example.com" self.message.subaccount = "override"
self.message.signing_domain = "example.com" self.message.tracking_domain = "override.example.com"
self.message.return_path_domain = "support.example.com" self.message.signing_domain = "override.example.com"
self.message.subaccount = "marketing-dept" self.message.return_path_domain = "override.example.com"
self.message.async = True self.message.google_analytics_domains = ['override.example.com']
self.message.google_analytics_campaign = ['UA-99999999-1']
self.message.metadata = ['override']
self.message.merge_language = 'handlebars'
self.message.async = False
self.message.ip_pool = "Bulk Pool" self.message.ip_pool = "Bulk Pool"
self.message.send() self.message.send()
data = self.get_api_call_data() data = self.get_api_call_data()
self.assertEqual(data['message']['important'], True) self.assertEqual(data['message']['from_name'], 'override')
self.assertEqual(data['message']['auto_text'], True) self.assertFalse(data['message']['important'])
self.assertEqual(data['message']['auto_html'], True) self.assertFalse(data['message']['track_opens'])
self.assertEqual(data['message']['inline_css'], True) self.assertFalse(data['message']['track_clicks'])
self.assertEqual(data['message']['preserve_recipients'], True) self.assertFalse(data['message']['auto_text'])
self.assertEqual(data['message']['view_content_link'], False) self.assertFalse(data['message']['auto_html'])
self.assertEqual(data['message']['tracking_domain'], "click.example.com") self.assertFalse(data['message']['inline_css'])
self.assertEqual(data['message']['signing_domain'], "example.com") self.assertFalse(data['message']['url_strip_qs'])
self.assertEqual(data['message']['return_path_domain'], "support.example.com") self.assertEqual(data['message']['tags'], ['override'])
self.assertEqual(data['message']['subaccount'], "marketing-dept") self.assertFalse(data['message']['preserve_recipients'])
self.assertEqual(data['async'], True) self.assertFalse(data['message']['view_content_link'])
self.assertEqual(data['ip_pool'], "Bulk Pool") self.assertEqual(data['message']['subaccount'], 'override')
self.assertEqual(data['message']['tracking_domain'], 'override.example.com')
def test_global_options_override_tracking(self): self.assertEqual(data['message']['signing_domain'], 'override.example.com')
"""Test that manually settings options overrides global settings self.assertEqual(data['message']['return_path_domain'], 'override.example.com')
""" self.assertEqual(data['message']['google_analytics_domains'], ['override.example.com'])
self.message.track_opens = False self.assertEqual(data['message']['google_analytics_campaign'], ['UA-99999999-1'])
self.message.track_clicks = False self.assertEqual(data['message']['metadata'], ['override'])
self.message.url_strip_qs = False self.assertEqual(data['message']['merge_language'], 'handlebars')
self.message.send() self.assertEqual(data['message']['global_merge_vars'],
data = self.get_api_call_data() [{'name': 'TEST', 'content': 'djrill'}])
self.assertEqual(data['message']['track_opens'], False) # Options at top level of api params (not in message dict):
self.assertEqual(data['message']['track_clicks'], False) self.assertFalse(data['async'])
self.assertEqual(data['message']['url_strip_qs'], False) self.assertEqual(data['ip_pool'], 'Bulk Pool')
def test_global_merge(self): def test_global_merge(self):
# Test that global settings merge in # Test that global settings merge in

View File

@@ -7,40 +7,36 @@ from .mock_backend import DjrillBackendMockAPITestCase
class DjrillMandrillSubaccountTests(DjrillBackendMockAPITestCase): class DjrillMandrillSubaccountTests(DjrillBackendMockAPITestCase):
"""Test Djrill backend support for Mandrill subaccounts""" """Test Djrill backend support for Mandrill subaccounts"""
def test_send_basic(self): def test_no_subaccount_by_default(self):
mail.send_mail('Subject here', 'Here is the message.', mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
'from@example.com', ['to@example.com'], fail_silently=False)
self.assert_mandrill_called("/messages/send.json")
data = self.get_api_call_data() data = self.get_api_call_data()
self.assertEqual(data['message']['subject'], "Subject here")
self.assertEqual(data['message']['text'], "Here is the message.")
self.assertFalse('from_name' in data['message'])
self.assertEqual(data['message']['from_email'], "from@example.com")
self.assertEqual(len(data['message']['to']), 1)
self.assertEqual(data['message']['to'][0]['email'], "to@example.com")
self.assertFalse('subaccount' in data['message']) self.assertFalse('subaccount' in data['message'])
@override_settings(MANDRILL_SUBACCOUNT="test_subaccount") @override_settings(MANDRILL_SETTINGS={'subaccount': 'test_subaccount'})
def test_send_from_subaccount(self): def test_subaccount_setting(self):
mail.send_mail('Subject here', 'Here is the message.', mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
'from@example.com', ['to@example.com'], fail_silently=False)
self.assert_mandrill_called("/messages/send.json")
data = self.get_api_call_data() data = self.get_api_call_data()
self.assertEqual(data['message']['subject'], "Subject here")
self.assertEqual(data['message']['text'], "Here is the message.")
self.assertFalse('from_name' in data['message'])
self.assertEqual(data['message']['from_email'], "from@example.com")
self.assertEqual(len(data['message']['to']), 1)
self.assertEqual(data['message']['to'][0]['email'], "to@example.com")
self.assertEqual(data['message']['subaccount'], "test_subaccount") self.assertEqual(data['message']['subaccount'], "test_subaccount")
@override_settings(MANDRILL_SUBACCOUNT="global_setting_subaccount") @override_settings(MANDRILL_SETTINGS={'subaccount': 'global_setting_subaccount'})
def test_subaccount_message_overrides_setting(self): def test_subaccount_message_overrides_setting(self):
message = mail.EmailMessage( message = mail.EmailMessage('Subject', 'Body', 'from@example.com', ['to@example.com'])
'Subject here', 'Here is the message',
'from@example.com', ['to@example.com'])
message.subaccount = "individual_message_subaccount" # should override global setting message.subaccount = "individual_message_subaccount" # should override global setting
message.send() message.send()
self.assert_mandrill_called("/messages/send.json") data = self.get_api_call_data()
self.assertEqual(data['message']['subaccount'], "individual_message_subaccount")
# Djrill 1.x offered dedicated MANDRILL_SUBACCOUNT setting.
# In Djrill 2.x, you should use the MANDRILL_SETTINGS dict as in the earlier tests.
# But we still support the old setting for compatibility:
@override_settings(MANDRILL_SUBACCOUNT="legacy_setting_subaccount")
def test_subaccount_legacy_setting(self):
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
data = self.get_api_call_data()
self.assertEqual(data['message']['subaccount'], "legacy_setting_subaccount")
message = mail.EmailMessage('Subject', 'Body', 'from@example.com', ['to@example.com'])
message.subaccount = "individual_message_subaccount" # should override legacy setting
message.send()
data = self.get_api_call_data() data = self.get_api_call_data()
self.assertEqual(data['message']['subaccount'], "individual_message_subaccount") self.assertEqual(data['message']['subaccount'], "individual_message_subaccount")

View File

@@ -96,6 +96,9 @@ Other Djrill 2.0 Changes
(You can also directly manage your own long-lived Djrill connection across multiple sends, (You can also directly manage your own long-lived Djrill connection across multiple sends,
by calling open and close on :ref:`Django's email backend <django:topic-email-backends>`.) by calling open and close on :ref:`Django's email backend <django:topic-email-backends>`.)
* Add global :setting:`MANDRILL_SETTINGS` dict that can provide defaults
for most Djrill message options.
* Add :exc:`djrill.NotSerializableForMandrillError` * Add :exc:`djrill.NotSerializableForMandrillError`

View File

@@ -61,21 +61,42 @@ on invalid or rejected recipients. (Default ``False``.)
.. versionadded:: 2.0 .. versionadded:: 2.0
.. setting:: MANDRILL_SETTINGS
MANDRILL_SETTINGS
~~~~~~~~~~~~~~~~~
You can supply global default options to apply to all messages sent through Djrill.
Set :setting:`!MANDRILL_SETTINGS` to a dict of these options. Example::
MANDRILL_SETTINGS = {
'subaccount': 'client-347',
'tracking_domain': 'example.com',
'track_opens': True,
}
See :ref:`mandrill-send-support` for a list of available options. (Everything
*except* :attr:`merge_vars`, :attr:`recipient_metadata`, and :attr:`send_at`
can be used with :setting:`!MANDRILL_SETTINGS`.)
Attributes set on individual EmailMessage objects will override the global
:setting:`!MANDRILL_SETTINGS` for that message. :attr:`global_merge_vars`
on an EmailMessage will be merged with any ``global_merge_vars`` in
:setting:`!MANDRILL_SETTINGS` (with the ones on the EmailMessage taking
precedence if there are conflicting var names).
.. versionadded:: 2.0
.. setting:: MANDRILL_SUBACCOUNT .. setting:: MANDRILL_SUBACCOUNT
MANDRILL_SUBACCOUNT MANDRILL_SUBACCOUNT
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
If you are using Mandrill's `subaccounts`_ feature, you can globally set the Prior to Djrill 2.0, the :setting:`!MANDRILL_SUBACCOUNT` setting could
subaccount for all messages sent through Djrill:: be used to globally set the `Mandrill subaccount <subaccounts>`_.
Although this is still supported for compatibility with existing code,
MANDRILL_SUBACCOUNT = "client-347" new code should set a global subaccount in :setting:`MANDRILL_SETTINGS`
as shown above.
(You can also set or override the :attr:`subaccount` on each individual message,
with :ref:`Mandrill-specific sending options <mandrill-send-support>`.)
.. versionadded:: 1.0
MANDRILL_SUBACCOUNT global setting
.. _subaccounts: http://help.mandrill.com/entries/25523278-What-are-subaccounts- .. _subaccounts: http://help.mandrill.com/entries/25523278-What-are-subaccounts-

View File

@@ -103,24 +103,17 @@ Some notes and limitations:
Mandrill-Specific Options Mandrill-Specific Options
------------------------- -------------------------
.. setting:: MANDRILL_SETTINGS
Most of the options from the Mandrill Most of the options from the Mandrill
`messages/send API <https://mandrillapp.com/api/docs/messages.html#method=send>`_ `messages/send API <https://mandrillapp.com/api/docs/messages.html#method=send>`_
`message` struct can be set directly on an :class:`~django.core.mail.EmailMessage` `message` struct can be set directly on an :class:`~django.core.mail.EmailMessage`
(or subclass) object: (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,
}
.. note:: .. note::
``merge_vars`` and ``recipient_metadata`` cannot be set globally. ``global_merge_vars`` is merged
(see :attribute:`global_merge_vars`) You can set global defaults for common options with the
:setting:`MANDRILL_SETTINGS` setting, to avoid having to
set them on every message.
.. These attributes are in the same order as they appear in the Mandrill API docs... .. These attributes are in the same order as they appear in the Mandrill API docs...
@@ -215,10 +208,6 @@ using :setting:`MANDRILL_SETTINGS`. For Example::
Merge data must be strings or other JSON-serializable types. Merge data must be strings or other JSON-serializable types.
(See :ref:`formatting-merge-data` for details.) (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 .. attribute:: merge_vars
``dict``: per-recipient merge variables (most useful with :ref:`mandrill-templates`). The keys ``dict``: per-recipient merge variables (most useful with :ref:`mandrill-templates`). The keys
@@ -244,8 +233,6 @@ using :setting:`MANDRILL_SETTINGS`. For Example::
.. attribute:: subaccount .. attribute:: subaccount
``str``: the ID of one of your subaccounts to use for sending this message. ``str``: the ID of one of your subaccounts to use for sending this message.
(The subaccount on an individual message will override any global
:setting:`MANDRILL_SUBACCOUNT` setting.)
.. versionadded:: 0.7 .. versionadded:: 0.7