Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
3d8c6f3
test for batched events. currently fails on NotImplementedError
Dec 6, 2012
23b21f3
refactored event view to use create_event_from_sendgrid_params for le…
Dec 6, 2012
bebaafa
updating SendGridBatchedEventTest with "event" param and event count …
Dec 6, 2012
0f7fd96
batched events view
Dec 6, 2012
ac40fc7
updated batched event test to follow send grids format
Dec 6, 2012
b6e77c1
updated batched event view to pass new test for send grid format
Dec 6, 2012
69503ab
added \r to split in batched event view
Dec 6, 2012
9c367b5
#49; Refactored event separator into constants
RyanBalfanz Dec 6, 2012
43311b1
added new line at end of body for batch event test
Dec 6, 2012
ebfa950
for batch events parsing use split lines instead of split('\r\n')
Dec 6, 2012
ddb30ef
Minor cleanup
RyanBalfanz Dec 6, 2012
1405287
Merge branch 'feature/batch_sendgrid_events' of github.com:RyanBalfan…
Dec 6, 2012
046fda9
Merge branch 'feature/batch_sendgrid_events' of github.com:RyanBalfan…
Dec 6, 2012
a089de9
#49; More cleanup
RyanBalfanz Dec 6, 2012
98e20ab
Merge branch 'feature/batch_sendgrid_events' of github.com:RyanBalfan…
RyanBalfanz Dec 6, 2012
341c806
#49; Use request.body (for django 1.4) and warn about future dependency
RyanBalfanz Dec 6, 2012
06ac433
#49; Fixed bad merge /cc @hoddez
RyanBalfanz Dec 6, 2012
30cebdc
added a test for newsletter events which currently fails. will addres…
Dec 7, 2012
3f2dbad
@ryan i think you meant to do this?
Dec 7, 2012
a7f04ee
get_value_from_dict_using_formdata_key util. there might be some djan…
Dec 7, 2012
a29aef8
refactored newsletter behavior to accept json formatting and pass new…
Dec 7, 2012
41b2c6f
added convert_dict_to_urlencoded_string
Dec 10, 2012
82926c8
new formatutils for converting between flat and nested dicts
Dec 11, 2012
f232e4c
refactored event tests, added some new ones for newsletters
Dec 11, 2012
69a66ed
refactored from_event to expect only one type of format
Dec 11, 2012
aa05dfd
new setting SENDGRID_CREATE_MISSING_EMAILS_FOR_EVENTS_WITH_MESSAGE_ID
Dec 11, 2012
847d89d
refactored handle_event views to convert single event style to batche…
Dec 11, 2012
55f63b2
create changed to get_or_create for EmailMessage in from_event
Dec 17, 2012
21f3ab7
Merge branch 'develop' into feature/batch_sendgrid_events
Dec 18, 2012
19d8473
back to get for emailMessage in from_event
Dec 18, 2012
b6bc42e
check for single categories
Dec 18, 2012
4e53083
Revert "back to get for emailMessage in from_event"
Dec 18, 2012
529c33c
emailMessage get_or_create changed back to create
Dec 18, 2012
912fa7f
some temporary logging to debug race condition
Dec 18, 2012
eb75345
fixed logging
Dec 18, 2012
e1650cb
trying manual transactions to beat race condition
Dec 18, 2012
48602d0
flush_transaction
Dec 19, 2012
0374b45
removed some flush_transactions which I think are not necessary
Dec 19, 2012
3705167
added another flush_transaction to events view
Dec 19, 2012
01ea75a
Revert "removed some flush_transactions which I think are not necessary"
Dec 19, 2012
f33d1ba
refactored flush_transaction to a dbutil
Dec 19, 2012
729386b
store events in bulk insert
Dec 29, 2012
18c1cfd
added a test for getting events from multiple newsletters in one batch
hoddez Dec 31, 2012
56193f0
[WIP] dump of bulk insert refactor for email events
hoddez Dec 31, 2012
f176ba5
typos
hoddez Dec 31, 2012
74b41ab
still [WIP] adding categories and arguments
hoddez Dec 31, 2012
d8360b1
test_batch_create_events passes
Dec 31, 2012
a2616e3
refactoring category and uniquearg building into functions for more r…
Dec 31, 2012
d4788e7
switched handle_batched_events_request to user batch_create_events
Dec 31, 2012
aa062a0
fixed multiple email creation on newsletter events for the same to_email
Dec 31, 2012
b535f08
only create categories/unique args once per email
Dec 31, 2012
dd354ff
batching category and unique arg creation
Dec 31, 2012
927fca7
eventTypes refactored to get only once
Dec 31, 2012
4c992b5
removed old commented lines
Dec 31, 2012
bf79b2e
refactored event lists for batching a bit
Dec 31, 2012
55323fc
Revert "refactored event lists for batching a bit"
Dec 31, 2012
c3c1930
added batching for events with message_ids
Jan 1, 2013
79f4164
altered batched test to include some events with message ids for whic…
Jan 1, 2013
5bf9cf1
added in batching for emails with message_ids, tests pass
Jan 1, 2013
1213f0e
added checks for None eventToCreate to fix 500 error on repeat/non-un…
Jan 1, 2013
6ec11cc
derp
Jan 1, 2013
cd9db1e
refactoring events tests for cleanliness and more DRY
Jan 2, 2013
e5c7587
moved transaction to whole batch_create_events, they should either al…
Jan 2, 2013
6c51035
fixed bug with events that have extra params
Jan 2, 2013
621bd54
fixed bad test for case when newsletter events are off
Jan 2, 2013
b5b451b
added response to delivered event test in batched
Jan 2, 2013
533b1db
refactored bulk_create_emails_with_manual_ids to a general manager
Jan 3, 2013
36aefa1
added manual post_save signals to send grid events
Jan 3, 2013
b7972c9
use new BulkCreate manager for events to get the post_save
Jan 3, 2013
dfd52be
refactoring constants
Jan 3, 2013
d3cfc2b
added batched option to post_test_event
Jan 3, 2013
91290cf
added post_save checking to a batched events test
Jan 3, 2013
55aceca
import json
Jan 3, 2013
aea5e0b
oops
Jan 3, 2013
cd5f2c8
added using='default' to events bulk post_save
Jan 3, 2013
ea68d7f
changed id to pk in bulk_create_with_manual_ids
Jan 3, 2013
bf2997e
added initial eventObj
Jan 3, 2013
fe3e26e
removed flush_transaction from save_email_message
Jan 16, 2013
cb9de51
removed flush_transaction from sendgrid events view
Jan 16, 2013
06a6ba8
new way of getting id for bulk_create_with_manual_ids that will lock …
Feb 28, 2013
77a980d
bulk_create_with_post_save refactor
Mar 1, 2013
3857457
bulk_create_with_manual_ids_retry
Mar 6, 2013
2a92541
use bulk_create_with_manual_ids_retry for batched events
Mar 6, 2013
5156ca7
Mar 6, 2013
20ccab4
Swapped out User for AUTH_USER_MODEL
Mar 8, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion example_project/main/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
User = get_user_model()
# from django.core.mail import get_connection
from django.core.mail import EmailMessage
# from django.db import models
Expand Down
2 changes: 1 addition & 1 deletion example_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
SENDGRID_EMAIL_PORT = 587
SENDGRID_EMAIL_USERNAME = os.getenv("SENDGRID_EMAIL_USERNAME")
SENDGRID_EMAIL_PASSWORD = os.getenv("SENDGRID_EMAIL_PASSWORD")
# SENDGRID_CREATE_MISSING_EMAIL_MESSAGES = True
# SENDGRID_CREATE_EVENTS_AND_EMAILS_FOR_NEWSLETTERS = True

try:
from settings_local import *
Expand Down
30 changes: 28 additions & 2 deletions sendgrid/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
ARGUMENT_DATA_TYPE_FLOAT = 3
ARGUMENT_DATA_TYPE_COMPLEX = 4
ARGUMENT_DATA_TYPE_STRING = 5

BATCHED_EVENT_SEPARATOR = "\r\n"

EVENT_SHORT_DESC_MAX_LENGTH = 32

EVENT_FIELDS = ("event","category","email")
Expand Down Expand Up @@ -34,8 +37,31 @@
"SPAMREPORT": (),
}

UNIQUE_ARGS_STORED_FOR_EVENTS_WITHOUT_MESSAGE_ID = (
NEWSLETTER_UNIQUE_IDENTIFIER = "newsletter[newsletter_id]"
UNIQUE_ARGS_STORED_FOR_NEWSLETTER_EVENTS = (
"newsletter[newsletter_id]",
"newsletter[newsletter_send_id]",
"newsletter[newsletter_user_list_id]",
)
)

TEST_SENDER_EMAIL = "ryan@example.com"
TEST_RECIPIENTS = ["ryan@example.com", "tom@example.com","anotherguy@example.com"]

SAMPLE_NEWSLETTER_IDS = {
"newsletter_send_id": "952852",
"newsletter_id": "916273",
"newsletter_user_list_id": "5059777"
}

SAMPLE_NEWSLETTER_IDS_2 = {
"newsletter_send_id": "666",
"newsletter_id": "4324324",
"newsletter_user_list_id": "2344324"
}

SAMPLE_EVENT_DICT_WITHOUT_MESSAGE_ID_OR_TIMESTAMP = {
"email": TEST_RECIPIENTS[0],
"category":["category1"],
"event": "OPEN"
}

40 changes: 39 additions & 1 deletion sendgrid/mixins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.conf import settings
from django.db import models, transaction, IntegrityError
from django.utils import simplejson

from utils import add_unsubscribes
Expand All @@ -9,6 +10,43 @@
SENDGRID_EMAIL_USERNAME = getattr(settings, "SENDGRID_EMAIL_USERNAME", None)
SENDGRID_EMAIL_PASSWORD = getattr(settings, "SENDGRID_EMAIL_PASSWORD", None)

class BulkCreateManager(models.Manager):
@transaction.commit_on_success
def bulk_create_with_post_save(self,instances):
instancesCreated = self.bulk_create(instances)
for instance in instancesCreated:
#this should possibly be abandoned for a custom bulk_post_save signal for efficiency reasons
models.signals.post_save.send(
sender=instance.__class__,
instance=instance,
created=True,
raw=False,
using='default'
)
return instancesCreated

def bulk_create_with_manual_ids(self,instances):
try:
start = self.select_for_update().all().order_by('-pk')[0].pk + 1
except IndexError:
start = 1
for i,instance in enumerate(instances):
instance.pk = start + i

return self.bulk_create_with_post_save(instances)

def bulk_create_with_manual_ids_retry(self,instances,max_retries=5,retry_counter=0):
try:
return self.bulk_create_with_manual_ids(instances)
except IntegrityError, e:
if "Duplicate" in e.__str__() and "PRIMARY" in e.__str__() and retry_counter < max_retries:
return self.bulk_create_with_manual_ids_retry(
instances=instances,
max_retries=max_retries,
retry_counter=retry_counter+1
)
else:
raise e

class SendGridUserMixin:
"""
Expand Down Expand Up @@ -36,4 +74,4 @@ def delete_from_unsubscribes(self):
"""
response = delete_unsubscribes(email=self.email)
result = simplejson.loads(response)
return result
return result
56 changes: 37 additions & 19 deletions sendgrid/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _

from .mixins import BulkCreateManager
from .signals import sendgrid_email_sent
from .signals import sendgrid_event_recieved
from sendgrid.constants import (
Expand All @@ -18,9 +19,11 @@
ARGUMENT_DATA_TYPE_FLOAT,
ARGUMENT_DATA_TYPE_COMPLEX,
ARGUMENT_DATA_TYPE_STRING,
UNIQUE_ARGS_STORED_FOR_EVENTS_WITHOUT_MESSAGE_ID,
UNIQUE_ARGS_STORED_FOR_NEWSLETTER_EVENTS,
NEWSLETTER_UNIQUE_IDENTIFIER,
)
from sendgrid.signals import sendgrid_email_sent
from sendgrid.utils.formatutils import get_value_from_dict_using_formdata_key

MAX_CATEGORIES_PER_EMAIL_MESSAGE = 10

Expand Down Expand Up @@ -50,7 +53,8 @@
EMAIL_MESSAGE_TO_EMAIL_MAX_LENGTH = 254

if SENDGRID_USER_MIXIN_ENABLED:
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
User = get_user_model()
from .mixins import SendGridUserMixin

User.__bases__ += (SendGridUserMixin,)
Expand All @@ -64,6 +68,7 @@ def update_email_message(sender, message, response, **kwargs):
emailMessage.response = response
emailMessage.save()


def save_email_message(sender, **kwargs):
message = kwargs.get("message", None)
response = kwargs.get("response", None)
Expand Down Expand Up @@ -104,6 +109,8 @@ def save_email_message(sender, **kwargs):
response=response,
)

logger.debug("DEBUG-EVENT: emailMessage record Created with message_id:{0}".format(emailMessage.message_id))

if categories:
for categoryName in categories:
category, created = Category.objects.get_or_create(name=categoryName)
Expand Down Expand Up @@ -141,6 +148,8 @@ def save_email_message(sender, **kwargs):
logMessage = "Component {c} is not tracked"
logger.debug(logMessage.format(c=component))

return emailMessage

@receiver(sendgrid_event_recieved)
def log_event_recieved(sender, request, **kwargs):
if settings.DEBUG:
Expand Down Expand Up @@ -187,8 +196,8 @@ class Meta:
def __unicode__(self):
return self.key


class EmailMessage(models.Model):
objects = BulkCreateManager()
message_id = models.CharField(unique=True, max_length=36, editable=False, blank=True, null=True, help_text="UUID")
# user = models.ForeignKey(User, null=True) # TODO
from_email = models.CharField(max_length=EMAIL_MESSAGE_FROM_EMAIL_MAX_LENGTH, help_text="Sender's e-mail")
Expand All @@ -209,12 +218,14 @@ def from_event(self, event_dict):
"""
Returns a new EmailMessage instance derived from an Event Dictionary.
"""
newsletter_id = event_dict.get("newsletter[newsletter_id]")
newsletter_id = get_value_from_dict_using_formdata_key(NEWSLETTER_UNIQUE_IDENTIFIER,event_dict)

to_email = event_dict.get("email")
try:
emailMessage = UniqueArgument.objects.get(data=newsletter_id, argument__key="newsletter[newsletter_id]", email_message__to_email=to_email).email_message
emailMessage = UniqueArgument.objects.get(data=newsletter_id, argument__key=NEWSLETTER_UNIQUE_IDENTIFIER, email_message__to_email=to_email).email_message
except UniqueArgument.DoesNotExist:
categories = [value for key,value in event_dict.items() if 'category' in key]
categories = event_dict.get("category",[])

emailMessageSpec = {
"message_id": event_dict.get("message_id", None),
"from_email": "",
Expand All @@ -223,26 +234,32 @@ def from_event(self, event_dict):
}
if len(categories) > 0:
emailMessageSpec["category"] = categories[0]


if event_dict.get("message_id", None):
logger.debug("DEBUG-EVENT: Trying To Create Email From Event with message_id {0}".format(event_dict.get("message_id", None)))
existingEmail = EmailMessage.objects.filter(message_id=event_dict.get("message_id", None))
if len(existingEmail) > 0:
logger.debug("DEBUG-EVENT: Found an existing email with message_id {0}".format(event_dict.get("message_id", None)))
emailMessage = EmailMessage.objects.create(**emailMessageSpec)

for category in categories:
categoryObj,created = Category.objects.get_or_create(name=category)
emailMessage.categories.add(categoryObj)

uniqueArgs = {}
for key in UNIQUE_ARGS_STORED_FOR_EVENTS_WITHOUT_MESSAGE_ID:
uniqueArgs[key] = event_dict.get(key)
if newsletter_id:
uniqueArgs = {}
for key in UNIQUE_ARGS_STORED_FOR_NEWSLETTER_EVENTS:
uniqueArgs[key] = get_value_from_dict_using_formdata_key(key,event_dict)

for argName, argValue in uniqueArgs.items():
argument,_ = Argument.objects.get_or_create(
key=argName
)
uniqueArg = UniqueArgument.objects.create(
argument=argument,
email_message=emailMessage,
data=argValue
)
for argName, argValue in uniqueArgs.items():
argument,_ = Argument.objects.get_or_create(
key=argName
)
uniqueArg = UniqueArgument.objects.create(
argument=argument,
email_message=emailMessage,
data=argValue
)

return emailMessage

Expand Down Expand Up @@ -439,6 +456,7 @@ def __unicode__(self):


class Event(models.Model):
objects = BulkCreateManager()
email_message = models.ForeignKey(EmailMessage)
email = models.EmailField()
event_type = models.ForeignKey(EventType)
Expand Down
3 changes: 2 additions & 1 deletion sendgrid/settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.conf import settings

# This is experimental, use with caution.
SENDGRID_CREATE_MISSING_EMAIL_MESSAGES = getattr(settings, "SENDGRID_CREATE_MISSING_EMAIL_MESSAGES", False)
SENDGRID_CREATE_MISSING_EMAILS_FOR_EVENTS_WITH_MESSAGE_ID = getattr(settings, "SENDGRID_CREATE_MISSING_EMAILS_FOR_EVENTS_WITH_MESSAGE_ID", False)
SENDGRID_CREATE_EVENTS_AND_EMAILS_FOR_NEWSLETTERS = getattr(settings, "SENDGRID_CREATE_EVENTS_AND_EMAILS_FOR_NEWSLETTERS", False)
Loading