Skip to content

Commit 0aa08ed

Browse files
committed
Merge pull request #83 from SparkPost/ISSUE-75
Support for cc/bcc in Django backend
2 parents 4cc4814 + e04ca3c commit 0aa08ed

File tree

7 files changed

+176
-110
lines changed

7 files changed

+176
-110
lines changed

README.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ The SparkPost python library comes with an email backend for Django. Put the fol
9595
SPARKPOST_API_KEY = 'API_KEY'
9696
EMAIL_BACKEND = 'sparkpost.django.email_backend.SparkPostEmailBackend'
9797
98-
Replace *API_KEY* with an actual API key that you've generated in `Get a Key`_ section.
98+
Replace *API_KEY* with an actual API key that you've generated in `Get a Key`_ section. Check out the `full documentation`_ on the Django email backend.
99+
100+
.. _full documentation: http://python-sparkpost.readthedocs.org/en/latest/django/backend.html
99101

100102
Documentation
101103
-------------

docs/django/backend.rst

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,39 @@ Django is now configured to use the SparkPost email backend. You can now send ma
3737
from django.core.mail import send_mail
3838
3939
send_mail(
40-
subject='hello from sparkpost',
41-
message='Hello Rock stars!'
40+
subject='Hello from SparkPost',
41+
message='Woo hoo! Sent from Django!'
4242
from_email='[email protected]',
43-
recipient_list=['to@friendsdomain.com'],
43+
recipient_list=['to@example.com'],
4444
html_message='<p>Hello Rock stars!</p>',
4545
)
4646
47+
If you need to add cc, bcc, reply to, or attachments, use the `EmailMultiAlternatives` class directly:
4748

48-
Supported version
49-
-----------------
50-
SparkPost will support all versions of Django that are within extended support period. Refer to `Django Supported_Version`_.
49+
.. code-block:: python
50+
51+
from django.core.mail import EmailMultiAlternatives
52+
53+
email = EmailMultiAlternatives(
54+
subject='hello from sparkpost',
55+
body='Woo hoo! Sent from Django!',
56+
from_email='[email protected]',
57+
58+
59+
60+
reply_to=['[email protected]']
61+
)
5162
52-
Current supported versions are:
53-
* 1.7
54-
* 1.8
55-
* 1.9b1
63+
email.attach_alternative('<p>Woo hoo! Sent from Django!</p>', 'text/html')
64+
email.attach('image.png', img_data, 'image/png')
65+
email.send()
5666
5767
58-
.. _Django Supported_Version: https://www.djangoproject.com/download/#supported-versions
68+
Supported version
69+
-----------------
70+
SparkPost will support all versions of Django that are within extended support period. Refer to `Django Supported Versions`_.
71+
72+
.. _Django Supported Versions: https://www.djangoproject.com/download/#supported-versions
5973

6074

6175
Additional documentation
@@ -64,4 +78,3 @@ Additional documentation
6478
See our `Using SparkPost with Django`_ in support article.
6579

6680
.. _Using SparkPost with Django: https://support.sparkpost.com/customer/en/portal/articles/2169630-using-sparkpost-with-django?b_id=7411
67-

sparkpost/django/email_backend.py

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
from sparkpost import SparkPost
55

6-
from .exceptions import UnsupportedContent
7-
from .exceptions import UnsupportedParam
6+
from .message import SparkPostMessage
87

98

109
class SparkPostEmailBackend(BaseEmailBackend):
@@ -27,51 +26,14 @@ def send_messages(self, email_messages):
2726
success = 0
2827
for message in email_messages:
2928
try:
30-
response = self._send(message)
29+
response = self._send(SparkPostMessage(message))
3130
success += response['total_accepted_recipients']
3231
except Exception:
3332
if not self.fail_silently:
3433
raise
3534
return success
3635

3736
def _send(self, message):
38-
self.check_unsupported(message)
39-
self.check_attachments(message)
40-
4137
params = getattr(settings, 'SPARKPOST_OPTIONS', {})
42-
params.update(dict(
43-
recipients=message.to,
44-
text=message.body,
45-
from_email=message.from_email,
46-
subject=message.subject
47-
))
48-
49-
if hasattr(message, 'alternatives') and len(message.alternatives) > 0:
50-
for alternative in message.alternatives:
51-
52-
if alternative[1] == 'text/html':
53-
params['html'] = alternative[0]
54-
else:
55-
raise UnsupportedContent(
56-
'Content type %s is not supported' % alternative[1]
57-
)
58-
38+
params.update(message)
5939
return self.client.transmissions.send(**params)
60-
61-
@staticmethod
62-
def check_attachments(message):
63-
if len(message.attachments):
64-
raise UnsupportedContent(
65-
'The SparkPost Django email backend does not '
66-
'currently support attachment.'
67-
)
68-
69-
@staticmethod
70-
def check_unsupported(message):
71-
unsupported_params = ['cc', 'bcc', 'reply_to']
72-
for param in unsupported_params:
73-
if len(getattr(message, param, [])):
74-
raise UnsupportedParam(
75-
'The SparkPost Django email backend does not currently '
76-
'support %s.' % param
77-
)

sparkpost/django/message.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from django.core.mail import EmailMultiAlternatives
2+
3+
from .exceptions import UnsupportedContent
4+
5+
6+
class SparkPostMessage(dict):
7+
"""
8+
Takes a Django EmailMessage and formats it for use with the SparkPost API.
9+
10+
The dictionary returned would be formatted like this:
11+
12+
{
13+
'recipients': ['[email protected]'],
14+
'from_email': '[email protected]',
15+
'text': 'Hello world',
16+
'html': '<p>Hello world</p>',
17+
'subject': 'Hello from the SparkPost Django email backend'
18+
}
19+
"""
20+
21+
def __init__(self, message):
22+
formatted = {
23+
'recipients': message.to,
24+
'from_email': message.from_email,
25+
'subject': message.subject,
26+
'text': message.body
27+
}
28+
29+
if message.cc:
30+
formatted['cc'] = message.cc
31+
32+
if message.bcc:
33+
formatted['bcc'] = message.bcc
34+
35+
if hasattr(message, 'reply_to') and message.reply_to:
36+
formatted['reply_to'] = ','.join(message.reply_to)
37+
38+
if isinstance(message, EmailMultiAlternatives):
39+
for alternative in message.alternatives:
40+
if alternative[1] == 'text/html':
41+
formatted['html'] = alternative[0]
42+
else:
43+
raise UnsupportedContent(
44+
'Content type %s is not supported' % alternative[1]
45+
)
46+
47+
if message.attachments:
48+
raise UnsupportedContent(
49+
'The SparkPost Django email backend does not '
50+
'currently support attachment.'
51+
)
52+
53+
return super(SparkPostMessage, self).__init__(formatted)

test/django/test_email_backend.py

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
import pytest
22
import mock
3-
from distutils.version import StrictVersion
43

5-
from django import get_version
64
from django.conf import settings
75
from django.core.mail import send_mail
86
from django.core.mail import send_mass_mail
9-
from django.core.mail import EmailMessage
107
from django.core.mail import EmailMultiAlternatives
118
from django.utils.functional import empty
129

1310
from sparkpost.django.email_backend import SparkPostEmailBackend
14-
from sparkpost.django.exceptions import UnsupportedParam
1511
from sparkpost.django.exceptions import UnsupportedContent
1612
from sparkpost.transmissions import Transmissions
1713

18-
try:
19-
from StringIO import StringIO
20-
except ImportError:
21-
from io import StringIO
22-
2314
API_KEY = 'API_Key'
2415

2516

@@ -37,10 +28,6 @@ def reconfigure_settings(**new_settings):
3728
)
3829

3930

40-
def at_least_version(version):
41-
return StrictVersion(get_version()) > StrictVersion(version)
42-
43-
4431
def get_params(overrides=None):
4532
if overrides is None:
4633
overrides = {}
@@ -174,49 +161,6 @@ def test_unsupported_content_types():
174161
mail.send()
175162

176163

177-
def test_attachment():
178-
params = get_params()
179-
params['body'] = params.pop('message')
180-
params['to'] = params.pop('recipient_list')
181-
182-
attachment = StringIO()
183-
attachment.write('hello file')
184-
email = EmailMessage(**params)
185-
email.attach('file.txt', attachment, 'text/plain')
186-
187-
with pytest.raises(UnsupportedContent):
188-
email.send()
189-
190-
191-
def test_cc_bcc_reply_to():
192-
params = get_params({
193-
194-
})
195-
params['body'] = params.pop('message')
196-
params['to'] = params.pop('recipient_list')
197-
198-
# test cc exception
199-
with pytest.raises(UnsupportedParam):
200-
email = EmailMessage(**params)
201-
email.send()
202-
params.pop('cc')
203-
204-
# test bcc exception
205-
params['bcc'] = ['[email protected]', '[email protected]']
206-
with pytest.raises(UnsupportedParam):
207-
email = EmailMessage(**params)
208-
email.send()
209-
params.pop('bcc')
210-
211-
if at_least_version('1.8'): # reply_to is supported from django 1.8
212-
# test reply_to exception
213-
params['reply_to'] = ['[email protected]']
214-
with pytest.raises(UnsupportedParam):
215-
email = EmailMessage(**params)
216-
email.send()
217-
params.pop('reply_to')
218-
219-
220164
def test_settings_options():
221165
SPARKPOST_OPTIONS = {
222166
'track_opens': False,

test/django/test_message.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
try:
2+
from StringIO import StringIO
3+
except ImportError:
4+
from io import StringIO
5+
6+
import pytest
7+
from django.core.mail import EmailMultiAlternatives
8+
from django.core.mail.message import EmailMessage
9+
10+
from sparkpost.django.exceptions import UnsupportedContent
11+
from sparkpost.django.message import SparkPostMessage
12+
from .utils import at_least_version
13+
14+
15+
base_options = dict(
16+
subject='Test',
17+
body='Testing',
18+
from_email='[email protected]',
19+
20+
)
21+
22+
23+
def message(**options):
24+
options.update(base_options)
25+
email_message = EmailMessage(**options)
26+
return SparkPostMessage(email_message)
27+
28+
29+
def multipart_message(**options):
30+
options.update(base_options)
31+
email_message = EmailMultiAlternatives(**options)
32+
email_message.attach_alternative('<p>Testing</p>', 'text/html')
33+
return SparkPostMessage(email_message)
34+
35+
36+
base_expected = dict(
37+
recipients=['[email protected]'],
38+
from_email='[email protected]',
39+
subject='Test',
40+
text='Testing'
41+
)
42+
43+
44+
def test_minimal():
45+
assert message() == base_expected
46+
47+
48+
def test_multipart():
49+
expected = dict(
50+
html='<p>Testing</p>'
51+
)
52+
expected.update(base_expected)
53+
assert multipart_message() == expected
54+
55+
56+
def test_cc_bcc():
57+
expected = dict(
58+
59+
60+
)
61+
expected.update(base_expected)
62+
63+
options = dict(cc=['[email protected]'],
64+
65+
assert message(**options) == expected
66+
67+
68+
def test_attachment():
69+
attachment = StringIO()
70+
attachment.write('hello file')
71+
email_message = EmailMessage(**base_options)
72+
email_message.attach('file.txt', attachment, 'text/plain')
73+
74+
with pytest.raises(UnsupportedContent):
75+
SparkPostMessage(email_message)
76+
77+
if at_least_version('1.8'):
78+
def test_reply_to():
79+
expected = dict(
80+
81+
)
82+
expected.update(base_expected)
83+
84+
assert message(reply_to=['[email protected]',
85+
'[email protected]']) == expected

test/django/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from distutils.version import StrictVersion
2+
3+
from django import get_version
4+
5+
6+
def at_least_version(version):
7+
return StrictVersion(get_version()) > StrictVersion(version)

0 commit comments

Comments
 (0)