From 8de82a655c695d0c59617d8c13fc5aaeee3626b6 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Thu, 23 Feb 2012 01:07:37 -0800 Subject: [PATCH 01/24] Added test which verifies detection of the test environment --- sendgrid/tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sendgrid/tests.py b/sendgrid/tests.py index 302c32c..4ef0337 100644 --- a/sendgrid/tests.py +++ b/sendgrid/tests.py @@ -16,12 +16,22 @@ from message import SendGridEmailMessage from signals import sendgrid_email_sent from utils import filterutils +from utils import in_test_environment validate_filter_setting_value = filterutils.validate_filter_setting_value validate_filter_specification = filterutils.validate_filter_specification update_filters = filterutils.update_filters + +class SendGridInTestEnvTest(TestCase): + def test_in_test_environment(self): + """ + Tests that the test environment is detected. + """ + self.assertEqual(in_test_environment(), True) + + class SendWithSendGridEmailMessageTest(TestCase): def setUp(self): """ From cbbfb79bd5857410d144c84e546e10346ec30541 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Thu, 23 Feb 2012 01:08:14 -0800 Subject: [PATCH 02/24] Added SendGridUserMixin --- sendgrid/mixins.py | 37 ++++++++++++ sendgrid/utils/__init__.py | 120 ++++++++++++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 sendgrid/mixins.py diff --git a/sendgrid/mixins.py b/sendgrid/mixins.py new file mode 100644 index 0000000..6d2db36 --- /dev/null +++ b/sendgrid/mixins.py @@ -0,0 +1,37 @@ +from django.conf import settings + +from utils import add_unsubscribes +from utils import delete_unsubscribes +from utils import get_unsubscribes + + +SENDGRID_EMAIL_USERNAME = getattr(settings, "SENDGRID_EMAIL_USERNAME", None) +SENDGRID_EMAIL_PASSWORD = getattr(settings, "SENDGRID_EMAIL_PASSWORD", None) + + +class SendGridUserMixin: + """ + Adds SendGrid related convienence functions and properties to ``User`` objects. + """ + def is_unsubscribed(self): + """ + Returns True if the ``User``.``email`` belongs to the unsubscribe list. + """ + result = get_unsubscribes(email=self.email) + return result + + def add_to_unsubscribes(self): + """ + Adds the ``User``.``email`` from the unsubscribe list. + """ + result = add_unsubscribes(email=self.email) + print result + return result + + def delete_from_unsubscribes(self): + """ + Removes the ``User``.``email`` from the unsubscribe list. + """ + result = delete_unsubscribes(email=self.email) + print result + return result diff --git a/sendgrid/utils/__init__.py b/sendgrid/utils/__init__.py index ca5f213..e5f24da 100644 --- a/sendgrid/utils/__init__.py +++ b/sendgrid/utils/__init__.py @@ -1,7 +1,123 @@ +import datetime +import httplib +import logging +import time +import urllib +import urllib2 + +from django.conf import settings +from django.core import mail + + +SENDGRID_EMAIL_USERNAME = getattr(settings, "SENDGRID_EMAIL_USERNAME", None) +SENDGRID_EMAIL_PASSWORD = getattr(settings, "SENDGRID_EMAIL_PASSWORD", None) + +logger = logging.getLogger(__name__) + def in_test_environment(): """ Returns True if in a test environment, False otherwise. """ - from django.core import mail - return hasattr(mail, 'outbox') + +def remove_keys_without_value(d): + """ + Removes all key-value pairs with empty values. + """ + dCopy = d.copy() + + delKeys = [k for k, v in dCopy.iteritems() if not v] + for k in delKeys: + del dCopy[k] + + return dCopy + +def normalize_parameters(d): + """ + Normalizes the parameters, adds authorization details and removes empty entries. + """ + dCopy = d.copy() + + authorization = { + "api_user": SENDGRID_EMAIL_USERNAME, + "api_key": SENDGRID_EMAIL_PASSWORD, + } + dCopy.update(authorization) + dCopy = remove_keys_without_value(dCopy) + + return dCopy + +def get_unsubscribes(date=None, days=None, start_date=None, end_date=None, limit=None, offset=None, email=None): + """ + Returns a list of unsubscribes with addresses and optionally with dates. + """ + ENDPOINT = "https://sendgrid.com/api/unsubscribes.get.json" + + if days and (start_date or end_date): + raise AttributeError + + if days: + if start_date or end_date: + raise AttributeError + elif start_date and end_date: + if days: + raise AttributeError + + parameters = { + "date": date, + "days": days, + "start_date": start_date, + "end_date": end_date, + "limit": limit, + "offset": offset, + "email": email, + } + parameters = normalize_parameters(parameters) + + data = urllib.urlencode(parameters) + request = urllib2.Request(ENDPOINT, data) + response = urllib2.urlopen(request) + content = response.read() + + return content + +def add_unsubscribes(email): + """ + Add email addresses to the Unsubscribe list. + """ + ENDPOINT = "https://sendgrid.com/api/unsubscribes.add.json" + + parameters = { + "email": email, + } + parameters = normalize_parameters(parameters) + + data = urllib.urlencode(parameters) + request = urllib2.Request(ENDPOINT, data) + response = urllib2.urlopen(request) + content = response.read() + + return content + +def delete_unsubscribes(email, start_date=None, end_date=None): + """ + Delete an address from the Unsubscribe list. Please note that if no parameters are provided the ENTIRE list will be removed. + """ + ENDPOINT = "https://sendgrid.com/api/unsubscribes.delete.json" + + if not ((start_date and end_date) or email): + raise Exception("You're about to delete the entire list!") + + parameters = { + "start_date": start_date, + "end_date": end_date, + "email": email, + } + parameters = normalize_parameters(parameters) + + data = urllib.urlencode(parameters) + request = urllib2.Request(ENDPOINT, data) + response = urllib2.urlopen(request) + content = response.read() + + return content From 93f1ce171cc4fd6189099acb93a260c505a85ebf Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Fri, 2 Mar 2012 22:22:13 -0800 Subject: [PATCH 03/24] Added SendGridEmailMultiAlternatives --- sendgrid/message.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ sendgrid/tests.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/sendgrid/message.py b/sendgrid/message.py index 886eb07..91fdcd9 100644 --- a/sendgrid/message.py +++ b/sendgrid/message.py @@ -68,3 +68,48 @@ def send(self, *args, **kwargs): sendgrid_email_sent.send(sender=self, response=response) return response + + +class SendGridEmailMultiAlternatives(SendGridEmailMessage): + """ + A version of SendGridEmailMessage that makes it easy to send multipart/alternative + messages. For example, including text and HTML versions of the text is + made easier. + + # TODO: Find a better way to support this. + """ + alternative_subtype = 'alternative' + + def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, + connection=None, attachments=None, headers=None, alternatives=None, + cc=None): + """ + Initialize a single email message (which can be sent to multiple + recipients). + + All strings used to create the message can be unicode strings (or UTF-8 + bytestrings). The SafeMIMEText class will handle any necessary encoding + conversions. + """ + super(SendGridEmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers, cc) + self.alternatives = alternatives or [] + + def attach_alternative(self, content, mimetype): + """Attach an alternative content representation.""" + assert content is not None + assert mimetype is not None + self.alternatives.append((content, mimetype)) + + def _create_message(self, msg): + return self._create_attachments(self._create_alternatives(msg)) + + def _create_alternatives(self, msg): + encoding = self.encoding or settings.DEFAULT_CHARSET + if self.alternatives: + body_msg = msg + msg = SafeMIMEMultipart(_subtype=self.alternative_subtype, encoding=encoding) + if self.body: + msg.attach(body_msg) + for alternative in self.alternatives: + msg.attach(self._create_mime_attachment(*alternative)) + return msg diff --git a/sendgrid/tests.py b/sendgrid/tests.py index 4ef0337..3cd3054 100644 --- a/sendgrid/tests.py +++ b/sendgrid/tests.py @@ -14,6 +14,7 @@ from mail import get_sendgrid_connection from mail import send_sendgrid_mail from message import SendGridEmailMessage +from message import SendGridEmailMultiAlternatives from signals import sendgrid_email_sent from utils import filterutils from utils import in_test_environment @@ -91,6 +92,35 @@ def test_send_with_email_message(self): email.send() +class SendWithEmailMessageTest(TestCase): + def setUp(self): + self.signalsReceived = defaultdict(list) + + def test_send_multipart_email(self): + """docstring for send_multipart_email""" + subject, from_email, to = 'hello', 'from@example.com', 'to@example.com' + text_content = 'This is an important message.' + html_content = '

This is an important message.

' + msg = SendGridEmailMultiAlternatives(subject, text_content, from_email, [to]) + msg.attach_alternative(html_content, "text/html") + msg.send() + + def test_send_multipart_email_sends_signal(self): + @receiver(sendgrid_email_sent) + def receive_sendgrid_email_sent(*args, **kwargs): + """ + Receives sendgrid_email_sent signals. + """ + self.signalsReceived["sendgrid_email_sent"].append(1) + return True + + email = SendGridEmailMultiAlternatives() + email.send() + + numEmailSentSignalsRecieved = sum(self.signalsReceived["sendgrid_email_sent"]) + self.assertEqual(numEmailSentSignalsRecieved, 1) + + class FilterUtilsTests(TestCase): """docstring for FilterUtilsTests""" def setUp(self): From 34d8d28799541aff560656ecfd426bb0fccf27f1 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Fri, 2 Mar 2012 22:30:52 -0800 Subject: [PATCH 04/24] Added link to djangopackages.com page --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 63494be..e2df41e 100644 --- a/README.rst +++ b/README.rst @@ -45,3 +45,4 @@ Additional Information - https://docs.djangoproject.com/en/1.3/topics/email/ - http://ryanbalfanz.github.com/django-sendgrid/ - http://pypi.python.org/pypi/django-sendgrid + - http://djangopackages.com/packages/p/django-sendgrid/ From fb910ce4803d01b196f319d6f9ae2cd552f033a7 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Tue, 6 Mar 2012 00:17:20 -0800 Subject: [PATCH 05/24] Refactored into mixin and added unique_args functionality, updated tests Next time, be better about using a feature branches and such --- sendgrid/message.py | 155 ++++++++++++++++++++++++++------------------ sendgrid/tests.py | 66 ++++++++++++++----- 2 files changed, 140 insertions(+), 81 deletions(-) diff --git a/sendgrid/message.py b/sendgrid/message.py index 91fdcd9..75e7300 100644 --- a/sendgrid/message.py +++ b/sendgrid/message.py @@ -1,38 +1,32 @@ +from __future__ import absolute_import + +# import datetime import logging -import json +import time +import uuid +try: + import simplejson as json +except ImportError: + import json from django.conf import settings from django.core import mail from django.core.mail.message import EmailMessage +from django.core.mail.message import EmailMultiAlternatives # django-sendgrid imports -from header import SmtpApiHeader -from mail import get_sendgrid_connection -from signals import sendgrid_email_sent +from .header import SmtpApiHeader +from .mail import get_sendgrid_connection +from .signals import sendgrid_email_sent logger = logging.getLogger(__name__) -class SendGridEmailMessage(EmailMessage): +class SendGridEmailMessageMixin: """ - Adapts Django's ``EmailMessage`` for use with SendGrid. - - >>> from sendgrid.message import SendGridEmailMessage - >>> myEmail = "rbalfanz@gmail.com" - >>> mySendGridCategory = "django-sendgrid" - >>> e = SendGridEmailMessage("Subject", "Message", myEmail, [myEmail], headers={"Reply-To": myEmail}) - >>> e.sendgrid_headers.setCategory(mySendGridCategory) - >>> response = e.send() + Adds support for SendGrid features. """ - sendgrid_headers = SmtpApiHeader() - - def __init__(self, *args, **kwargs): - """ - Initialize the object. - """ - super(SendGridEmailMessage, self).__init__(*args, **kwargs) - def _update_headers_with_sendgrid_headers(self): """ Updates the existing headers to include SendGrid headers. @@ -43,17 +37,71 @@ def _update_headers_with_sendgrid_headers(self): "X-SMTPAPI": self.sendgrid_headers.asJSON() } self.extra_headers.update(additionalHeaders) - + logging.debug(str(self.extra_headers)) - + return self.extra_headers + def _update_unique_args(self, uniqueArgs): + """docstring for _update_unique_args""" + # assert self.unique_args is None, self.unique_args + self.sendgrid_headers.setUniqueArgs(uniqueArgs) + + return self.unique_args + def update_headers(self, *args, **kwargs): """ Updates the headers. """ return self._update_headers_with_sendgrid_headers(*args, **kwargs) + def get_category(self): + """docstring for get_category""" + return self.sendgrid_headers.data["category"] + category = property(get_category) + + def get_unique_args(self): + """docstring for get_unique_args""" + if "unique_args" in self.sendgrid_headers.data: + # raise Exception(self.sendgrid_headers.data["unique_args"]) + uniqueArgs = self.sendgrid_headers.data["unique_args"] + else: + uniqueArgs = None + return uniqueArgs + unique_args = property(get_unique_args) + + def prep_message_for_sending(self): + """docstring for prep_message_for_sending""" + # now = tz.localize(datetime.datetime.strptime(timestamp[:26], POSTMARK_DATETIME_STRING)).astimezone(pytz.utc) + uniqueArgs = { + "message_id": str(self._message_id), + # "submition_time": time.time(), + } + self._update_unique_args(uniqueArgs) + + self.update_headers() + + +class SendGridEmailMessage(EmailMessage, SendGridEmailMessageMixin): + """ + Adapts Django's ``EmailMessage`` for use with SendGrid. + + >>> from sendgrid.message import SendGridEmailMessage + >>> myEmail = "rbalfanz@gmail.com" + >>> mySendGridCategory = "django-sendgrid" + >>> e = SendGridEmailMessage("Subject", "Message", myEmail, [myEmail], headers={"Reply-To": myEmail}) + >>> e.sendgrid_headers.setCategory(mySendGridCategory) + >>> response = e.send() + """ + sendgrid_headers = SmtpApiHeader() + + def __init__(self, *args, **kwargs): + """ + Initialize the object. + """ + self._message_id = uuid.uuid4() + super(SendGridEmailMessage, self).__init__(*args, **kwargs) + def send(self, *args, **kwargs): """Sends the email message.""" # Set up the connection @@ -61,7 +109,7 @@ def send(self, *args, **kwargs): self.connection = connection logger.debug("Connection: {c}".format(c=connection)) - self.update_headers() + self.prep_message_for_sending() response = super(SendGridEmailMessage, self).send(*args, **kwargs) logger.debug("Tried to send an email with SendGrid and got response {r}".format(r=response)) @@ -70,46 +118,27 @@ def send(self, *args, **kwargs): return response -class SendGridEmailMultiAlternatives(SendGridEmailMessage): +class SendGridEmailMultiAlternatives(EmailMultiAlternatives, SendGridEmailMessageMixin): """ - A version of SendGridEmailMessage that makes it easy to send multipart/alternative - messages. For example, including text and HTML versions of the text is - made easier. - - # TODO: Find a better way to support this. + Adapts Django's ``EmailMultiAlternatives`` for use with SendGrid. """ - alternative_subtype = 'alternative' + sendgrid_headers = SmtpApiHeader() - def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, - connection=None, attachments=None, headers=None, alternatives=None, - cc=None): - """ - Initialize a single email message (which can be sent to multiple - recipients). + def __init__(self, *args, **kwargs): + self._message_id = uuid.uuid4() + super(SendGridEmailMultiAlternatives, self).__init__(*args, **kwargs) - All strings used to create the message can be unicode strings (or UTF-8 - bytestrings). The SafeMIMEText class will handle any necessary encoding - conversions. - """ - super(SendGridEmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers, cc) - self.alternatives = alternatives or [] - - def attach_alternative(self, content, mimetype): - """Attach an alternative content representation.""" - assert content is not None - assert mimetype is not None - self.alternatives.append((content, mimetype)) - - def _create_message(self, msg): - return self._create_attachments(self._create_alternatives(msg)) - - def _create_alternatives(self, msg): - encoding = self.encoding or settings.DEFAULT_CHARSET - if self.alternatives: - body_msg = msg - msg = SafeMIMEMultipart(_subtype=self.alternative_subtype, encoding=encoding) - if self.body: - msg.attach(body_msg) - for alternative in self.alternatives: - msg.attach(self._create_mime_attachment(*alternative)) - return msg + def send(self, *args, **kwargs): + """Sends the email message.""" + # Set up the connection + connection = get_sendgrid_connection() + self.connection = connection + logger.debug("Connection: {c}".format(c=connection)) + + self.prep_message_for_sending() + + response = super(SendGridEmailMultiAlternatives, self).send(*args, **kwargs) + logger.debug("Tried to send an email with SendGrid and got response {r}".format(r=response)) + sendgrid_email_sent.send(sender=self, response=response) + + return response diff --git a/sendgrid/tests.py b/sendgrid/tests.py index 3cd3054..f68f5c5 100644 --- a/sendgrid/tests.py +++ b/sendgrid/tests.py @@ -25,6 +25,36 @@ update_filters = filterutils.update_filters +class SendGridEmailTest(TestCase): + """docstring for SendGridEmailTest""" + def setUp(self): + """docstring for setUp""" + pass + + def test_email_has_unique_id(self): + """docstring for email_has_unique_id""" + email = SendGridEmailMessage() + self.assertTrue(email._message_id) + + def test_email_sends_unique_id(self): + """docstring for email_sends_unique_id""" + email = SendGridEmailMessage() + email.send() + self.assertTrue(email.sendgrid_headers.data["unique_args"]["message_id"]) + + def test_email_sent_signal_has_message(self): + """docstring for email_sent_signal_has_message""" + @receiver(sendgrid_email_sent) + def receive_sendgrid_email_sent(*args, **kwargs): + """ + Receives sendgrid_email_sent signals. + """ + self.assertTrue("response" in kwargs) + # self.assertTrue("message" in kwargs) + + email = SendGridEmailMessage() + response = email.send() + class SendGridInTestEnvTest(TestCase): def test_in_test_environment(self): """ @@ -92,7 +122,7 @@ def test_send_with_email_message(self): email.send() -class SendWithEmailMessageTest(TestCase): +class SendWithSendGridEmailMultiAlternativesTest(TestCase): def setUp(self): self.signalsReceived = defaultdict(list) @@ -141,33 +171,33 @@ def test_validate_filter_spec(self): "enable": 0, }, } - assert validate_filter_specification(filterSpec) == True + self.assertEqual(validate_filter_specification(filterSpec), True) def test_subscriptiontrack_enable_parameter(self): """ Tests the ``subscriptiontrack`` filter's ``enable`` paramter. """ - assert validate_filter_setting_value("subscriptiontrack", "enable", 0) == True - assert validate_filter_setting_value("subscriptiontrack", "enable", 1) == True - assert validate_filter_setting_value("subscriptiontrack", "enable", 0.0) == True - assert validate_filter_setting_value("subscriptiontrack", "enable", 1.0) == True - assert validate_filter_setting_value("subscriptiontrack", "enable", "0") == True - assert validate_filter_setting_value("subscriptiontrack", "enable", "1") == True - assert validate_filter_setting_value("subscriptiontrack", "enable", "0.0") == False - assert validate_filter_setting_value("subscriptiontrack", "enable", "1.0") == False + self.assertEqual(validate_filter_setting_value("subscriptiontrack", "enable", 0), True) + self.assertEqual(validate_filter_setting_value("subscriptiontrack", "enable", 1), True) + self.assertEqual(validate_filter_setting_value("subscriptiontrack", "enable", 0.0), True) + self.assertEqual(validate_filter_setting_value("subscriptiontrack", "enable", 1.0), True) + self.assertEqual(validate_filter_setting_value("subscriptiontrack", "enable", "0"), True) + self.assertEqual(validate_filter_setting_value("subscriptiontrack", "enable", "1"), True) + self.assertEqual(validate_filter_setting_value("subscriptiontrack", "enable", "0.0"), False) + self.assertEqual(validate_filter_setting_value("subscriptiontrack", "enable", "1.0"), False) def test_opentrack_enable_parameter(self): """ Tests the ``opentrack`` filter's ``enable`` paramter. """ - assert validate_filter_setting_value("opentrack", "enable", 0) == True - assert validate_filter_setting_value("opentrack", "enable", 1) == True - assert validate_filter_setting_value("opentrack", "enable", 0.0) == True - assert validate_filter_setting_value("opentrack", "enable", 1.0) == True - assert validate_filter_setting_value("opentrack", "enable", "0") == True - assert validate_filter_setting_value("opentrack", "enable", "1") == True - assert validate_filter_setting_value("opentrack", "enable", "0.0") == False - assert validate_filter_setting_value("opentrack", "enable", "1.0") == False + self.assertEqual(validate_filter_setting_value("opentrack", "enable", 0), True) + self.assertEqual(validate_filter_setting_value("opentrack", "enable", 1), True) + self.assertEqual(validate_filter_setting_value("opentrack", "enable", 0.0), True) + self.assertEqual(validate_filter_setting_value("opentrack", "enable", 1.0), True) + self.assertEqual(validate_filter_setting_value("opentrack", "enable", "0"), True) + self.assertEqual(validate_filter_setting_value("opentrack", "enable", "1"), True) + self.assertEqual(validate_filter_setting_value("opentrack", "enable", "0.0"), False) + self.assertEqual(validate_filter_setting_value("opentrack", "enable", "1.0"), False) class UpdateFiltersTests(TestCase): From f8874054ed04fca8cd5c1bf1e37bfb525e64eed8 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Tue, 6 Mar 2012 01:02:23 -0800 Subject: [PATCH 06/24] Moved getting the connection into the mixin --- sendgrid/message.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/sendgrid/message.py b/sendgrid/message.py index 75e7300..a3070ec 100644 --- a/sendgrid/message.py +++ b/sendgrid/message.py @@ -70,8 +70,17 @@ def get_unique_args(self): return uniqueArgs unique_args = property(get_unique_args) + def setup_connection(self): + """docstring for setup_connection""" + # Set up the connection + connection = get_sendgrid_connection() + self.connection = connection + logger.debug("Connection: {c}".format(c=connection)) + def prep_message_for_sending(self): """docstring for prep_message_for_sending""" + self.setup_connection() + # now = tz.localize(datetime.datetime.strptime(timestamp[:26], POSTMARK_DATETIME_STRING)).astimezone(pytz.utc) uniqueArgs = { "message_id": str(self._message_id), @@ -104,11 +113,6 @@ def __init__(self, *args, **kwargs): def send(self, *args, **kwargs): """Sends the email message.""" - # Set up the connection - connection = get_sendgrid_connection() - self.connection = connection - logger.debug("Connection: {c}".format(c=connection)) - self.prep_message_for_sending() response = super(SendGridEmailMessage, self).send(*args, **kwargs) @@ -130,13 +134,8 @@ def __init__(self, *args, **kwargs): def send(self, *args, **kwargs): """Sends the email message.""" - # Set up the connection - connection = get_sendgrid_connection() - self.connection = connection - logger.debug("Connection: {c}".format(c=connection)) - self.prep_message_for_sending() - + response = super(SendGridEmailMultiAlternatives, self).send(*args, **kwargs) logger.debug("Tried to send an email with SendGrid and got response {r}".format(r=response)) sendgrid_email_sent.send(sender=self, response=response) From 8b81035543f6821a34b9b1809feaa36cc936aacd Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Tue, 6 Mar 2012 13:52:08 -0800 Subject: [PATCH 07/24] Renamed misspelled requirements file --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e0cdb73 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Django==1.3.1 +distribute==0.6.24 +virtualenv==1.6.4 +virtualenvwrapper==2.10.1 +wsgiref==0.1.2 From 2693cbc7b0889a1ac02d97be80a0b2a2ae8cd2dc Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Tue, 6 Mar 2012 14:13:16 -0800 Subject: [PATCH 08/24] rm'd misspelled requirements file --- requirments.txt | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 requirments.txt diff --git a/requirments.txt b/requirments.txt deleted file mode 100644 index e0cdb73..0000000 --- a/requirments.txt +++ /dev/null @@ -1,5 +0,0 @@ -Django==1.3.1 -distribute==0.6.24 -virtualenv==1.6.4 -virtualenvwrapper==2.10.1 -wsgiref==0.1.2 From 8d75156aae59e8b33f2c09f2be8bf31c79e70d5f Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Tue, 6 Mar 2012 19:23:45 -0800 Subject: [PATCH 09/24] Renamed requirments.txt -> requirements.txt --- requirments.txt => requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename requirments.txt => requirements.txt (100%) diff --git a/requirments.txt b/requirements.txt similarity index 100% rename from requirments.txt rename to requirements.txt From f047f938992afd9f658367ec4b22118e5fe7b9d3 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Wed, 7 Mar 2012 11:46:04 -0800 Subject: [PATCH 10/24] Added 404.html --- example_project/templates/404.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 example_project/templates/404.html diff --git a/example_project/templates/404.html b/example_project/templates/404.html new file mode 100644 index 0000000..d857027 --- /dev/null +++ b/example_project/templates/404.html @@ -0,0 +1 @@ +404 Error \ No newline at end of file From 20906ca55339528b1d5180852e6a9fbd0928e5d1 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Wed, 7 Mar 2012 11:46:11 -0800 Subject: [PATCH 11/24] Added 500.html --- example_project/templates/500.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 example_project/templates/500.html diff --git a/example_project/templates/500.html b/example_project/templates/500.html new file mode 100644 index 0000000..da80778 --- /dev/null +++ b/example_project/templates/500.html @@ -0,0 +1 @@ +500 Error \ No newline at end of file From 45ede8e48320ac9586fb0806bb3f54b126e8d549 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Wed, 7 Mar 2012 18:03:55 -0800 Subject: [PATCH 12/24] Try to import local settings --- example_project/settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/example_project/settings.py b/example_project/settings.py index 7109b72..6f8f16a 100644 --- a/example_project/settings.py +++ b/example_project/settings.py @@ -169,3 +169,8 @@ SENDGRID_EMAIL_PORT = 587 SENDGRID_EMAIL_USERNAME = os.getenv("SENDGRID_EMAIL_USERNAME") SENDGRID_EMAIL_PASSWORD = os.getenv("SENDGRID_EMAIL_PASSWORD") + +try: + from settings_local import * +except: + pass From 8a8e9fbceb3c7d4a41909cdca35cc2e61ed2eb55 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Wed, 7 Mar 2012 19:08:39 -0800 Subject: [PATCH 13/24] Only handle ImportError --- example_project/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example_project/settings.py b/example_project/settings.py index 6f8f16a..d036465 100644 --- a/example_project/settings.py +++ b/example_project/settings.py @@ -172,5 +172,5 @@ try: from settings_local import * -except: +except ImportError: pass From 51757163b16cd465bce965287c8b9fdeffb6a115 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Thu, 8 Mar 2012 00:28:05 -0800 Subject: [PATCH 14/24] Updated example project urls --- example_project/urls.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/example_project/urls.py b/example_project/urls.py index d0903b6..7716f08 100644 --- a/example_project/urls.py +++ b/example_project/urls.py @@ -6,9 +6,8 @@ urlpatterns = patterns('', # Examples: - url(r'^$', include('example_project.main.urls')), - url(r'^sendgrid/$', include('example_project.main.urls')), - + url(r'^$', include("sendgrid.urls")), + url(r"^sendgrid/", include("sendgrid.urls")), # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), From ec897b7276e112b46698b2bf58a68f48b4e1a311 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Thu, 8 Mar 2012 00:28:32 -0800 Subject: [PATCH 15/24] Beautified example send email page --- .../main/templates/main/send_email.html | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/example_project/main/templates/main/send_email.html b/example_project/main/templates/main/send_email.html index 1f5ad0e..42efdba 100644 --- a/example_project/main/templates/main/send_email.html +++ b/example_project/main/templates/main/send_email.html @@ -3,11 +3,39 @@ django-sendgrid example project + + -
{% csrf_token %} - {{ form.as_p }} - -
+ +
+
{% csrf_token %} + {{ form.as_p }} + +
+
From 20403826d2b5a5dbcf481eadc65ed138bfc606cc Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Thu, 8 Mar 2012 00:29:09 -0800 Subject: [PATCH 16/24] Ignored docs/_build/* --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ae815ff..58c838c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.pyc *.DS_Store secret.sh +docs/_build/* build/* dist/* django_sendgrid.egg-info/* From f22b5f15487c9797ed83acc417b7601408e8d1d2 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Thu, 8 Mar 2012 00:33:54 -0800 Subject: [PATCH 17/24] Fixed broken urls --- example_project/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example_project/urls.py b/example_project/urls.py index 7716f08..106ee50 100644 --- a/example_project/urls.py +++ b/example_project/urls.py @@ -6,8 +6,8 @@ urlpatterns = patterns('', # Examples: - url(r'^$', include("sendgrid.urls")), - url(r"^sendgrid/", include("sendgrid.urls")), + url(r'^$', include("example_project.main.urls")), + url(r"^sendgrid/", include("example_project.main.urls")), # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), From 8740e87d5b89e8741ac1430e4e27fe443797f161 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Thu, 8 Mar 2012 00:38:46 -0800 Subject: [PATCH 18/24] Redirected root to /sendgrid/ --- example_project/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example_project/urls.py b/example_project/urls.py index 106ee50..1b3b59b 100644 --- a/example_project/urls.py +++ b/example_project/urls.py @@ -6,7 +6,7 @@ urlpatterns = patterns('', # Examples: - url(r'^$', include("example_project.main.urls")), + url(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/sendgrid/'}, name='home'), url(r"^sendgrid/", include("example_project.main.urls")), # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), From b3a07b6c64b00afe61b49b6ecc574160048b4c2b Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Tue, 13 Mar 2012 00:36:30 -0700 Subject: [PATCH 19/24] Raise ImproperlyConfigured when a setting isn't found --- sendgrid/backends.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sendgrid/backends.py b/sendgrid/backends.py index 2f83b4f..0bd3816 100644 --- a/sendgrid/backends.py +++ b/sendgrid/backends.py @@ -1,6 +1,7 @@ import logging from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django.core.mail.backends.smtp import EmailBackend @@ -11,8 +12,7 @@ logger = logging.getLogger(__name__) - -def check_settings(): +def check_settings(fail_silently=False): """ Checks that the required settings are available. """ @@ -29,17 +29,20 @@ def check_settings(): if not value: logger.warn("{k} is not set".format(k=key)) allOk = False + if not fail_silently: + raise ImproperlyConfigured("{k} was not found".format(k=key)) return allOk + class SendGridEmailBackend(EmailBackend): """ A wrapper that manages the SendGrid SMTP network connection. """ def __init__(self, host=None, port=None, username=None, password=None, use_tls=None, fail_silently=False, **kwargs): if not check_settings(): - raise ValueError("A required setting was not found") - + logger.exception("A required setting was not found") + super(SendGridEmailBackend, self).__init__( host=SENDGRID_EMAIL_HOST, port=SENDGRID_EMAIL_PORT, From ed9416417ca04d411667207677725c18b292043f Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Tue, 13 Mar 2012 00:36:36 -0700 Subject: [PATCH 20/24] Cleaned --- sendgrid/mixins.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sendgrid/mixins.py b/sendgrid/mixins.py index 6d2db36..3cca596 100644 --- a/sendgrid/mixins.py +++ b/sendgrid/mixins.py @@ -25,7 +25,6 @@ def add_to_unsubscribes(self): Adds the ``User``.``email`` from the unsubscribe list. """ result = add_unsubscribes(email=self.email) - print result return result def delete_from_unsubscribes(self): @@ -33,5 +32,4 @@ def delete_from_unsubscribes(self): Removes the ``User``.``email`` from the unsubscribe list. """ result = delete_unsubscribes(email=self.email) - print result return result From 81a506edd8941e94a5da3078fd76f93dd11882ef Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Tue, 13 Mar 2012 00:41:21 -0700 Subject: [PATCH 21/24] Cleaned --- sendgrid/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/tests.py b/sendgrid/tests.py index f68f5c5..798a0fb 100644 --- a/sendgrid/tests.py +++ b/sendgrid/tests.py @@ -217,4 +217,4 @@ def test_update_filters(self): }, } update_filters(self.email, filterSpec) - self.email.send() \ No newline at end of file + self.email.send() From fbf728c5034a9ff990495b967e0a78fc8a6f8f74 Mon Sep 17 00:00:00 2001 From: Ryan Balfanz Date: Sat, 24 Mar 2012 15:36:33 -0700 Subject: [PATCH 22/24] Added blank favicon --- example_project/main/templates/main/send_email.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example_project/main/templates/main/send_email.html b/example_project/main/templates/main/send_email.html index 42efdba..6c7d64e 100644 --- a/example_project/main/templates/main/send_email.html +++ b/example_project/main/templates/main/send_email.html @@ -10,6 +10,8 @@ } + +