0002-reminders-split-send-code-61234.patch
chrono/agendas/management/commands/send_booking_reminders.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
from datetime import datetime, timedelta |
18 |
from smtplib import SMTPException |
|
19 | 18 | |
20 | 19 |
import pytz |
21 | 20 |
from django.conf import settings |
22 |
from django.core.mail import send_mail |
|
23 | 21 |
from django.core.management.base import BaseCommand |
24 | 22 |
from django.db.models import F |
25 |
from django.db.transaction import atomic |
|
26 |
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist |
|
27 |
from django.template.loader import render_to_string |
|
28 | 23 |
from django.utils import timezone, translation |
29 |
from django.utils.translation import ugettext_lazy as _ |
|
30 |
from requests import RequestException |
|
31 | 24 | |
32 | 25 |
from chrono.agendas.models import Booking |
33 |
from chrono.utils.requests_wrapper import requests |
|
26 | ||
27 |
from .utils import send_reminder |
|
34 | 28 | |
35 | 29 |
SENDING_IN_PROGRESS = datetime(year=2, month=1, day=1, tzinfo=pytz.UTC) |
36 | 30 | |
... | ... | |
66 | 60 |
**{f'{msg_type}_reminder_datetime__isnull': True}, |
67 | 61 |
).select_related('event', 'event__agenda', 'event__agenda__reminder_settings') |
68 | 62 | |
69 |
bookings_list = list(bookings) |
|
63 |
bookings_list = list(bookings.order_by('pk'))
|
|
70 | 64 |
bookings_pk = list(bookings.values_list('pk', flat=True)) |
71 | 65 |
bookings.update(**{f'{msg_type}_reminder_datetime': SENDING_IN_PROGRESS}) |
72 | 66 | |
73 | 67 |
try: |
74 | 68 |
for booking in bookings_list: |
75 |
self.send_reminder(booking, msg_type)
|
|
69 |
send_reminder(booking, msg_type) |
|
76 | 70 |
finally: |
77 | 71 |
Booking.objects.filter( |
78 | 72 |
pk__in=bookings_pk, **{f'{msg_type}_reminder_datetime': SENDING_IN_PROGRESS} |
79 | 73 |
).update( |
80 | 74 |
**{f'{msg_type}_reminder_datetime': None}, |
81 | 75 |
) |
82 | ||
83 |
def send_reminder(self, booking, msg_type): |
|
84 |
agenda = booking.event.agenda |
|
85 |
kind = agenda.kind |
|
86 |
days = getattr(agenda.reminder_settings, f'days_before_{msg_type}') |
|
87 | ||
88 |
ctx = { |
|
89 |
'booking': booking, |
|
90 |
'in_x_days': _('tomorrow') if days == 1 else _('in %s days') % days, |
|
91 |
'date': booking.event.start_datetime, |
|
92 |
} |
|
93 |
ctx.update(settings.TEMPLATE_VARS) |
|
94 | ||
95 |
for extra_info in ('email_extra_info', 'sms_extra_info'): |
|
96 |
try: |
|
97 |
ctx[extra_info] = Template(getattr(agenda.reminder_settings, extra_info)).render( |
|
98 |
Context({'booking': booking}, autoescape=False) |
|
99 |
) |
|
100 |
except (VariableDoesNotExist, TemplateSyntaxError): |
|
101 |
pass |
|
102 | ||
103 |
if msg_type == 'email': |
|
104 |
emails = set(booking.extra_emails) |
|
105 |
if booking.user_email: |
|
106 |
emails.add(booking.user_email) |
|
107 | ||
108 |
for email in emails: |
|
109 |
self.send_email(email, booking, kind, ctx) |
|
110 |
elif msg_type == 'sms': |
|
111 |
phone_numbers = set(booking.extra_phone_numbers) |
|
112 |
if booking.user_phone_number: |
|
113 |
phone_numbers.add(booking.user_phone_number) |
|
114 | ||
115 |
if phone_numbers: |
|
116 |
self.send_sms(list(phone_numbers), booking, kind, ctx) |
|
117 | ||
118 |
@staticmethod |
|
119 |
def send_email(email, booking, kind, ctx): |
|
120 |
subject = render_to_string('agendas/%s_reminder_subject.txt' % kind, ctx).strip() |
|
121 |
body = render_to_string('agendas/%s_reminder_body.txt' % kind, ctx) |
|
122 |
html_body = render_to_string('agendas/%s_reminder_body.html' % kind, ctx) |
|
123 |
try: |
|
124 |
with atomic(): |
|
125 |
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [email], html_message=html_body) |
|
126 |
booking.email_reminder_datetime = timezone.now() |
|
127 |
booking.save() |
|
128 |
except SMTPException: |
|
129 |
pass |
|
130 | ||
131 |
@staticmethod |
|
132 |
def send_sms(phone_numbers, booking, kind, ctx): |
|
133 |
if not settings.SMS_URL: |
|
134 |
return |
|
135 | ||
136 |
message = render_to_string('agendas/%s_reminder_message.txt' % kind, ctx).strip() |
|
137 |
payload = { |
|
138 |
'message': message, |
|
139 |
'from': settings.SMS_SENDER, |
|
140 |
'to': phone_numbers, |
|
141 |
} |
|
142 | ||
143 |
try: |
|
144 |
with atomic(): |
|
145 |
request = requests.post( |
|
146 |
settings.SMS_URL, json=payload, remote_service='auto', timeout=10, without_user=True |
|
147 |
) |
|
148 |
request.raise_for_status() |
|
149 |
booking.sms_reminder_datetime = timezone.now() |
|
150 |
booking.save() |
|
151 |
except RequestException: |
|
152 |
pass |
chrono/agendas/management/commands/utils.py | ||
---|---|---|
1 |
from smtplib import SMTPException |
|
2 | ||
3 |
from django.conf import settings |
|
4 |
from django.core.mail import send_mail |
|
5 |
from django.db.transaction import atomic |
|
6 |
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist |
|
7 |
from django.template.loader import render_to_string |
|
8 |
from django.utils import timezone |
|
9 |
from django.utils.translation import ugettext_lazy as _ |
|
10 |
from requests import RequestException |
|
11 | ||
12 |
from chrono.utils.requests_wrapper import requests |
|
13 | ||
14 | ||
15 |
def send_reminder(booking, msg_type): |
|
16 |
agenda = booking.event.agenda |
|
17 |
kind = agenda.kind |
|
18 |
days = getattr(agenda.reminder_settings, f'days_before_{msg_type}') |
|
19 | ||
20 |
ctx = { |
|
21 |
'booking': booking, |
|
22 |
'in_x_days': _('tomorrow') if days == 1 else _('in %s days') % days, |
|
23 |
'date': booking.event.start_datetime, |
|
24 |
} |
|
25 |
ctx.update(settings.TEMPLATE_VARS) |
|
26 | ||
27 |
for extra_info in ('email_extra_info', 'sms_extra_info'): |
|
28 |
try: |
|
29 |
ctx[extra_info] = Template(getattr(agenda.reminder_settings, extra_info)).render( |
|
30 |
Context({'booking': booking}, autoescape=False) |
|
31 |
) |
|
32 |
except (VariableDoesNotExist, TemplateSyntaxError): |
|
33 |
pass |
|
34 | ||
35 |
if msg_type == 'email': |
|
36 |
emails = set(booking.extra_emails) |
|
37 |
if booking.user_email: |
|
38 |
emails.add(booking.user_email) |
|
39 | ||
40 |
for email in emails: |
|
41 |
send_email_reminder(email, booking, kind, ctx) |
|
42 |
elif msg_type == 'sms': |
|
43 |
phone_numbers = set(booking.extra_phone_numbers) |
|
44 |
if booking.user_phone_number: |
|
45 |
phone_numbers.add(booking.user_phone_number) |
|
46 | ||
47 |
if phone_numbers: |
|
48 |
send_sms_reminder(list(phone_numbers), booking, kind, ctx) |
|
49 | ||
50 | ||
51 |
def send_email_reminder(email, booking, kind, ctx): |
|
52 |
subject = render_to_string('agendas/%s_reminder_subject.txt' % kind, ctx).strip() |
|
53 |
body = render_to_string('agendas/%s_reminder_body.txt' % kind, ctx) |
|
54 |
html_body = render_to_string('agendas/%s_reminder_body.html' % kind, ctx) |
|
55 |
try: |
|
56 |
with atomic(): |
|
57 |
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [email], html_message=html_body) |
|
58 |
booking.email_reminder_datetime = timezone.now() |
|
59 |
booking.save() |
|
60 |
except SMTPException: |
|
61 |
pass |
|
62 | ||
63 | ||
64 |
def send_sms_reminder(phone_numbers, booking, kind, ctx): |
|
65 |
if not settings.SMS_URL: |
|
66 |
return |
|
67 | ||
68 |
message = render_to_string('agendas/%s_reminder_message.txt' % kind, ctx).strip() |
|
69 |
payload = { |
|
70 |
'message': message, |
|
71 |
'from': settings.SMS_SENDER, |
|
72 |
'to': phone_numbers, |
|
73 |
} |
|
74 | ||
75 |
try: |
|
76 |
with atomic(): |
|
77 |
request = requests.post( |
|
78 |
settings.SMS_URL, json=payload, remote_service='auto', timeout=10, without_user=True |
|
79 |
) |
|
80 |
request.raise_for_status() |
|
81 |
booking.sms_reminder_datetime = timezone.now() |
|
82 |
booking.save() |
|
83 |
except RequestException: |
|
84 |
pass |
tests/test_agendas.py | ||
---|---|---|
1737 | 1737 |
def send_mail_error(*args, **kwargs): |
1738 | 1738 |
raise smtplib.SMTPException |
1739 | 1739 | |
1740 |
with mock.patch('chrono.agendas.management.commands.send_booking_reminders.send_mail') as mock_send:
|
|
1740 |
with mock.patch('chrono.agendas.management.commands.utils.send_mail') as mock_send:
|
|
1741 | 1741 |
mock_send.return_value = None |
1742 | 1742 |
mock_send.side_effect = send_mail_error |
1743 | 1743 |
call_command('send_booking_reminders') |
... | ... | |
1787 | 1787 |
freezer.move_to('2020-01-02 15:00') |
1788 | 1788 | |
1789 | 1789 |
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send, mock.patch( |
1790 |
'chrono.agendas.management.commands.send_booking_reminders.send_mail'
|
|
1790 |
'chrono.agendas.management.commands.utils.send_mail'
|
|
1791 | 1791 |
) as mock_send_mail: |
1792 | 1792 |
mock_response = mock.Mock(status_code=200) |
1793 | 1793 |
mock_send.return_value = mock_response |
... | ... | |
1815 | 1815 |
Booking.objects.create(event=event, user_email='t@test.org', user_phone_number='+336123456789') |
1816 | 1816 | |
1817 | 1817 |
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send_sms, mock.patch( |
1818 |
'chrono.agendas.management.commands.send_booking_reminders.send_mail'
|
|
1818 |
'chrono.agendas.management.commands.utils.send_mail'
|
|
1819 | 1819 |
) as mock_send_mail: |
1820 | 1820 |
mock_response = mock.Mock(status_code=200) |
1821 | 1821 |
mock_send_sms.return_value = mock_response |
1822 |
- |