Mandrill: support esp_extra

* Merge esp_extra with Mandrill send payload
* Handle pythonic forms of `recipient_metadata`
  and `template_content` in esp_extra
* DeprecationWarning for Mandrill EmailMessage
  attributes inherited from Djrill
This commit is contained in:
medmunds
2016-05-11 15:08:57 -07:00
parent d1da685d13
commit a0b92bee7a
4 changed files with 390 additions and 218 deletions

View File

@@ -1,6 +1,7 @@
import warnings
from datetime import datetime
from ..exceptions import AnymailRequestsAPIError
from ..exceptions import AnymailRequestsAPIError, AnymailWarning
from ..message import AnymailRecipientStatus, ANYMAIL_STATUSES
from ..utils import last, combine, get_anymail_setting
@@ -43,14 +44,8 @@ class MandrillBackend(AnymailRequestsBackend):
return recipient_status
def _expand_merge_vars(vardict):
"""Convert a Python dict to an array of name-content used by Mandrill.
{ name: value, ... } --> [ {'name': name, 'content': value }, ... ]
"""
# For testing reproducibility, we sort the keys
return [{'name': name, 'content': vardict[name]}
for name in sorted(vardict.keys())]
class DjrillDeprecationWarning(AnymailWarning, DeprecationWarning):
"""Warning for features carried over from Djrill that will be removed soon"""
def encode_date_for_mandrill(dt):
@@ -69,6 +64,10 @@ def encode_date_for_mandrill(dt):
class MandrillPayload(RequestsPayload):
def __init__(self, *args, **kwargs):
self.esp_extra = {} # late-bound in serialize_data
super(MandrillPayload, self).__init__(*args, **kwargs)
def get_api_endpoint(self):
if 'template_name' in self.data:
return "messages/send-template.json"
@@ -76,6 +75,7 @@ class MandrillPayload(RequestsPayload):
return "messages/send.json"
def serialize_data(self):
self.process_esp_extra()
return self.serialize_json(self.data)
#
@@ -89,7 +89,9 @@ class MandrillPayload(RequestsPayload):
}
def set_from_email(self, email):
if not getattr(self.message, "use_template_from", False): # Djrill compat!
if getattr(self.message, "use_template_from", False):
self.deprecation_warning('message.use_template_from', 'message.from_email = None')
else:
self.data["message"]["from_email"] = email.email
if email.name:
self.data["message"]["from_name"] = email.name
@@ -100,7 +102,9 @@ class MandrillPayload(RequestsPayload):
to_list.append({"email": email.email, "name": email.name, "type": recipient_type})
def set_subject(self, subject):
if not getattr(self.message, "use_template_subject", False): # Djrill compat!
if getattr(self.message, "use_template_subject", False):
self.deprecation_warning('message.use_template_subject', 'message.subject = None')
else:
self.data["message"]["subject"] = subject
def set_reply_to(self, emails):
@@ -166,9 +170,59 @@ class MandrillPayload(RequestsPayload):
]
def set_esp_extra(self, extra):
# late bind in serialize_data, so that obsolete Djrill attrs can contribute
self.esp_extra = extra
def process_esp_extra(self):
if self.esp_extra is not None and len(self.esp_extra) > 0:
esp_extra = self.esp_extra
# Convert pythonic template_content dict to Mandrill name/content list
try:
template_content = esp_extra['template_content']
except KeyError:
pass
else:
if hasattr(template_content, 'items'): # if it's dict-like
if esp_extra is self.esp_extra:
esp_extra = self.esp_extra.copy() # don't modify caller's value
esp_extra['template_content'] = [
{'name': var, 'content': value}
for var, value in template_content.items()]
# Convert pythonic recipient_metadata dict to Mandrill rcpt/values list
try:
recipient_metadata = esp_extra['message']['recipient_metadata']
except KeyError:
pass
else:
if hasattr(recipient_metadata, 'keys'): # if it's dict-like
if esp_extra['message'] is self.esp_extra['message']:
esp_extra['message'] = self.esp_extra['message'].copy() # don't modify caller's value
# For testing reproducibility, we sort the recipients
esp_extra['message']['recipient_metadata'] = [
{'rcpt': rcpt, 'values': recipient_metadata[rcpt]}
for rcpt in sorted(recipient_metadata.keys())]
# Merge esp_extra with payload data: shallow merge within ['message'] and top-level keys
self.data.update({k:v for k,v in esp_extra.items() if k != 'message'})
try:
self.data['message'].update(esp_extra['message'])
except KeyError:
pass
# Djrill leftovers
# Djrill deprecated message attrs
def deprecation_warning(self, feature, replacement=None):
msg = "Djrill's `%s` will be removed in an upcoming Anymail release." % feature
if replacement:
msg += " Use `%s` instead." % replacement
warnings.warn(msg, DjrillDeprecationWarning)
def deprecated_to_esp_extra(self, attr, in_message_dict=False):
feature = "message.%s" % attr
if in_message_dict:
replacement = "message.esp_extra = {'message': {'%s': <value>}}" % attr
else:
replacement = "message.esp_extra = {'%s': <value>}" % attr
self.deprecation_warning(feature, replacement)
esp_message_attrs = (
('async', last, None),
@@ -188,25 +242,40 @@ class MandrillPayload(RequestsPayload):
('subaccount', last, None),
('google_analytics_domains', last, None),
('google_analytics_campaign', last, None),
('global_merge_vars', combine, None),
('merge_vars', combine, None),
('recipient_metadata', combine, None),
('template_content', combine, _expand_merge_vars),
('template_name', last, None),
('template_content', combine, None),
)
def set_async(self, async):
self.data["async"] = async
self.deprecated_to_esp_extra('async')
self.esp_extra['async'] = async
def set_ip_pool(self, ip_pool):
self.data["ip_pool"] = ip_pool
self.deprecated_to_esp_extra('ip_pool')
self.esp_extra['ip_pool'] = ip_pool
def set_global_merge_vars(self, global_merge_vars):
self.deprecation_warning('message.global_merge_vars', 'message.merge_global_data')
self.set_merge_global_data(global_merge_vars)
def set_merge_vars(self, merge_vars):
self.deprecation_warning('message.merge_vars', 'message.merge_data')
self.set_merge_data(merge_vars)
def set_template_name(self, template_name):
self.deprecation_warning('message.template_name', 'message.template_id')
self.set_template_id(template_name)
def set_template_content(self, template_content):
self.data["template_content"] = template_content
self.deprecated_to_esp_extra('template_content')
self.esp_extra['template_content'] = template_content
def set_recipient_metadata(self, recipient_metadata):
# For testing reproducibility, we sort the recipients
self.data['message']['recipient_metadata'] = [
{'rcpt': rcpt, 'values': recipient_metadata[rcpt]}
for rcpt in sorted(recipient_metadata.keys())
]
self.deprecated_to_esp_extra('recipient_metadata', in_message_dict=True)
self.esp_extra.setdefault('message', {})['recipient_metadata'] = recipient_metadata
# Set up simple set_<attr> functions for any missing esp_message_attrs attrs
# (avoids dozens of simple `self.data["message"][<attr>] = value` functions)
@@ -225,7 +294,8 @@ class MandrillPayload(RequestsPayload):
def make_setter(attr, setter_name):
# sure wish we could use functools.partial to create instance methods (descriptors)
def setter(self, value):
self.data["message"][attr] = value
self.deprecated_to_esp_extra(attr, in_message_dict=True)
self.esp_extra.setdefault('message', {})[attr] = value
setter.__name__ = setter_name
return setter

View File

@@ -97,9 +97,38 @@ which is the secure, production version of Mandrill's 1.0 API.
esp_extra support
-----------------
Anymail's Mandrill backend does not yet implement the
:attr:`~anymail.message.AnymailMessage.esp_extra` feature.
To use Mandrill features not directly supported by Anymail, you can
set a message's :attr:`~anymail.message.AnymailMessage.esp_extra` to
a `dict` of parameters to merge into Mandrill's `messages/send API`_ call.
Note that a few parameters go at the top level, but Mandrill expects
most options within a `'message'` sub-dict---be sure to check their
API docs:
.. code-block:: python
message.esp_extra = {
# Mandrill expects 'ip_pool' at top level...
'ip_pool': 'Bulk Pool',
# ... but 'subaccount' must be within a 'message' dict:
'message': {
'subaccount': 'Marketing Dept.'
}
}
Anymail has special handling that lets you specify Mandrill's
`'recipient_metadata'` as a simple, pythonic `dict` (similar in form
to Anymail's :attr:`~anymail.message.AnymailMessage.merge_data`),
rather than Mandrill's more complex list of rcpt/values dicts.
You can use whichever style you prefer (but either way,
recipient_metadata must be in `esp_extra['message']`).
Similary, Anymail allows Mandrill's `'template_content'` in esp_extra
(top level) either as a pythonic `dict` (similar to Anymail's
:attr:`~anymail.message.AnymailMessage.merge_global_data`) or
as Mandrill's more complex list of name/content dicts.
.. _messages/send API:
https://mandrillapp.com/api/docs/messages.JSON.html#method=send
.. _mandrill-templates:
@@ -222,16 +251,21 @@ Changes to settings
the values from :setting:`ANYMAIL_SEND_DEFAULTS`.
``MANDRILL_SUBACCOUNT``
Use :setting:`ANYMAIL_MANDRILL_SEND_DEFAULTS`:
Set :ref:`esp_extra <mandrill-esp-extra>`
globally in :setting:`ANYMAIL_SEND_DEFAULTS`:
.. code-block:: python
ANYMAIL = {
...
"MANDRILL_SEND_DEFAULTS": {
"esp_extra": {
"message": {
"subaccount": "<your subaccount>"
}
}
}
}
``MANDRILL_IGNORE_RECIPIENT_STATUS``
Renamed to :setting:`ANYMAIL_IGNORE_RECIPIENT_STATUS`
@@ -290,13 +324,30 @@ Changes to EmailMessage attributes
to use the values from the stored template.
**Other Mandrill-specific attributes**
Are currently still supported by Anymail's Mandrill backend,
but will be ignored by other Anymail backends.
Djrill allowed nearly all Mandrill API parameters to be set
as attributes directly on an EmailMessage. With Anymail, you
should instead set these in the message's
:ref:`esp_extra <mandrill-esp-extra>` dict as described above.
Although the Djrill style attributes are still supported (for now),
Anymail will issue a :exc:`DeprecationWarning` if you try to use them.
These warnings are visible during tests (with Django's default test
runner), and will explain how to update your code.
You can also use the following git grep expression to find potential
problems:
.. code-block:: console
git grep -w \
-e 'async' -e 'auto_html' -e 'auto_text' -e 'from_name' -e 'global_merge_vars' \
-e 'google_analytics_campaign' -e 'google_analytics_domains' -e 'important' \
-e 'inline_css' -e 'ip_pool' -e 'merge_language' -e 'merge_vars' \
-e 'preserve_recipients' -e 'recipient_metadata' -e 'return_path_domain' \
-e 'signing_domain' -e 'subaccount' -e 'template_content' -e 'template_name' \
-e 'tracking_domain' -e 'url_strip_qs' -e 'use_template_from' -e 'use_template_subject' \
-e 'view_content_link'
It's best to eliminate them if they're not essential
to your code. In the future, the Mandrill-only attributes
will be moved into the
:attr:`~anymail.message.AnymailMessage.esp_extra` dict.
**Inline images**
Djrill (incorrectly) used the presence of a :mailheader:`Content-ID`

View File

@@ -401,6 +401,69 @@ class MandrillBackendAnymailFeatureTests(MandrillBackendMockAPITestCase):
data = self.get_api_call_json()
self.assertNotIn('subject', data['message'])
def test_esp_extra(self):
self.message.esp_extra = {
'ip_pool': 'Bulk Pool', # Mandrill send param that goes at top level of API payload
'message': {
'subaccount': 'Marketing Dept.' # param that goes within message dict
}
}
self.message.tags = ['test-tag'] # make sure non-esp_extra params are merged
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['ip_pool'], 'Bulk Pool')
self.assertEqual(data['message']['subaccount'], 'Marketing Dept.')
self.assertEqual(data['message']['tags'], ['test-tag'])
def test_esp_extra_recipient_metadata(self):
"""Anymail allows pythonic recipient_metadata dict"""
self.message.esp_extra = {'message': {'recipient_metadata': {
# Anymail expands simple python dicts into the more-verbose
# rcpt/values lists the Mandrill API uses
"customer@example.com": {'cust_id': "67890", 'order_id': "54321"},
"guest@example.com": {'cust_id': "94107", 'order_id': "43215"} ,
}}}
self.message.send()
data = self.get_api_call_json()
self.assertCountEqual(data['message']['recipient_metadata'], [
{'rcpt': "customer@example.com", 'values': {'cust_id': "67890", 'order_id': "54321"}},
{'rcpt': "guest@example.com", 'values': {'cust_id': "94107", 'order_id': "43215"}}])
# You can also just supply it in Mandrill's native form
self.message.esp_extra = {'message': {'recipient_metadata': [
{'rcpt': "customer@example.com", 'values': {'cust_id': "80806", 'order_id': "70701"}},
{'rcpt': "guest@example.com", 'values': {'cust_id': "21212", 'order_id': "10305"}}]}}
self.message.send()
data = self.get_api_call_json()
self.assertCountEqual(data['message']['recipient_metadata'], [
{'rcpt': "customer@example.com", 'values': {'cust_id': "80806", 'order_id': "70701"}},
{'rcpt': "guest@example.com", 'values': {'cust_id': "21212", 'order_id': "10305"}}])
def test_esp_extra_template_content(self):
"""Anymail allows pythonic template_content dict"""
self.message.template_id = "welcome_template" # forces send-template API and default template_content
self.message.esp_extra = {'template_content': {
# Anymail expands simple python dicts into the more-verbose name/content
# structures the Mandrill API uses
'HEADLINE': "<h1>Specials Just For *|FNAME|*</h1>",
'OFFER_BLOCK': "<p><em>Half off</em> all fruit</p>",
}}
self.message.send()
data = self.get_api_call_json()
self.assertCountEqual(data['template_content'], [
{'name': "HEADLINE", 'content': "<h1>Specials Just For *|FNAME|*</h1>"},
{'name': "OFFER_BLOCK", 'content': "<p><em>Half off</em> all fruit</p>"}])
# You can also just supply it in Mandrill's native form
self.message.esp_extra = {'template_content': [
{'name': "HEADLINE", 'content': "<h1>Exciting offers for *|FNAME|*</h1>"},
{'name': "OFFER_BLOCK", 'content': "<p><em>25% off</em> all fruit</p>"}]}
self.message.send()
data = self.get_api_call_json()
self.assertCountEqual(data['template_content'], [
{'name': "HEADLINE", 'content': "<h1>Exciting offers for *|FNAME|*</h1>"},
{'name': "OFFER_BLOCK", 'content': "<p><em>25% off</em> all fruit</p>"}])
def test_default_omits_options(self):
"""Make sure by default we don't send any ESP-specific options.
@@ -411,11 +474,15 @@ class MandrillBackendAnymailFeatureTests(MandrillBackendMockAPITestCase):
self.message.send()
self.assert_esp_called("/messages/send.json")
data = self.get_api_call_json()
self.assertNotIn('global_merge_vars', data['message'])
self.assertNotIn('merge_vars', data['message'])
self.assertNotIn('metadata', data['message'])
self.assertNotIn('send_at', data)
self.assertNotIn('tags', data['message'])
self.assertNotIn('track_opens', data['message'])
self.assertNotIn('template_content', data['message'])
self.assertNotIn('template_name', data['message'])
self.assertNotIn('track_clicks', data['message'])
self.assertNotIn('track_opens', data['message'])
# noinspection PyUnresolvedReferences
def test_send_attaches_anymail_status(self):

View File

@@ -2,54 +2,85 @@ from datetime import date
from django.core import mail
from django.test import override_settings
from anymail.exceptions import AnymailAPIError, AnymailSerializationError
from anymail.exceptions import AnymailSerializationError
from .test_mandrill_backend import MandrillBackendMockAPITestCase
class MandrillBackendDjrillFeatureTests(MandrillBackendMockAPITestCase):
"""Test backend support for features leftover from Djrill"""
"""Test backend support for deprecated features leftover from Djrill"""
# Most of these features should be moved to esp_extra.
# The template and merge_ta
# These features should now be accessed through esp_extra
def test_djrill_message_options(self):
self.message.url_strip_qs = True
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"
def test_async(self):
self.message.async = True
self.message.ip_pool = "Bulk Pool"
with self.assertWarnsRegex(DeprecationWarning, 'async'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['url_strip_qs'], True)
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_google_analytics(self):
self.message.google_analytics_domains = ["example.com"]
def test_auto_html(self):
self.message.auto_html = True
with self.assertWarnsRegex(DeprecationWarning, 'auto_html'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['auto_html'], True)
def test_auto_text(self):
self.message.auto_text = True
with self.assertWarnsRegex(DeprecationWarning, 'auto_text'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['auto_text'], True)
def test_google_analytics_campaign(self):
self.message.google_analytics_campaign = "Email Receipts"
with self.assertWarnsRegex(DeprecationWarning, 'google_analytics_campaign'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['google_analytics_campaign'], "Email Receipts")
def test_google_analytics_domains(self):
self.message.google_analytics_domains = ["example.com"]
with self.assertWarnsRegex(DeprecationWarning, 'google_analytics_domains'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['google_analytics_domains'], ["example.com"])
self.assertEqual(data['message']['google_analytics_campaign'], "Email Receipts")
def test_important(self):
self.message.important = True
with self.assertWarnsRegex(DeprecationWarning, 'important'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['important'], True)
def test_inline_css(self):
self.message.inline_css = True
with self.assertWarnsRegex(DeprecationWarning, 'inline_css'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['inline_css'], True)
def test_ip_pool(self):
self.message.ip_pool = "Bulk Pool"
with self.assertWarnsRegex(DeprecationWarning, 'ip_pool'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['ip_pool'], "Bulk Pool")
def test_merge_language(self):
self.message.merge_language = "mailchimp"
with self.assertWarnsRegex(DeprecationWarning, 'merge_language'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['merge_language'], "mailchimp")
def test_preserve_recipients(self):
self.message.preserve_recipients = True
with self.assertWarnsRegex(DeprecationWarning, 'preserve_recipients'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['preserve_recipients'], True)
def test_recipient_metadata(self):
self.message.recipient_metadata = {
@@ -58,33 +89,87 @@ class MandrillBackendDjrillFeatureTests(MandrillBackendMockAPITestCase):
"customer@example.com": {'cust_id': "67890", 'order_id': "54321"},
"guest@example.com": {'cust_id': "94107", 'order_id': "43215"}
}
with self.assertWarnsRegex(DeprecationWarning, 'recipient_metadata'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['recipient_metadata'],
[{'rcpt': "customer@example.com",
self.assertCountEqual(data['message']['recipient_metadata'], [
{'rcpt': "customer@example.com",
'values': {'cust_id': "67890", 'order_id': "54321"}},
{'rcpt': "guest@example.com",
'values': {'cust_id': "94107", 'order_id': "43215"}}
])
'values': {'cust_id': "94107", 'order_id': "43215"}}])
def test_no_subaccount_by_default(self):
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
def test_return_path_domain(self):
self.message.return_path_domain = "support.example.com"
with self.assertWarnsRegex(DeprecationWarning, 'return_path_domain'):
self.message.send()
data = self.get_api_call_json()
self.assertFalse('subaccount' in data['message'])
self.assertEqual(data['message']['return_path_domain'], "support.example.com")
@override_settings(ANYMAIL_MANDRILL_SEND_DEFAULTS={'subaccount': 'test_subaccount'})
def test_subaccount_setting(self):
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
def test_signing_domain(self):
self.message.signing_domain = "example.com"
with self.assertWarnsRegex(DeprecationWarning, 'signing_domain'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['subaccount'], "test_subaccount")
self.assertEqual(data['message']['signing_domain'], "example.com")
@override_settings(ANYMAIL_MANDRILL_SEND_DEFAULTS={'subaccount': 'global_setting_subaccount'})
def test_subaccount_message_overrides_setting(self):
message = mail.EmailMessage('Subject', 'Body', 'from@example.com', ['to@example.com'])
message.subaccount = "individual_message_subaccount" # should override global setting
message.send()
def test_subaccount(self):
self.message.subaccount = "marketing-dept"
with self.assertWarnsRegex(DeprecationWarning, 'subaccount'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['subaccount'], "individual_message_subaccount")
self.assertEqual(data['message']['subaccount'], "marketing-dept")
def test_template_content(self):
self.message.template_content = {
'HEADLINE': "<h1>Specials Just For *|FNAME|*</h1>",
'OFFER_BLOCK': "<p><em>Half off</em> all fruit</p>"
}
with self.assertWarnsRegex(DeprecationWarning, 'template_content'):
self.message.send()
data = self.get_api_call_json()
# Anymail expands simple python dicts into the more-verbose name/content
# structures the Mandrill API uses
self.assertCountEqual(data['template_content'], [
{'name': "HEADLINE", 'content': "<h1>Specials Just For *|FNAME|*</h1>"},
{'name': "OFFER_BLOCK", 'content': "<p><em>Half off</em> all fruit</p>"}])
def test_tracking_domain(self):
self.message.tracking_domain = "click.example.com"
with self.assertWarnsRegex(DeprecationWarning, 'tracking_domain'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['tracking_domain'], "click.example.com")
def test_url_strip_qs(self):
self.message.url_strip_qs = True
with self.assertWarnsRegex(DeprecationWarning, 'url_strip_qs'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['url_strip_qs'], True)
def test_use_template_from(self):
self.message.template_id = "PERSONALIZED_SPECIALS" # forces send-template api
self.message.use_template_from = True
with self.assertWarnsRegex(DeprecationWarning, 'use_template_from'):
self.message.send()
data = self.get_api_call_json()
self.assertNotIn('from_email', data['message'])
self.assertNotIn('from_name', data['message'])
def test_use_template_subject(self):
self.message.template_id = "PERSONALIZED_SPECIALS" # force send-template API
self.message.use_template_subject = True
with self.assertWarnsRegex(DeprecationWarning, 'use_template_subject'):
self.message.send()
data = self.get_api_call_json()
self.assertNotIn('subject', data['message'])
def test_view_content_link(self):
self.message.view_content_link = True
with self.assertWarnsRegex(DeprecationWarning, 'view_content_link'):
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['view_content_link'], True)
def test_default_omits_options(self):
"""Make sure by default we don't send any Mandrill-specific options.
@@ -96,25 +181,25 @@ class MandrillBackendDjrillFeatureTests(MandrillBackendMockAPITestCase):
self.message.send()
self.assert_esp_called("/messages/send.json")
data = self.get_api_call_json()
self.assertFalse('from_name' in data['message'])
self.assertFalse('bcc_address' in data['message'])
self.assertFalse('important' in data['message'])
self.assertFalse('auto_text' in data['message'])
self.assertFalse('auto_html' in data['message'])
self.assertFalse('inline_css' in data['message'])
self.assertFalse('url_strip_qs' in data['message'])
self.assertFalse('preserve_recipients' in data['message'])
self.assertFalse('view_content_link' in data['message'])
self.assertFalse('tracking_domain' in data['message'])
self.assertFalse('signing_domain' in data['message'])
self.assertFalse('return_path_domain' in data['message'])
self.assertFalse('subaccount' in data['message'])
self.assertFalse('google_analytics_domains' in data['message'])
self.assertFalse('google_analytics_campaign' in data['message'])
self.assertFalse('merge_language' in data['message'])
self.assertFalse('auto_text' in data['message'])
self.assertFalse('bcc_address' in data['message'])
self.assertFalse('from_name' in data['message'])
self.assertFalse('global_merge_vars' in data['message'])
self.assertFalse('google_analytics_campaign' in data['message'])
self.assertFalse('google_analytics_domains' in data['message'])
self.assertFalse('important' in data['message'])
self.assertFalse('inline_css' in data['message'])
self.assertFalse('merge_language' in data['message'])
self.assertFalse('merge_vars' in data['message'])
self.assertFalse('preserve_recipients' in data['message'])
self.assertFalse('recipient_metadata' in data['message'])
self.assertFalse('return_path_domain' in data['message'])
self.assertFalse('signing_domain' in data['message'])
self.assertFalse('subaccount' in data['message'])
self.assertFalse('tracking_domain' in data['message'])
self.assertFalse('url_strip_qs' in data['message'])
self.assertFalse('view_content_link' in data['message'])
# Options at top level of api params (not in message dict):
self.assertFalse('async' in data)
self.assertFalse('ip_pool' in data)
@@ -125,121 +210,20 @@ class MandrillBackendDjrillFeatureTests(MandrillBackendMockAPITestCase):
with self.assertRaises(AnymailSerializationError):
self.message.send()
@override_settings(ANYMAIL_SEND_DEFAULTS={
'from_name': 'Djrill Test',
'important': True,
'auto_text': True,
'auto_html': True,
'inline_css': True,
'url_strip_qs': True,
'preserve_recipients': True,
'view_content_link': True,
'subaccount': 'example-subaccount',
'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'],
'merge_language': 'mailchimp',
'async': True,
'ip_pool': 'Pool1',
'invalid': 'invalid',
})
class MandrillBackendDjrillSendDefaultsTests(MandrillBackendMockAPITestCase):
"""Tests backend support for global SEND_DEFAULTS"""
def test_global_options(self):
"""Test that any global settings get passed through
"""
self.message.send()
@override_settings(ANYMAIL_MANDRILL_SEND_DEFAULTS={'subaccount': 'test_subaccount'})
def test_subaccount_setting(self):
"""Global, non-esp_extra version of subaccount default"""
with self.assertWarnsRegex(DeprecationWarning, 'subaccount'):
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
data = self.get_api_call_json()
self.assertEqual(data['message']['from_name'], 'Djrill Test')
self.assertTrue(data['message']['important'])
self.assertTrue(data['message']['auto_text'])
self.assertTrue(data['message']['auto_html'])
self.assertTrue(data['message']['inline_css'])
self.assertTrue(data['message']['url_strip_qs'])
self.assertTrue(data['message']['preserve_recipients'])
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']['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']['merge_language'], 'mailchimp')
self.assertFalse('recipient_metadata' in data['message'])
# Options at top level of api params (not in message dict):
self.assertTrue(data['async'])
self.assertEqual(data['ip_pool'], 'Pool1')
# Option that shouldn't be added
self.assertFalse('invalid' in data['message'])
self.assertEqual(data['message']['subaccount'], "test_subaccount")
def test_global_options_override(self):
"""Test that manually settings options overrides global settings
"""
self.message.from_name = "override"
self.message.important = False
self.message.auto_text = False
self.message.auto_html = False
self.message.inline_css = False
self.message.url_strip_qs = False
self.message.preserve_recipients = False
self.message.view_content_link = False
self.message.subaccount = "override"
self.message.tracking_domain = "override.example.com"
self.message.signing_domain = "override.example.com"
self.message.return_path_domain = "override.example.com"
self.message.google_analytics_domains = ['override.example.com']
self.message.google_analytics_campaign = ['UA-99999999-1']
self.message.merge_language = 'handlebars'
self.message.async = False
self.message.ip_pool = "Bulk Pool"
self.message.send()
@override_settings(ANYMAIL_MANDRILL_SEND_DEFAULTS={'subaccount': 'global_setting_subaccount'})
def test_subaccount_message_overrides_setting(self):
"""Global, non-esp_extra version of subaccount default"""
message = mail.EmailMessage('Subject', 'Body', 'from@example.com', ['to@example.com'])
message.subaccount = "individual_message_subaccount" # should override global setting
with self.assertWarnsRegex(DeprecationWarning, 'subaccount'):
message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['from_name'], 'override')
self.assertFalse(data['message']['important'])
self.assertFalse(data['message']['auto_text'])
self.assertFalse(data['message']['auto_html'])
self.assertFalse(data['message']['inline_css'])
self.assertFalse(data['message']['url_strip_qs'])
self.assertFalse(data['message']['preserve_recipients'])
self.assertFalse(data['message']['view_content_link'])
self.assertEqual(data['message']['subaccount'], 'override')
self.assertEqual(data['message']['tracking_domain'], 'override.example.com')
self.assertEqual(data['message']['signing_domain'], 'override.example.com')
self.assertEqual(data['message']['return_path_domain'], 'override.example.com')
self.assertEqual(data['message']['google_analytics_domains'], ['override.example.com'])
self.assertEqual(data['message']['google_analytics_campaign'], ['UA-99999999-1'])
self.assertEqual(data['message']['merge_language'], 'handlebars')
# Options at top level of api params (not in message dict):
self.assertFalse(data['async'])
self.assertEqual(data['ip_pool'], 'Bulk Pool')
class MandrillBackendDjrillTemplateTests(MandrillBackendMockAPITestCase):
"""Test backend support for ESP templating features"""
# Holdovers from Djrill, until we design Anymail's normalized esp-template support
def test_merge_language(self):
self.message.merge_language = "mailchimp"
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['merge_language'], "mailchimp")
def test_template_content(self):
self.message.template_content = {
'HEADLINE': "<h1>Specials Just For *|FNAME|*</h1>",
'OFFER_BLOCK': "<p><em>Half off</em> all fruit</p>"
}
self.message.send()
data = self.get_api_call_json()
# Anymail expands simple python dicts into the more-verbose name/content
# structures the Mandrill API uses
self.assertEqual(data['template_content'],
[{'name': "HEADLINE",
'content': "<h1>Specials Just For *|FNAME|*</h1>"},
{'name': "OFFER_BLOCK",
'content': "<p><em>Half off</em> all fruit</p>"}])
self.assertEqual(data['message']['subaccount'], "individual_message_subaccount")