0003-manager-test-booking-reminders-sending-61234.patch
chrono/agendas/management/commands/utils.py | ||
---|---|---|
32 | 32 |
except (VariableDoesNotExist, TemplateSyntaxError): |
33 | 33 |
pass |
34 | 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: |
|
35 |
if msg_type == 'email' and booking.emails: |
|
36 |
for email in booking.emails: |
|
41 | 37 |
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) |
|
38 |
elif msg_type == 'sms' and booking.phone_numbers: |
|
39 |
send_sms_reminder(booking.phone_numbers, booking, kind, ctx) |
|
49 | 40 | |
50 | 41 | |
51 | 42 |
def send_email_reminder(email, booking, kind, ctx): |
chrono/agendas/models.py | ||
---|---|---|
1897 | 1897 |
def user_name(self): |
1898 | 1898 |
return ('%s %s' % (self.user_first_name, self.user_last_name)).strip() |
1899 | 1899 | |
1900 |
@cached_property |
|
1901 |
def emails(self): |
|
1902 |
emails = set(self.extra_emails) |
|
1903 |
if self.user_email: |
|
1904 |
emails.add(self.user_email) |
|
1905 |
return list(emails) |
|
1906 | ||
1907 |
@cached_property |
|
1908 |
def phone_numbers(self): |
|
1909 |
phone_numbers = set(self.extra_phone_numbers) |
|
1910 |
if self.user_phone_number: |
|
1911 |
phone_numbers.add(self.user_phone_number) |
|
1912 |
return list(phone_numbers) |
|
1913 | ||
1900 | 1914 |
def cancel(self, trigger_callback=False): |
1901 | 1915 |
timestamp = now() |
1902 | 1916 |
with transaction.atomic(): |
chrono/manager/forms.py | ||
---|---|---|
26 | 26 |
from django.db import transaction |
27 | 27 |
from django.forms import ValidationError |
28 | 28 |
from django.utils.encoding import force_text |
29 |
from django.utils.formats import date_format |
|
29 | 30 |
from django.utils.six import StringIO |
30 |
from django.utils.timezone import make_aware, now |
|
31 |
from django.utils.timezone import localtime, make_aware, now
|
|
31 | 32 |
from django.utils.translation import ugettext_lazy as _ |
32 | 33 | |
33 | 34 |
from chrono.agendas.models import ( |
... | ... | |
926 | 927 |
del self.fields['sms_extra_info'] |
927 | 928 | |
928 | 929 | |
930 |
class BookingChoiceField(forms.ModelChoiceField): |
|
931 |
def label_from_instance(self, obj): |
|
932 |
name = obj.user_name or obj.label or _('Anonymous') |
|
933 |
date = date_format(localtime(obj.creation_datetime), 'SHORT_DATETIME_FORMAT') |
|
934 |
emails = ', '.join(sorted(obj.emails)) or _('no email') |
|
935 |
phone_numbers = ', '.join(sorted(obj.phone_numbers)) or _('no phone number') |
|
936 | ||
937 |
if settings.SMS_URL: |
|
938 |
return '%s, %s, %s (%s)' % (name, emails, phone_numbers, date) |
|
939 |
else: |
|
940 |
return '%s, %s (%s)' % (name, emails, date) |
|
941 | ||
942 | ||
943 |
class AgendaReminderTestForm(forms.Form): |
|
944 |
booking = BookingChoiceField( |
|
945 |
label=_('Booking'), |
|
946 |
queryset=Booking.objects.none(), |
|
947 |
help_text=_('Only the last ten bookings are displayed.'), |
|
948 |
) |
|
949 |
msg_type = forms.MultipleChoiceField( |
|
950 |
label=_('Send via'), |
|
951 |
choices=(('email', _('Email')), ('sms', _('SMS'))), |
|
952 |
widget=forms.CheckboxSelectMultiple(), |
|
953 |
) |
|
954 |
phone_number = forms.CharField( |
|
955 |
label=_('Phone number'), |
|
956 |
max_length=16, |
|
957 |
help_text=_('This will override phone number specified on booking creation, if any.'), |
|
958 |
required=False, |
|
959 |
) |
|
960 | ||
961 |
def __init__(self, *args, **kwargs): |
|
962 |
agenda = kwargs.pop('agenda') |
|
963 |
super().__init__(*args, **kwargs) |
|
964 |
self.fields['booking'].queryset = Booking.objects.filter( |
|
965 |
pk__in=Booking.objects.filter(event__agenda=agenda).order_by('-creation_datetime')[:10] |
|
966 |
).order_by('-creation_datetime') |
|
967 | ||
968 |
if not settings.SMS_URL: |
|
969 |
self.fields['msg_type'].initial = ['email'] |
|
970 |
self.fields['msg_type'].widget = forms.MultipleHiddenInput() |
|
971 |
del self.fields['phone_number'] |
|
972 | ||
973 | ||
929 | 974 |
class AgendasExportForm(forms.Form): |
930 | 975 |
agendas = forms.BooleanField(label=_('Agendas'), required=False, initial=True) |
931 | 976 |
unavailability_calendars = forms.BooleanField( |
chrono/manager/templates/chrono/manager_agenda_settings.html | ||
---|---|---|
57 | 57 |
<a rel="popup" data-selector="#message-preview" href="{% url 'chrono-manager-agenda-reminder-preview' pk=object.id type='sms' %}">{% trans "Preview SMS" %}</a> |
58 | 58 |
{% endif %} |
59 | 59 |
</p> |
60 |
{% if agenda.reminder_settings.days_before_email or agenda.reminder_settings.days_before_sms %} |
|
61 |
<p> |
|
62 |
<a rel="popup" href="{% url 'chrono-manager-agenda-reminder-test' pk=object.pk %}">{% trans "Test reminder sending" %}</a> |
|
63 |
</p> |
|
64 |
{% endif %} |
|
60 | 65 |
</div> |
61 | 66 |
</div> |
62 | 67 |
{% endblock %} |
chrono/manager/templates/chrono/manager_send_reminder_form.html | ||
---|---|---|
1 |
{% extends "chrono/manager_agenda_view.html" %} |
|
2 |
{% load i18n gadjo %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'chrono-manager-agenda-settings' agenda.pk %}">{% trans 'Settings' %}</a> |
|
7 |
<a href="{% url 'chrono-manager-agenda-edit' agenda.pk %}">{{ title }}</a> |
|
8 |
{% endblock %} |
|
9 | ||
10 |
{% block appbar %} |
|
11 |
<h2>{% trans "Test reminder sending" %}</h2> |
|
12 |
{% endblock %} |
|
13 | ||
14 |
{% block content %} |
|
15 |
<form method="post" enctype="multipart/form-data"> |
|
16 |
{% csrf_token %} |
|
17 |
{{ form|with_template }} |
|
18 |
<div class="buttons"> |
|
19 |
<button class="submit-button">{% trans "Send" %}</button> |
|
20 |
<a class="cancel" href="{% url 'chrono-manager-agenda-settings' pk=agenda.id %}">{% trans 'Cancel' %}</a> |
|
21 |
</div> |
|
22 |
</form> |
|
23 |
{% endblock %} |
chrono/manager/urls.py | ||
---|---|---|
186 | 186 |
views.agenda_reminder_settings, |
187 | 187 |
name='chrono-manager-agenda-reminder-settings', |
188 | 188 |
), |
189 |
url( |
|
190 |
r'^agendas/(?P<pk>\d+)/reminder/test/$', |
|
191 |
views.agenda_reminder_test, |
|
192 |
name='chrono-manager-agenda-reminder-test', |
|
193 |
), |
|
189 | 194 |
url( |
190 | 195 |
r'^agendas/(?P<pk>\d+)/reminder/preview/(?P<type>(email|sms))/$', |
191 | 196 |
views.agenda_reminder_preview, |
chrono/manager/views.py | ||
---|---|---|
55 | 55 |
View, |
56 | 56 |
) |
57 | 57 | |
58 |
from chrono.agendas.management.commands.utils import send_reminder |
|
58 | 59 |
from chrono.agendas.models import ( |
59 | 60 |
AbsenceReason, |
60 | 61 |
AbsenceReasonGroup, |
... | ... | |
87 | 88 |
AgendaEditForm, |
88 | 89 |
AgendaNotificationsForm, |
89 | 90 |
AgendaReminderForm, |
91 |
AgendaReminderTestForm, |
|
90 | 92 |
AgendaResourceForm, |
91 | 93 |
AgendaRolesForm, |
92 | 94 |
AgendasExportForm, |
... | ... | |
1901 | 1903 |
agenda_reminder_settings = AgendaReminderSettingsView.as_view() |
1902 | 1904 | |
1903 | 1905 | |
1906 |
class AgendaReminderTestView(ManagedAgendaMixin, FormView): |
|
1907 |
template_name = 'chrono/manager_send_reminder_form.html' |
|
1908 |
form_class = AgendaReminderTestForm |
|
1909 | ||
1910 |
def get_form_kwargs(self): |
|
1911 |
kwargs = super(FormView, self).get_form_kwargs() |
|
1912 |
kwargs['agenda'] = self.agenda |
|
1913 |
return kwargs |
|
1914 | ||
1915 |
def form_valid(self, form): |
|
1916 |
booking = form.cleaned_data['booking'] |
|
1917 | ||
1918 |
if form.cleaned_data.get('phone_number'): |
|
1919 |
booking.user_phone_number = form.cleaned_data['phone_number'] |
|
1920 |
booking.extra_phone_numbers.clear() |
|
1921 | ||
1922 |
for msg_type in form.cleaned_data['msg_type']: |
|
1923 |
send_reminder(booking, msg_type) |
|
1924 |
return super().form_valid(form) |
|
1925 | ||
1926 | ||
1927 |
agenda_reminder_test = AgendaReminderTestView.as_view() |
|
1928 | ||
1929 | ||
1904 | 1930 |
class AgendaReminderPreviewView(ManagedAgendaMixin, TemplateView): |
1905 | 1931 |
template_name = 'chrono/manager_agenda_reminder_preview.html' |
1906 | 1932 |
tests/manager/test_all.py | ||
---|---|---|
1 | 1 |
import datetime |
2 |
import json |
|
2 | 3 |
from unittest import mock |
3 | 4 | |
4 | 5 |
import freezegun |
... | ... | |
2977 | 2978 |
assert '{{ booking.extra_data.xxx }}' in resp.text |
2978 | 2979 | |
2979 | 2980 | |
2981 |
def test_manager_reminders_test_sending(app, admin_user, freezer, mailoutbox, settings): |
|
2982 |
agenda = Agenda.objects.create(label='Events', kind='events') |
|
2983 |
Desk.objects.create(agenda=agenda, slug='_exceptions_holder') |
|
2984 |
AgendaReminderSettings.objects.create( |
|
2985 |
agenda=agenda, |
|
2986 |
days_before_email=1, |
|
2987 |
email_extra_info='Take your {{ booking.extra_data.document_type }}.', |
|
2988 |
days_before_sms=1, |
|
2989 |
sms_extra_info='Take {{ booking.extra_data.document_type }}.', |
|
2990 |
) |
|
2991 | ||
2992 |
login(app) |
|
2993 |
resp = app.get('/manage/agendas/%s/settings' % agenda.id) |
|
2994 |
resp = resp.click('Test reminder sending') |
|
2995 | ||
2996 |
assert 'phone_number' not in resp.form.fields |
|
2997 |
assert resp.form['msg_type'].attrs['type'] == 'hidden' |
|
2998 |
assert resp.form['booking'].options == [('', True, '---------')] |
|
2999 | ||
3000 |
# add bookings |
|
3001 |
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10) |
|
3002 |
freezer.move_to('2020-01-01 14:00') |
|
3003 |
Booking.objects.create(user_first_name='oldest', user_email='t@test.org', event=event) |
|
3004 |
freezer.move_to('2020-01-02 14:00') |
|
3005 |
for _ in range(10): |
|
3006 |
Booking.objects.create( |
|
3007 |
event=event, |
|
3008 |
user_first_name='Jon', |
|
3009 |
user_last_name='Doe', |
|
3010 |
user_email='t@test.org', |
|
3011 |
user_phone_number='+336123456780', |
|
3012 |
) |
|
3013 |
freezer.move_to('2020-01-03 14:00') |
|
3014 |
last_booking = Booking.objects.create( |
|
3015 |
event=event, |
|
3016 |
user_first_name='Jane', |
|
3017 |
user_last_name='Doe', |
|
3018 |
user_email='t@test.org', |
|
3019 |
extra_emails=['u@test.org'], |
|
3020 |
user_phone_number='+33122334455', |
|
3021 |
extra_phone_numbers=['+33122334456'], |
|
3022 |
extra_data={'document_type': 'receipt'}, |
|
3023 |
) |
|
3024 | ||
3025 |
resp = app.get('/manage/agendas/%s/reminder/test/' % agenda.id) |
|
3026 |
assert [x[2] for x in resp.form['booking'].options[:2]] == [ |
|
3027 |
'---------', |
|
3028 |
'Jane Doe, t@test.org, u@test.org (01/03/2020 3 p.m.)', |
|
3029 |
] |
|
3030 |
assert [x[2] for x in resp.form['booking'].options[2:]] == ['Jon Doe, t@test.org (01/02/2020 3 p.m.)'] * 9 |
|
3031 | ||
3032 |
resp.form['booking'] = last_booking.pk |
|
3033 |
resp = resp.form.submit().follow() |
|
3034 | ||
3035 |
assert len(mailoutbox) == 2 |
|
3036 |
assert {x.to[0] for x in mailoutbox} == {'t@test.org', 'u@test.org'} |
|
3037 |
assert all('Take your receipt' in mail.body for mail in mailoutbox) |
|
3038 |
mailoutbox.clear() |
|
3039 | ||
3040 |
settings.SMS_URL = 'https://passerelle.test.org/sms/send/' |
|
3041 |
settings.SMS_SENDER = 'EO' |
|
3042 | ||
3043 |
resp = app.get('/manage/agendas/%s/reminder/test/' % agenda.id) |
|
3044 |
assert [x[2] for x in resp.form['booking'].options[:2]] == [ |
|
3045 |
'---------', |
|
3046 |
'Jane Doe, t@test.org, u@test.org, +33122334455, +33122334456 (01/03/2020 3 p.m.)', |
|
3047 |
] |
|
3048 |
assert [x[2] for x in resp.form['booking'].options[2:]] == [ |
|
3049 |
'Jon Doe, t@test.org, +336123456780 (01/02/2020 3 p.m.)' |
|
3050 |
] * 9 |
|
3051 | ||
3052 |
resp.form['booking'] = last_booking.pk |
|
3053 |
resp.form['msg_type'] = ['email', 'sms'] |
|
3054 |
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send: |
|
3055 |
mock_send.return_value = mock.Mock(status_code=200) |
|
3056 |
resp = resp.form.submit().follow() |
|
3057 | ||
3058 |
body = json.loads(mock_send.call_args[0][0].body.decode()) |
|
3059 |
assert 'Take receipt' in body['message'] |
|
3060 |
assert set(body['to']) == {'+33122334455', '+33122334456'} |
|
3061 | ||
3062 |
assert len(mailoutbox) == 2 |
|
3063 |
mailoutbox.clear() |
|
3064 | ||
3065 |
resp = app.get('/manage/agendas/%s/reminder/test/' % agenda.id) |
|
3066 |
resp.form['booking'] = last_booking.pk |
|
3067 |
resp.form['msg_type'] = ['sms'] |
|
3068 |
resp.form['phone_number'] = '+33333333333' |
|
3069 |
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send: |
|
3070 |
mock_send.return_value = mock.Mock(status_code=200) |
|
3071 |
resp = resp.form.submit().follow() |
|
3072 | ||
3073 |
body = json.loads(mock_send.call_args[0][0].body.decode()) |
|
3074 |
assert body['to'] == ['+33333333333'] |
|
3075 |
assert len(mailoutbox) == 0 |
|
3076 | ||
3077 | ||
2980 | 3078 |
def test_manager_agenda_roles(app, admin_user, manager_user): |
2981 | 3079 |
agenda = Agenda.objects.create(label='Events', kind='events') |
2982 | 3080 |
Desk.objects.create(agenda=agenda, slug='_exceptions_holder') |
2983 |
- |