diff --git a/anymail/backends/sendgrid.py b/anymail/backends/sendgrid.py index 56b45bb..e97e319 100644 --- a/anymail/backends/sendgrid.py +++ b/anymail/backends/sendgrid.py @@ -91,7 +91,13 @@ class SendGridPayload(RequestsPayload): # If esp_extra was also used to set x-smtpapi, need to merge it if "x-smtpapi" in self.data: esp_extra_smtpapi = self.data["x-smtpapi"] - self.smtpapi.update(esp_extra_smtpapi) # need to make this deep merge (for filters)! + for key, value in esp_extra_smtpapi.items(): + if key == "filters": + # merge filters (else it's difficult to mix esp_extra with other features) + self.smtpapi.setdefault(key, {}).update(value) + else: + # all other keys replace any current value + self.smtpapi[key] = value self.data["x-smtpapi"] = self.serialize_json(self.smtpapi) elif "x-smtpapi" in self.data: self.data["x-smtpapi"] = self.serialize_json(self.data["x-smtpapi"]) diff --git a/docs/esps/sendgrid.rst b/docs/esps/sendgrid.rst index bedf991..e9decca 100644 --- a/docs/esps/sendgrid.rst +++ b/docs/esps/sendgrid.rst @@ -117,7 +117,16 @@ Example: message.esp_extra = { 'x-smtpapi': { "asm_group": 1, # Assign SendGrid unsubscribe group for this message - "asm_groups_to_display": [1, 2, 3] + "asm_groups_to_display": [1, 2, 3], + "filters": { + "subscriptiontrack": { # Insert SendGrid subscription management links + "settings": { + "text/html": "If you would like to unsubscribe <% click here %>.", + "text/plain": "If you would like to unsubscribe click here: <% %>.", + "enable": 1 + } + } + } } } diff --git a/tests/test_sendgrid_backend.py b/tests/test_sendgrid_backend.py index 01b9b9d..40ea809 100644 --- a/tests/test_sendgrid_backend.py +++ b/tests/test_sendgrid_backend.py @@ -423,18 +423,35 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase): def test_esp_extra(self): self.message.tags = ["tag"] + self.message.track_clicks = True self.message.esp_extra = { - 'x-smtpapi': {'asm_group_id': 1}, + 'x-smtpapi': { + # Most SendMail options go in the 'x-smtpapi' block... + 'asm_group_id': 1, + 'filters': { + # If you add a filter, you must supply all required settings for it. + 'subscriptiontrack': { + 'settings': { + 'enable': 1, + 'replace': '[unsubscribe_url]', + }, + }, + }, + }, 'newthing': "some param not supported by Anymail", } self.message.send() # Additional send params: data = self.get_api_call_data() self.assertEqual(data['newthing'], "some param not supported by Anymail") - # Should merge x-smtpapi + # Should merge x-smtpapi, and merge filters within x-smtpapi smtpapi = self.get_smtpapi() self.assertEqual(smtpapi['category'], ["tag"]) self.assertEqual(smtpapi['asm_group_id'], 1) + self.assertEqual(smtpapi['filters']['subscriptiontrack'], + {'settings': {'enable': 1, 'replace': '[unsubscribe_url]'}}) # esp_extra merged + self.assertEqual(smtpapi['filters']['clicktrack'], + {'settings': {'enable': 1}}) # Anymail message option preserved # noinspection PyUnresolvedReferences def test_send_attaches_anymail_status(self): diff --git a/tests/utils.py b/tests/utils.py index 94f8e6e..bd505d5 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,10 +1,8 @@ # Anymail test utils import sys -import unittest import os import re -import six import warnings from base64 import b64decode from contextlib import contextmanager @@ -60,19 +58,23 @@ class AnymailTestMixin: finally: warnings.resetwarnings() - # Plus these methods added below: - # assertCountEqual - # assertRaisesRegex - # assertRegex + def assertCountEqual(self, *args, **kwargs): + try: + return super(AnymailTestMixin, self).assertCountEqual(*args, **kwargs) + except TypeError: + return self.assertItemsEqual(*args, **kwargs) # Python 2 -# Add the Python 3 TestCase assertions, if they're not already there. -# (The six implementations cause infinite recursion if installed on -# a py3 TestCase.) -for method in ('assertCountEqual', 'assertRaisesRegex', 'assertRegex'): - try: - getattr(unittest.TestCase, method) - except AttributeError: - setattr(AnymailTestMixin, method, getattr(six, method)) + def assertRaisesRegex(self, *args, **kwargs): + try: + return super(AnymailTestMixin, self).assertRaisesRegex(*args, **kwargs) + except TypeError: + return self.assertRaisesRegexp(*args, **kwargs) # Python 2 + + def assertRegex(self, *args, **kwargs): + try: + return super(AnymailTestMixin, self).assertRegex(*args, **kwargs) + except TypeError: + return self.assertRegexpMatches(*args, **kwargs) # Python 2 # Backported from python 3.5