mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 11:51:05 -05:00
Handle Django lazy strings.
In BasePayload, ensure any Django ugettext_lazy (or similar) are converted to real strings before handing off to ESP code. This resolves problems where calling code expects it can use lazy strings "anywhere", but non-Django code (requests, ESP packages) don't always handle them correctly. * Add utils helpers for lazy objects (is_lazy, force_non_lazy*) * Add lazy object handling to utils.Attachment * Add lazy object handling converters to BasePayload attr processing where appropriate. (This ends up varying by the expected attribute type.) Fixes #34.
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
from datetime import datetime
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
import six
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.mail import get_connection, send_mail
|
||||
from django.test import SimpleTestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.functional import Promise
|
||||
from django.utils.timezone import utc
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
from anymail.exceptions import AnymailConfigurationError, AnymailUnsupportedFeature
|
||||
from anymail.message import AnymailMessage
|
||||
@@ -212,3 +216,80 @@ class SendDefaultsTests(TestBackendTestCase):
|
||||
self.assertEqual(params['template_id'], 'global-template') # global-defaults only
|
||||
self.assertEqual(params['espextra'], 'espsetting')
|
||||
self.assertNotIn('globalextra', params) # entire esp_extra is overriden by esp-send-defaults
|
||||
|
||||
|
||||
class LazyStringsTest(TestBackendTestCase):
|
||||
"""
|
||||
Tests ugettext_lazy strings forced real before passing to ESP transport.
|
||||
|
||||
Docs notwithstanding, Django lazy strings *don't* work anywhere regular
|
||||
strings would. In particular, they aren't instances of unicode/str.
|
||||
There are some cases (e.g., urllib.urlencode, requests' _encode_params)
|
||||
where this can cause encoding errors or just very wrong results.
|
||||
|
||||
Since Anymail sits on the border between Django app code and non-Django
|
||||
ESP code (e.g., requests), it's responsible for converting lazy text
|
||||
to actual strings.
|
||||
"""
|
||||
|
||||
def assertNotLazy(self, s, msg=None):
|
||||
self.assertNotIsInstance(s, Promise,
|
||||
msg=msg or "String %r is lazy" % six.text_type(s))
|
||||
|
||||
def test_lazy_from(self):
|
||||
# This sometimes ends up lazy when settings.DEFAULT_FROM_EMAIL is meant to be localized
|
||||
self.message.from_email = ugettext_lazy(u'"Global Sales" <sales@example.com>')
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertNotLazy(params['from'].address)
|
||||
|
||||
def test_lazy_subject(self):
|
||||
self.message.subject = ugettext_lazy("subject")
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertNotLazy(params['subject'])
|
||||
|
||||
def test_lazy_body(self):
|
||||
self.message.body = ugettext_lazy("text body")
|
||||
self.message.attach_alternative(ugettext_lazy("html body"), "text/html")
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertNotLazy(params['text_body'])
|
||||
self.assertNotLazy(params['html_body'])
|
||||
|
||||
def test_lazy_headers(self):
|
||||
self.message.extra_headers['X-Test'] = ugettext_lazy("Test Header")
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertNotLazy(params['extra_headers']['X-Test'])
|
||||
|
||||
def test_lazy_attachments(self):
|
||||
self.message.attach(ugettext_lazy("test.csv"), ugettext_lazy("test,csv,data"), "text/csv")
|
||||
self.message.attach(MIMEText(ugettext_lazy("contact info")))
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertNotLazy(params['attachments'][0].name)
|
||||
self.assertNotLazy(params['attachments'][0].content)
|
||||
self.assertNotLazy(params['attachments'][1].content)
|
||||
|
||||
def test_lazy_tags(self):
|
||||
self.message.tags = [ugettext_lazy("Shipping"), ugettext_lazy("Sales")]
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertNotLazy(params['tags'][0])
|
||||
self.assertNotLazy(params['tags'][1])
|
||||
|
||||
def test_lazy_metadata(self):
|
||||
self.message.metadata = {'order_type': ugettext_lazy("Subscription")}
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertNotLazy(params['metadata']['order_type'])
|
||||
|
||||
def test_lazy_merge_data(self):
|
||||
self.message.merge_data = {
|
||||
'to@example.com': {'duration': ugettext_lazy("One Month")}}
|
||||
self.message.merge_global_data = {'order_type': ugettext_lazy("Subscription")}
|
||||
self.message.send()
|
||||
params = self.get_send_params()
|
||||
self.assertNotLazy(params['merge_data']['to@example.com']['duration'])
|
||||
self.assertNotLazy(params['merge_global_data']['order_type'])
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Tests for the anymail/utils.py module
|
||||
# (not to be confused with utilities for testing found in in tests/utils.py)
|
||||
|
||||
import six
|
||||
from django.test import SimpleTestCase
|
||||
from django.utils.translation import ugettext_lazy, string_concat
|
||||
|
||||
from anymail.exceptions import AnymailInvalidAddress
|
||||
from anymail.utils import ParsedEmail
|
||||
from anymail.utils import ParsedEmail, is_lazy, force_non_lazy, force_non_lazy_dict, force_non_lazy_list
|
||||
|
||||
|
||||
class ParsedEmailTests(SimpleTestCase):
|
||||
@@ -61,3 +62,57 @@ class ParsedEmailTests(SimpleTestCase):
|
||||
def test_whitespace_only_address(self):
|
||||
with self.assertRaises(AnymailInvalidAddress):
|
||||
ParsedEmail(' ', self.ADDRESS_ENCODING)
|
||||
|
||||
|
||||
class LazyCoercionTests(SimpleTestCase):
|
||||
"""Test utils.is_lazy and force_non_lazy*"""
|
||||
|
||||
def test_is_lazy(self):
|
||||
self.assertTrue(is_lazy(ugettext_lazy("lazy string is lazy")))
|
||||
self.assertTrue(is_lazy(string_concat(ugettext_lazy("concatenation"),
|
||||
ugettext_lazy("is lazy"))))
|
||||
|
||||
def test_not_lazy(self):
|
||||
self.assertFalse(is_lazy(u"text not lazy"))
|
||||
self.assertFalse(is_lazy(b"bytes not lazy"))
|
||||
self.assertFalse(is_lazy(None))
|
||||
self.assertFalse(is_lazy({'dict': "not lazy"}))
|
||||
self.assertFalse(is_lazy(["list", "not lazy"]))
|
||||
self.assertFalse(is_lazy(object()))
|
||||
self.assertFalse(is_lazy([ugettext_lazy("doesn't recurse")]))
|
||||
|
||||
def test_force_lazy(self):
|
||||
result = force_non_lazy(ugettext_lazy(u"text"))
|
||||
self.assertIsInstance(result, six.text_type)
|
||||
self.assertEqual(result, u"text")
|
||||
|
||||
def test_force_concat(self):
|
||||
result = force_non_lazy(string_concat(ugettext_lazy(u"text"), ugettext_lazy("concat")))
|
||||
self.assertIsInstance(result, six.text_type)
|
||||
self.assertEqual(result, u"textconcat")
|
||||
|
||||
def test_force_string(self):
|
||||
result = force_non_lazy(u"text")
|
||||
self.assertIsInstance(result, six.text_type)
|
||||
self.assertEqual(result, u"text")
|
||||
|
||||
def test_force_bytes(self):
|
||||
result = force_non_lazy(b"bytes \xFE")
|
||||
self.assertIsInstance(result, six.binary_type)
|
||||
self.assertEqual(result, b"bytes \xFE")
|
||||
|
||||
def test_force_none(self):
|
||||
result = force_non_lazy(None)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_force_dict(self):
|
||||
result = force_non_lazy_dict({'a': 1, 'b': ugettext_lazy(u"b"),
|
||||
'c': {'c1': ugettext_lazy(u"c1")}})
|
||||
self.assertEqual(result, {'a': 1, 'b': u"b", 'c': {'c1': u"c1"}})
|
||||
self.assertIsInstance(result['b'], six.text_type)
|
||||
self.assertIsInstance(result['c']['c1'], six.text_type)
|
||||
|
||||
def test_force_list(self):
|
||||
result = force_non_lazy_list([0, ugettext_lazy(u"b"), u"c"])
|
||||
self.assertEqual(result, [0, u"b", u"c"]) # coerced to list
|
||||
self.assertIsInstance(result[1], six.text_type)
|
||||
|
||||
Reference in New Issue
Block a user