Skip to content

Commit

Permalink
Merge pull request #83 from SparkPost/ISSUE-75
Browse files Browse the repository at this point in the history
Support for cc/bcc in Django backend
  • Loading branch information
ewandennis committed Mar 10, 2016
2 parents 4cc4814 + e04ca3c commit 0aa08ed
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 110 deletions.
4 changes: 3 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ The SparkPost python library comes with an email backend for Django. Put the fol
SPARKPOST_API_KEY = 'API_KEY'
EMAIL_BACKEND = 'sparkpost.django.email_backend.SparkPostEmailBackend'
Replace *API_KEY* with an actual API key that you've generated in `Get a Key`_ section.
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.

.. _full documentation: http://python-sparkpost.readthedocs.org/en/latest/django/backend.html

Documentation
-------------
Expand Down
37 changes: 25 additions & 12 deletions docs/django/backend.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,39 @@ Django is now configured to use the SparkPost email backend. You can now send ma
from django.core.mail import send_mail
send_mail(
subject='hello from sparkpost',
message='Hello Rock stars!'
subject='Hello from SparkPost',
message='Woo hoo! Sent from Django!'
from_email='from@yourdomain.com',
recipient_list=['to@friendsdomain.com'],
recipient_list=['to@example.com'],
html_message='<p>Hello Rock stars!</p>',
)
If you need to add cc, bcc, reply to, or attachments, use the `EmailMultiAlternatives` class directly:

Supported version
-----------------
SparkPost will support all versions of Django that are within extended support period. Refer to `Django Supported_Version`_.
.. code-block:: python
from django.core.mail import EmailMultiAlternatives
email = EmailMultiAlternatives(
subject='hello from sparkpost',
body='Woo hoo! Sent from Django!',
from_email='from@yourdomain.com',
to=['to@example.com'],
cc=['ccone@example.com'],
bcc=['bccone@example.com'],
reply_to=['replyone@example.com']
)
Current supported versions are:
* 1.7
* 1.8
* 1.9b1
email.attach_alternative('<p>Woo hoo! Sent from Django!</p>', 'text/html')
email.attach('image.png', img_data, 'image/png')
email.send()
.. _Django Supported_Version: https://www.djangoproject.com/download/#supported-versions
Supported version
-----------------
SparkPost will support all versions of Django that are within extended support period. Refer to `Django Supported Versions`_.

.. _Django Supported Versions: https://www.djangoproject.com/download/#supported-versions


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

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

44 changes: 3 additions & 41 deletions sparkpost/django/email_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

from sparkpost import SparkPost

from .exceptions import UnsupportedContent
from .exceptions import UnsupportedParam
from .message import SparkPostMessage


class SparkPostEmailBackend(BaseEmailBackend):
Expand All @@ -27,51 +26,14 @@ def send_messages(self, email_messages):
success = 0
for message in email_messages:
try:
response = self._send(message)
response = self._send(SparkPostMessage(message))
success += response['total_accepted_recipients']
except Exception:
if not self.fail_silently:
raise
return success

def _send(self, message):
self.check_unsupported(message)
self.check_attachments(message)

params = getattr(settings, 'SPARKPOST_OPTIONS', {})
params.update(dict(
recipients=message.to,
text=message.body,
from_email=message.from_email,
subject=message.subject
))

if hasattr(message, 'alternatives') and len(message.alternatives) > 0:
for alternative in message.alternatives:

if alternative[1] == 'text/html':
params['html'] = alternative[0]
else:
raise UnsupportedContent(
'Content type %s is not supported' % alternative[1]
)

params.update(message)
return self.client.transmissions.send(**params)

@staticmethod
def check_attachments(message):
if len(message.attachments):
raise UnsupportedContent(
'The SparkPost Django email backend does not '
'currently support attachment.'
)

@staticmethod
def check_unsupported(message):
unsupported_params = ['cc', 'bcc', 'reply_to']
for param in unsupported_params:
if len(getattr(message, param, [])):
raise UnsupportedParam(
'The SparkPost Django email backend does not currently '
'support %s.' % param
)
53 changes: 53 additions & 0 deletions sparkpost/django/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from django.core.mail import EmailMultiAlternatives

from .exceptions import UnsupportedContent


class SparkPostMessage(dict):
"""
Takes a Django EmailMessage and formats it for use with the SparkPost API.
The dictionary returned would be formatted like this:
{
'recipients': ['recipient@example.com'],
'from_email': 'from@example.com',
'text': 'Hello world',
'html': '<p>Hello world</p>',
'subject': 'Hello from the SparkPost Django email backend'
}
"""

def __init__(self, message):
formatted = {
'recipients': message.to,
'from_email': message.from_email,
'subject': message.subject,
'text': message.body
}

if message.cc:
formatted['cc'] = message.cc

if message.bcc:
formatted['bcc'] = message.bcc

if hasattr(message, 'reply_to') and message.reply_to:
formatted['reply_to'] = ','.join(message.reply_to)

if isinstance(message, EmailMultiAlternatives):
for alternative in message.alternatives:
if alternative[1] == 'text/html':
formatted['html'] = alternative[0]
else:
raise UnsupportedContent(
'Content type %s is not supported' % alternative[1]
)

if message.attachments:
raise UnsupportedContent(
'The SparkPost Django email backend does not '
'currently support attachment.'
)

return super(SparkPostMessage, self).__init__(formatted)
56 changes: 0 additions & 56 deletions test/django/test_email_backend.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import pytest
import mock
from distutils.version import StrictVersion

from django import get_version
from django.conf import settings
from django.core.mail import send_mail
from django.core.mail import send_mass_mail
from django.core.mail import EmailMessage
from django.core.mail import EmailMultiAlternatives
from django.utils.functional import empty

from sparkpost.django.email_backend import SparkPostEmailBackend
from sparkpost.django.exceptions import UnsupportedParam
from sparkpost.django.exceptions import UnsupportedContent
from sparkpost.transmissions import Transmissions

try:
from StringIO import StringIO
except ImportError:
from io import StringIO

API_KEY = 'API_Key'


Expand All @@ -37,10 +28,6 @@ def reconfigure_settings(**new_settings):
)


def at_least_version(version):
return StrictVersion(get_version()) > StrictVersion(version)


def get_params(overrides=None):
if overrides is None:
overrides = {}
Expand Down Expand Up @@ -174,49 +161,6 @@ def test_unsupported_content_types():
mail.send()


def test_attachment():
params = get_params()
params['body'] = params.pop('message')
params['to'] = params.pop('recipient_list')

attachment = StringIO()
attachment.write('hello file')
email = EmailMessage(**params)
email.attach('file.txt', attachment, 'text/plain')

with pytest.raises(UnsupportedContent):
email.send()


def test_cc_bcc_reply_to():
params = get_params({
'cc': ['cc1@example.com', 'cc2@example.com']
})
params['body'] = params.pop('message')
params['to'] = params.pop('recipient_list')

# test cc exception
with pytest.raises(UnsupportedParam):
email = EmailMessage(**params)
email.send()
params.pop('cc')

# test bcc exception
params['bcc'] = ['bcc1@example.com', 'bcc1@example.com']
with pytest.raises(UnsupportedParam):
email = EmailMessage(**params)
email.send()
params.pop('bcc')

if at_least_version('1.8'): # reply_to is supported from django 1.8
# test reply_to exception
params['reply_to'] = ['devnull@example.com']
with pytest.raises(UnsupportedParam):
email = EmailMessage(**params)
email.send()
params.pop('reply_to')


def test_settings_options():
SPARKPOST_OPTIONS = {
'track_opens': False,
Expand Down
85 changes: 85 additions & 0 deletions test/django/test_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
try:
from StringIO import StringIO
except ImportError:
from io import StringIO

import pytest
from django.core.mail import EmailMultiAlternatives
from django.core.mail.message import EmailMessage

from sparkpost.django.exceptions import UnsupportedContent
from sparkpost.django.message import SparkPostMessage
from .utils import at_least_version


base_options = dict(
subject='Test',
body='Testing',
from_email='test@from.com',
to=['recipient@example.com']
)


def message(**options):
options.update(base_options)
email_message = EmailMessage(**options)
return SparkPostMessage(email_message)


def multipart_message(**options):
options.update(base_options)
email_message = EmailMultiAlternatives(**options)
email_message.attach_alternative('<p>Testing</p>', 'text/html')
return SparkPostMessage(email_message)


base_expected = dict(
recipients=['recipient@example.com'],
from_email='test@from.com',
subject='Test',
text='Testing'
)


def test_minimal():
assert message() == base_expected


def test_multipart():
expected = dict(
html='<p>Testing</p>'
)
expected.update(base_expected)
assert multipart_message() == expected


def test_cc_bcc():
expected = dict(
cc=['ccone@example.com'],
bcc=['bccone@example.com']
)
expected.update(base_expected)

options = dict(cc=['ccone@example.com'],
bcc=['bccone@example.com'])
assert message(**options) == expected


def test_attachment():
attachment = StringIO()
attachment.write('hello file')
email_message = EmailMessage(**base_options)
email_message.attach('file.txt', attachment, 'text/plain')

with pytest.raises(UnsupportedContent):
SparkPostMessage(email_message)

if at_least_version('1.8'):
def test_reply_to():
expected = dict(
reply_to='replyone@example.com,replytwo@example.com'
)
expected.update(base_expected)

assert message(reply_to=['replyone@example.com',
'replytwo@example.com']) == expected
7 changes: 7 additions & 0 deletions test/django/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from distutils.version import StrictVersion

from django import get_version


def at_least_version(version):
return StrictVersion(get_version()) > StrictVersion(version)

0 comments on commit 0aa08ed

Please sign in to comment.