Skip to content

Commit ffdc598

Browse files
authored
Merge pull request #79 from thinkt4nk/chore/merge-v2-hotfixes
Chore/merge v2 hotfixes
2 parents 3c1605e + 4026c65 commit ffdc598

File tree

5 files changed

+65
-12
lines changed

5 files changed

+65
-12
lines changed

entity_emailer/interface.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import traceback
44

55
from datetime import datetime
6-
6+
from django.db import transaction
77
from django.core import mail
88
from entity_event import context_loader
99

@@ -94,7 +94,7 @@ def send_unsent_scheduled_emails(cls):
9494
for email in emails_to_send:
9595
try:
9696
# Send mail
97-
connection.send_message(email.get('message'))
97+
connection.send_messages(email.get('message'))
9898
except Exception as e:
9999
cls.save_email_exception(email.get('model'), e)
100100

@@ -123,6 +123,7 @@ def convert_events_to_emails():
123123
Email.objects.create_email(event=event, from_address=from_address, recipients=targets)
124124

125125
@staticmethod
126+
@transaction.atomic
126127
def bulk_convert_events_to_emails():
127128
"""
128129
Converts unseen events to emails and marks them as seen. Uses the create_emails method to bulk create

entity_emailer/models.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,20 @@ def create_emails(self, email_params_list):
4747

4848
# Build list of recipient through relationships to create
4949
recipients_to_create = []
50+
51+
# Keep track of unique pairs to avoid unique constraint on through relationship
52+
email_entity_pairs = set()
5053
for i, recipient_entities in enumerate(recipient_entities_per_email):
5154
for recipient_entity in recipient_entities:
52-
recipients_to_create.append(
53-
Email.recipients.through(
54-
email_id=emails[i].id,
55-
entity_id=recipient_entity.id,
55+
if (emails[i].id, recipient_entity.id) not in email_entity_pairs:
56+
email_entity_pairs.add((emails[i].id, recipient_entity.id))
57+
58+
recipients_to_create.append(
59+
Email.recipients.through(
60+
email_id=emails[i].id,
61+
entity_id=recipient_entity.id,
62+
)
5663
)
57-
)
5864

5965
# Bulk create the recipient relationships
6066
Email.recipients.through.objects.bulk_create(recipients_to_create)

entity_emailer/tests/interface_tests.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,15 @@ def test_multiple_events_only_following_false(self):
298298

299299
@freeze_time('2013-1-2')
300300
def test_bulk_multiple_events_only_following_false(self):
301+
"""
302+
Handles bulk creating events and tests the unique constraint of the duplicated subscription which would cause
303+
a bulk create error if it wasn't handled
304+
"""
301305
source = G(Source)
302306
e = G(Entity)
303307
other_e = G(Entity)
304308

309+
G(Subscription, entity=e, source=source, medium=self.email_medium, only_following=False)
305310
G(Subscription, entity=e, source=source, medium=self.email_medium, only_following=False)
306311
G(Subscription, entity=other_e, source=source, medium=self.email_medium, only_following=False)
307312
email_context = {
@@ -320,6 +325,35 @@ def test_bulk_multiple_events_only_following_false(self):
320325
self.assertEquals(email.subject, '')
321326
self.assertEquals(email.scheduled, datetime(2013, 1, 2))
322327

328+
@freeze_time('2013-1-2')
329+
def test_bulk_multiple_events_only_following_true(self):
330+
"""
331+
Handles bulk creating events and tests the unique constraint of the duplicated subscription which would cause
332+
a bulk create error if it wasn't handled
333+
"""
334+
source = G(Source)
335+
e = G(Entity)
336+
other_e = G(Entity)
337+
338+
G(Subscription, entity=e, source=source, medium=self.email_medium, only_following=True)
339+
G(Subscription, entity=e, source=source, medium=self.email_medium, only_following=True)
340+
G(Subscription, entity=other_e, source=source, medium=self.email_medium, only_following=True)
341+
email_context = {
342+
'entity_emailer_template': 'template',
343+
'entity_emailer_subject': 'hi',
344+
}
345+
G(Event, source=source, context=email_context)
346+
event = G(Event, source=source, context=email_context)
347+
G(EventActor, event=event, entity=e)
348+
349+
EntityEmailerInterface.bulk_convert_events_to_emails()
350+
351+
email = Email.objects.get()
352+
self.assertEquals(set(email.recipients.all()), set([e]))
353+
self.assertEquals(email.event.context, email_context)
354+
self.assertEquals(email.subject, '')
355+
self.assertEquals(email.scheduled, datetime(2013, 1, 2))
356+
323357
@freeze_time('2013-1-2')
324358
def test_multiple_events_only_following_true(self):
325359
source = G(Source)
@@ -371,7 +405,7 @@ def test_sends_all_scheduled_emails(self, render_mock, address_mock):
371405
with patch(settings.EMAIL_BACKEND) as mock_connection:
372406
EntityEmailerInterface.send_unsent_scheduled_emails()
373407

374-
self.assertEqual(2, mock_connection.return_value.__enter__.return_value.send_message.call_count)
408+
self.assertEqual(2, mock_connection.return_value.__enter__.return_value.send_messages.call_count)
375409

376410
@patch('entity_emailer.interface.pre_send')
377411
@patch('entity_emailer.interface.get_subscribed_email_addresses')
@@ -392,7 +426,7 @@ def test_send_signals(self, render_mock, address_mock, mock_pre_send):
392426
EntityEmailerInterface.send_unsent_scheduled_emails()
393427

394428
# Assert that we sent the email
395-
self.assertEqual(1, mock_connection.return_value.__enter__.return_value.send_message.call_count)
429+
self.assertEqual(1, mock_connection.return_value.__enter__.return_value.send_messages.call_count)
396430

397431
# Assert that we called the pre send signal with the proper values
398432
name, args, kwargs = mock_pre_send.send.mock_calls[0]
@@ -416,7 +450,7 @@ def test_sends_email_with_specified_from_address(self, render_mock, address_mock
416450
with patch(settings.EMAIL_BACKEND) as mock_connection:
417451
EntityEmailerInterface.send_unsent_scheduled_emails()
418452

419-
args = mock_connection.return_value.__enter__.return_value.send_message.call_args
453+
args = mock_connection.return_value.__enter__.return_value.send_messages.call_args
420454
self.assertEqual(args[0][0].from_email, from_address)
421455

422456
@patch('entity_emailer.interface.get_subscribed_email_addresses')
@@ -504,7 +538,7 @@ def to_dict(self):
504538

505539
with patch(settings.EMAIL_BACKEND) as mock_connection:
506540
# Mock side effects for sending emails
507-
mock_connection.return_value.__enter__.return_value.send_message.side_effect = [
541+
mock_connection.return_value.__enter__.return_value.send_messages.side_effect = [
508542
None,
509543
TestEmailSendMessageException('test'),
510544
]

entity_emailer/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.0.1'
1+
__version__ = '2.0.3'

release_notes.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
Release Notes
22
=============
33

4+
v2.0.3
5+
------
6+
* Merging v2 hotfixes
7+
48
v2.0.1
59
------
610
* Fix for handling single failures in a batch of outgoing emails
711

12+
v2.0.0.2
13+
------
14+
* Atomic decorator on event fetching
15+
16+
v2.0.0.1
17+
------
18+
* Fix unique constraint when bulk creating emails
19+
820
v2.0.0
921
------
1022
* Added bulk interface for converting to emails

0 commit comments

Comments
 (0)