Skip to content

Commit 1372ef2

Browse files
committed
SendGrid: merge 'filters' in esp_extra
Previously, setting esp_extra['x-smtpapi']['filters'] would override the entire filters setting, potentially undoing other Anymail options that use SendGrid filters (like track_opens). Now, 'filters' is special-cased, and merged with any other Anymail filter options. (We don't do a fully deep merge, because otherwise there would be no way to use esp_extra to *clear* Anymail settings.)
1 parent a26d284 commit 1372ef2

File tree

4 files changed

+53
-19
lines changed

4 files changed

+53
-19
lines changed

anymail/backends/sendgrid.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,13 @@ def serialize_data(self):
9191
# If esp_extra was also used to set x-smtpapi, need to merge it
9292
if "x-smtpapi" in self.data:
9393
esp_extra_smtpapi = self.data["x-smtpapi"]
94-
self.smtpapi.update(esp_extra_smtpapi) # need to make this deep merge (for filters)!
94+
for key, value in esp_extra_smtpapi.items():
95+
if key == "filters":
96+
# merge filters (else it's difficult to mix esp_extra with other features)
97+
self.smtpapi.setdefault(key, {}).update(value)
98+
else:
99+
# all other keys replace any current value
100+
self.smtpapi[key] = value
95101
self.data["x-smtpapi"] = self.serialize_json(self.smtpapi)
96102
elif "x-smtpapi" in self.data:
97103
self.data["x-smtpapi"] = self.serialize_json(self.data["x-smtpapi"])

docs/esps/sendgrid.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,16 @@ Example:
117117
message.esp_extra = {
118118
'x-smtpapi': {
119119
"asm_group": 1, # Assign SendGrid unsubscribe group for this message
120-
"asm_groups_to_display": [1, 2, 3]
120+
"asm_groups_to_display": [1, 2, 3],
121+
"filters": {
122+
"subscriptiontrack": { # Insert SendGrid subscription management links
123+
"settings": {
124+
"text/html": "If you would like to unsubscribe <% click here %>.",
125+
"text/plain": "If you would like to unsubscribe click here: <% %>.",
126+
"enable": 1
127+
}
128+
}
129+
}
121130
}
122131
}
123132

tests/test_sendgrid_backend.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,18 +423,35 @@ def test_default_omits_options(self):
423423

424424
def test_esp_extra(self):
425425
self.message.tags = ["tag"]
426+
self.message.track_clicks = True
426427
self.message.esp_extra = {
427-
'x-smtpapi': {'asm_group_id': 1},
428+
'x-smtpapi': {
429+
# Most SendMail options go in the 'x-smtpapi' block...
430+
'asm_group_id': 1,
431+
'filters': {
432+
# If you add a filter, you must supply all required settings for it.
433+
'subscriptiontrack': {
434+
'settings': {
435+
'enable': 1,
436+
'replace': '[unsubscribe_url]',
437+
},
438+
},
439+
},
440+
},
428441
'newthing': "some param not supported by Anymail",
429442
}
430443
self.message.send()
431444
# Additional send params:
432445
data = self.get_api_call_data()
433446
self.assertEqual(data['newthing'], "some param not supported by Anymail")
434-
# Should merge x-smtpapi
447+
# Should merge x-smtpapi, and merge filters within x-smtpapi
435448
smtpapi = self.get_smtpapi()
436449
self.assertEqual(smtpapi['category'], ["tag"])
437450
self.assertEqual(smtpapi['asm_group_id'], 1)
451+
self.assertEqual(smtpapi['filters']['subscriptiontrack'],
452+
{'settings': {'enable': 1, 'replace': '[unsubscribe_url]'}}) # esp_extra merged
453+
self.assertEqual(smtpapi['filters']['clicktrack'],
454+
{'settings': {'enable': 1}}) # Anymail message option preserved
438455

439456
# noinspection PyUnresolvedReferences
440457
def test_send_attaches_anymail_status(self):

tests/utils.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
# Anymail test utils
22
import sys
3-
import unittest
43

54
import os
65
import re
7-
import six
86
import warnings
97
from base64 import b64decode
108
from contextlib import contextmanager
@@ -60,19 +58,23 @@ def assertDoesNotWarn(self):
6058
finally:
6159
warnings.resetwarnings()
6260

63-
# Plus these methods added below:
64-
# assertCountEqual
65-
# assertRaisesRegex
66-
# assertRegex
67-
68-
# Add the Python 3 TestCase assertions, if they're not already there.
69-
# (The six implementations cause infinite recursion if installed on
70-
# a py3 TestCase.)
71-
for method in ('assertCountEqual', 'assertRaisesRegex', 'assertRegex'):
72-
try:
73-
getattr(unittest.TestCase, method)
74-
except AttributeError:
75-
setattr(AnymailTestMixin, method, getattr(six, method))
61+
def assertCountEqual(self, *args, **kwargs):
62+
try:
63+
return super(AnymailTestMixin, self).assertCountEqual(*args, **kwargs)
64+
except TypeError:
65+
return self.assertItemsEqual(*args, **kwargs) # Python 2
66+
67+
def assertRaisesRegex(self, *args, **kwargs):
68+
try:
69+
return super(AnymailTestMixin, self).assertRaisesRegex(*args, **kwargs)
70+
except TypeError:
71+
return self.assertRaisesRegexp(*args, **kwargs) # Python 2
72+
73+
def assertRegex(self, *args, **kwargs):
74+
try:
75+
return super(AnymailTestMixin, self).assertRegex(*args, **kwargs)
76+
except TypeError:
77+
return self.assertRegexpMatches(*args, **kwargs) # Python 2
7678

7779

7880
# Backported from python 3.5

0 commit comments

Comments
 (0)