From a0b92bee7a62576c880e1d6690d400c95afaf51f Mon Sep 17 00:00:00 2001 From: medmunds Date: Wed, 11 May 2016 15:08:57 -0700 Subject: [PATCH] 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 --- anymail/backends/mandrill.py | 116 ++++++-- docs/esps/mandrill.rst | 71 ++++- tests/test_mandrill_backend.py | 69 ++++- tests/test_mandrill_djrill_features.py | 352 ++++++++++++------------- 4 files changed, 390 insertions(+), 218 deletions(-) diff --git a/anymail/backends/mandrill.py b/anymail/backends/mandrill.py index 22415c9..be203e4 100644 --- a/anymail/backends/mandrill.py +++ b/anymail/backends/mandrill.py @@ -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): - pass + # late bind in serialize_data, so that obsolete Djrill attrs can contribute + self.esp_extra = extra - # Djrill leftovers + 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 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': }}" % attr + else: + replacement = "message.esp_extra = {'%s': }" % 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_ functions for any missing esp_message_attrs attrs # (avoids dozens of simple `self.data["message"][] = 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 diff --git a/docs/esps/mandrill.rst b/docs/esps/mandrill.rst index 5906a43..6bfd1f6 100644 --- a/docs/esps/mandrill.rst +++ b/docs/esps/mandrill.rst @@ -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,14 +251,19 @@ Changes to settings the values from :setting:`ANYMAIL_SEND_DEFAULTS`. ``MANDRILL_SUBACCOUNT`` - Use :setting:`ANYMAIL_MANDRILL_SEND_DEFAULTS`: + Set :ref:`esp_extra ` + globally in :setting:`ANYMAIL_SEND_DEFAULTS`: .. code-block:: python ANYMAIL = { ... "MANDRILL_SEND_DEFAULTS": { - "subaccount": "" + "esp_extra": { + "message": { + "subaccount": "" + } + } } } @@ -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 ` 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` diff --git a/tests/test_mandrill_backend.py b/tests/test_mandrill_backend.py index 75f9c1a..4bf6762 100644 --- a/tests/test_mandrill_backend.py +++ b/tests/test_mandrill_backend.py @@ -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': "

Specials Just For *|FNAME|*

", + 'OFFER_BLOCK': "

Half off all fruit

", + }} + self.message.send() + data = self.get_api_call_json() + self.assertCountEqual(data['template_content'], [ + {'name': "HEADLINE", 'content': "

Specials Just For *|FNAME|*

"}, + {'name': "OFFER_BLOCK", 'content': "

Half off all fruit

"}]) + + # You can also just supply it in Mandrill's native form + self.message.esp_extra = {'template_content': [ + {'name': "HEADLINE", 'content': "

Exciting offers for *|FNAME|*

"}, + {'name': "OFFER_BLOCK", 'content': "

25% off all fruit

"}]} + self.message.send() + data = self.get_api_call_json() + self.assertCountEqual(data['template_content'], [ + {'name': "HEADLINE", 'content': "

Exciting offers for *|FNAME|*

"}, + {'name': "OFFER_BLOCK", 'content': "

25% off all fruit

"}]) + 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): diff --git a/tests/test_mandrill_djrill_features.py b/tests/test_mandrill_djrill_features.py index 7448588..9a38e0b 100644 --- a/tests/test_mandrill_djrill_features.py +++ b/tests/test_mandrill_djrill_features.py @@ -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" - self.message.send() + 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" - self.message.send() + 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"} } - self.message.send() + 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", - 'values': {'cust_id': "67890", 'order_id': "54321"}}, - {'rcpt': "guest@example.com", - 'values': {'cust_id': "94107", 'order_id': "43215"}} - ]) + 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"}}]) - 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': "

Specials Just For *|FNAME|*

", + 'OFFER_BLOCK': "

Half off all fruit

" + } + 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': "

Specials Just For *|FNAME|*

"}, + {'name': "OFFER_BLOCK", 'content': "

Half off all fruit

"}]) + + 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': "

Specials Just For *|FNAME|*

", - 'OFFER_BLOCK': "

Half off all fruit

" - } - 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': "

Specials Just For *|FNAME|*

"}, - {'name': "OFFER_BLOCK", - 'content': "

Half off all fruit

"}]) + self.assertEqual(data['message']['subaccount'], "individual_message_subaccount")