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.)
This commit is contained in:
medmunds
2016-04-30 10:21:11 -07:00
parent a26d284772
commit 1372ef21eb
4 changed files with 52 additions and 18 deletions

View File

@@ -91,7 +91,13 @@ class SendGridPayload(RequestsPayload):
# If esp_extra was also used to set x-smtpapi, need to merge it # If esp_extra was also used to set x-smtpapi, need to merge it
if "x-smtpapi" in self.data: if "x-smtpapi" in self.data:
esp_extra_smtpapi = self.data["x-smtpapi"] 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) self.data["x-smtpapi"] = self.serialize_json(self.smtpapi)
elif "x-smtpapi" in self.data: elif "x-smtpapi" in self.data:
self.data["x-smtpapi"] = self.serialize_json(self.data["x-smtpapi"]) self.data["x-smtpapi"] = self.serialize_json(self.data["x-smtpapi"])

View File

@@ -117,7 +117,16 @@ Example:
message.esp_extra = { message.esp_extra = {
'x-smtpapi': { 'x-smtpapi': {
"asm_group": 1, # Assign SendGrid unsubscribe group for this message "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
}
}
}
} }
} }

View File

@@ -423,18 +423,35 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
def test_esp_extra(self): def test_esp_extra(self):
self.message.tags = ["tag"] self.message.tags = ["tag"]
self.message.track_clicks = True
self.message.esp_extra = { 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", 'newthing': "some param not supported by Anymail",
} }
self.message.send() self.message.send()
# Additional send params: # Additional send params:
data = self.get_api_call_data() data = self.get_api_call_data()
self.assertEqual(data['newthing'], "some param not supported by Anymail") 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() smtpapi = self.get_smtpapi()
self.assertEqual(smtpapi['category'], ["tag"]) self.assertEqual(smtpapi['category'], ["tag"])
self.assertEqual(smtpapi['asm_group_id'], 1) 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 # noinspection PyUnresolvedReferences
def test_send_attaches_anymail_status(self): def test_send_attaches_anymail_status(self):

View File

@@ -1,10 +1,8 @@
# Anymail test utils # Anymail test utils
import sys import sys
import unittest
import os import os
import re import re
import six
import warnings import warnings
from base64 import b64decode from base64 import b64decode
from contextlib import contextmanager from contextlib import contextmanager
@@ -60,19 +58,23 @@ class AnymailTestMixin:
finally: finally:
warnings.resetwarnings() warnings.resetwarnings()
# Plus these methods added below: def assertCountEqual(self, *args, **kwargs):
# assertCountEqual try:
# assertRaisesRegex return super(AnymailTestMixin, self).assertCountEqual(*args, **kwargs)
# assertRegex except TypeError:
return self.assertItemsEqual(*args, **kwargs) # Python 2
# Add the Python 3 TestCase assertions, if they're not already there. def assertRaisesRegex(self, *args, **kwargs):
# (The six implementations cause infinite recursion if installed on try:
# a py3 TestCase.) return super(AnymailTestMixin, self).assertRaisesRegex(*args, **kwargs)
for method in ('assertCountEqual', 'assertRaisesRegex', 'assertRegex'): except TypeError:
try: return self.assertRaisesRegexp(*args, **kwargs) # Python 2
getattr(unittest.TestCase, method)
except AttributeError: def assertRegex(self, *args, **kwargs):
setattr(AnymailTestMixin, method, getattr(six, method)) try:
return super(AnymailTestMixin, self).assertRegex(*args, **kwargs)
except TypeError:
return self.assertRegexpMatches(*args, **kwargs) # Python 2
# Backported from python 3.5 # Backported from python 3.5