From a79768d3811671b565fc32ee9fcccb0b5318515d Mon Sep 17 00:00:00 2001
From: Valentin Deniaud
Date: Tue, 15 Feb 2022 10:52:24 +0100
Subject: [PATCH 3/3] manager: test booking reminders sending (#61234)
---
chrono/agendas/management/commands/utils.py | 17 +---
chrono/agendas/models.py | 14 +++
chrono/manager/forms.py | 47 ++++++++-
.../chrono/manager_agenda_settings.html | 5 +
.../chrono/manager_send_reminder_form.html | 23 +++++
chrono/manager/urls.py | 5 +
chrono/manager/views.py | 26 +++++
tests/manager/test_all.py | 98 +++++++++++++++++++
8 files changed, 221 insertions(+), 14 deletions(-)
create mode 100644 chrono/manager/templates/chrono/manager_send_reminder_form.html
diff --git a/chrono/agendas/management/commands/utils.py b/chrono/agendas/management/commands/utils.py
index 4f16143c..51b0be11 100644
--- a/chrono/agendas/management/commands/utils.py
+++ b/chrono/agendas/management/commands/utils.py
@@ -32,20 +32,11 @@ def send_reminder(booking, msg_type):
except (VariableDoesNotExist, TemplateSyntaxError):
pass
- if msg_type == 'email':
- emails = set(booking.extra_emails)
- if booking.user_email:
- emails.add(booking.user_email)
-
- for email in emails:
+ if msg_type == 'email' and booking.emails:
+ for email in booking.emails:
send_email_reminder(email, booking, kind, ctx)
- elif msg_type == 'sms':
- phone_numbers = set(booking.extra_phone_numbers)
- if booking.user_phone_number:
- phone_numbers.add(booking.user_phone_number)
-
- if phone_numbers:
- send_sms_reminder(list(phone_numbers), booking, kind, ctx)
+ elif msg_type == 'sms' and booking.phone_numbers:
+ send_sms_reminder(booking.phone_numbers, booking, kind, ctx)
def send_email_reminder(email, booking, kind, ctx):
diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index f3875f78..3320c167 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -1897,6 +1897,20 @@ class Booking(models.Model):
def user_name(self):
return ('%s %s' % (self.user_first_name, self.user_last_name)).strip()
+ @cached_property
+ def emails(self):
+ emails = set(self.extra_emails)
+ if self.user_email:
+ emails.add(self.user_email)
+ return list(emails)
+
+ @cached_property
+ def phone_numbers(self):
+ phone_numbers = set(self.extra_phone_numbers)
+ if self.user_phone_number:
+ phone_numbers.add(self.user_phone_number)
+ return list(phone_numbers)
+
def cancel(self, trigger_callback=False):
timestamp = now()
with transaction.atomic():
diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index 916d3b4e..3e4b04f1 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -26,8 +26,9 @@ from django.core.exceptions import FieldDoesNotExist
from django.db import transaction
from django.forms import ValidationError
from django.utils.encoding import force_text
+from django.utils.formats import date_format
from django.utils.six import StringIO
-from django.utils.timezone import make_aware, now
+from django.utils.timezone import localtime, make_aware, now
from django.utils.translation import ugettext_lazy as _
from chrono.agendas.models import (
@@ -926,6 +927,50 @@ class AgendaReminderForm(forms.ModelForm):
del self.fields['sms_extra_info']
+class BookingChoiceField(forms.ModelChoiceField):
+ def label_from_instance(self, obj):
+ name = obj.user_name or obj.label or _('Anonymous')
+ date = date_format(localtime(obj.creation_datetime), 'SHORT_DATETIME_FORMAT')
+ emails = ', '.join(sorted(obj.emails)) or _('no email')
+ phone_numbers = ', '.join(sorted(obj.phone_numbers)) or _('no phone number')
+
+ if settings.SMS_URL:
+ return '%s, %s, %s (%s)' % (name, emails, phone_numbers, date)
+ else:
+ return '%s, %s (%s)' % (name, emails, date)
+
+
+class AgendaReminderTestForm(forms.Form):
+ booking = BookingChoiceField(
+ label=_('Booking'),
+ queryset=Booking.objects.none(),
+ help_text=_('Only the last ten bookings are displayed.'),
+ )
+ msg_type = forms.MultipleChoiceField(
+ label=_('Send via'),
+ choices=(('email', _('Email')), ('sms', _('SMS'))),
+ widget=forms.CheckboxSelectMultiple(),
+ )
+ phone_number = forms.CharField(
+ label=_('Phone number'),
+ max_length=16,
+ help_text=_('This will override phone number specified on booking creation, if any.'),
+ required=False,
+ )
+
+ def __init__(self, *args, **kwargs):
+ agenda = kwargs.pop('agenda')
+ super().__init__(*args, **kwargs)
+ self.fields['booking'].queryset = Booking.objects.filter(
+ pk__in=Booking.objects.filter(event__agenda=agenda).order_by('-creation_datetime')[:10]
+ ).order_by('-creation_datetime')
+
+ if not settings.SMS_URL:
+ self.fields['msg_type'].initial = ['email']
+ self.fields['msg_type'].widget = forms.MultipleHiddenInput()
+ del self.fields['phone_number']
+
+
class AgendasExportForm(forms.Form):
agendas = forms.BooleanField(label=_('Agendas'), required=False, initial=True)
unavailability_calendars = forms.BooleanField(
diff --git a/chrono/manager/templates/chrono/manager_agenda_settings.html b/chrono/manager/templates/chrono/manager_agenda_settings.html
index 7bd33350..18c4363e 100644
--- a/chrono/manager/templates/chrono/manager_agenda_settings.html
+++ b/chrono/manager/templates/chrono/manager_agenda_settings.html
@@ -57,6 +57,11 @@
{% trans "Preview SMS" %}
{% endif %}
+{% if agenda.reminder_settings.days_before_email or agenda.reminder_settings.days_before_sms %}
+
+{% trans "Test reminder sending" %}
+
+{% endif %}
{% endblock %}
diff --git a/chrono/manager/templates/chrono/manager_send_reminder_form.html b/chrono/manager/templates/chrono/manager_send_reminder_form.html
new file mode 100644
index 00000000..e0612f2c
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_send_reminder_form.html
@@ -0,0 +1,23 @@
+{% extends "chrono/manager_agenda_view.html" %}
+{% load i18n gadjo %}
+
+{% block breadcrumb %}
+{{ block.super }}
+{% trans 'Settings' %}
+{{ title }}
+{% endblock %}
+
+{% block appbar %}
+{% trans "Test reminder sending" %}
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py
index 83cbb7df..e0e79979 100644
--- a/chrono/manager/urls.py
+++ b/chrono/manager/urls.py
@@ -186,6 +186,11 @@ urlpatterns = [
views.agenda_reminder_settings,
name='chrono-manager-agenda-reminder-settings',
),
+ url(
+ r'^agendas/(?P\d+)/reminder/test/$',
+ views.agenda_reminder_test,
+ name='chrono-manager-agenda-reminder-test',
+ ),
url(
r'^agendas/(?P\d+)/reminder/preview/(?P(email|sms))/$',
views.agenda_reminder_preview,
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index 497783d6..ef2a4d74 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -55,6 +55,7 @@ from django.views.generic import (
View,
)
+from chrono.agendas.management.commands.utils import send_reminder
from chrono.agendas.models import (
AbsenceReason,
AbsenceReasonGroup,
@@ -87,6 +88,7 @@ from .forms import (
AgendaEditForm,
AgendaNotificationsForm,
AgendaReminderForm,
+ AgendaReminderTestForm,
AgendaResourceForm,
AgendaRolesForm,
AgendasExportForm,
@@ -1901,6 +1903,30 @@ class AgendaReminderSettingsView(AgendaEditView):
agenda_reminder_settings = AgendaReminderSettingsView.as_view()
+class AgendaReminderTestView(ManagedAgendaMixin, FormView):
+ template_name = 'chrono/manager_send_reminder_form.html'
+ form_class = AgendaReminderTestForm
+
+ def get_form_kwargs(self):
+ kwargs = super(FormView, self).get_form_kwargs()
+ kwargs['agenda'] = self.agenda
+ return kwargs
+
+ def form_valid(self, form):
+ booking = form.cleaned_data['booking']
+
+ if form.cleaned_data.get('phone_number'):
+ booking.user_phone_number = form.cleaned_data['phone_number']
+ booking.extra_phone_numbers.clear()
+
+ for msg_type in form.cleaned_data['msg_type']:
+ send_reminder(booking, msg_type)
+ return super().form_valid(form)
+
+
+agenda_reminder_test = AgendaReminderTestView.as_view()
+
+
class AgendaReminderPreviewView(ManagedAgendaMixin, TemplateView):
template_name = 'chrono/manager_agenda_reminder_preview.html'
diff --git a/tests/manager/test_all.py b/tests/manager/test_all.py
index ba732597..2666064c 100644
--- a/tests/manager/test_all.py
+++ b/tests/manager/test_all.py
@@ -1,4 +1,5 @@
import datetime
+import json
from unittest import mock
import freezegun
@@ -2977,6 +2978,103 @@ def test_manager_reminders_preview(app, admin_user):
assert '{{ booking.extra_data.xxx }}' in resp.text
+def test_manager_reminders_test_sending(app, admin_user, freezer, mailoutbox, settings):
+ agenda = Agenda.objects.create(label='Events', kind='events')
+ Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
+ AgendaReminderSettings.objects.create(
+ agenda=agenda,
+ days_before_email=1,
+ email_extra_info='Take your {{ booking.extra_data.document_type }}.',
+ days_before_sms=1,
+ sms_extra_info='Take {{ booking.extra_data.document_type }}.',
+ )
+
+ login(app)
+ resp = app.get('/manage/agendas/%s/settings' % agenda.id)
+ resp = resp.click('Test reminder sending')
+
+ assert 'phone_number' not in resp.form.fields
+ assert resp.form['msg_type'].attrs['type'] == 'hidden'
+ assert resp.form['booking'].options == [('', True, '---------')]
+
+ # add bookings
+ event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
+ freezer.move_to('2020-01-01 14:00')
+ Booking.objects.create(user_first_name='oldest', user_email='t@test.org', event=event)
+ freezer.move_to('2020-01-02 14:00')
+ for _ in range(10):
+ Booking.objects.create(
+ event=event,
+ user_first_name='Jon',
+ user_last_name='Doe',
+ user_email='t@test.org',
+ user_phone_number='+336123456780',
+ )
+ freezer.move_to('2020-01-03 14:00')
+ last_booking = Booking.objects.create(
+ event=event,
+ user_first_name='Jane',
+ user_last_name='Doe',
+ user_email='t@test.org',
+ extra_emails=['u@test.org'],
+ user_phone_number='+33122334455',
+ extra_phone_numbers=['+33122334456'],
+ extra_data={'document_type': 'receipt'},
+ )
+
+ resp = app.get('/manage/agendas/%s/reminder/test/' % agenda.id)
+ assert [x[2] for x in resp.form['booking'].options[:2]] == [
+ '---------',
+ 'Jane Doe, t@test.org, u@test.org (01/03/2020 3 p.m.)',
+ ]
+ assert [x[2] for x in resp.form['booking'].options[2:]] == ['Jon Doe, t@test.org (01/02/2020 3 p.m.)'] * 9
+
+ resp.form['booking'] = last_booking.pk
+ resp = resp.form.submit().follow()
+
+ assert len(mailoutbox) == 2
+ assert {x.to[0] for x in mailoutbox} == {'t@test.org', 'u@test.org'}
+ assert all('Take your receipt' in mail.body for mail in mailoutbox)
+ mailoutbox.clear()
+
+ settings.SMS_URL = 'https://passerelle.test.org/sms/send/'
+ settings.SMS_SENDER = 'EO'
+
+ resp = app.get('/manage/agendas/%s/reminder/test/' % agenda.id)
+ assert [x[2] for x in resp.form['booking'].options[:2]] == [
+ '---------',
+ 'Jane Doe, t@test.org, u@test.org, +33122334455, +33122334456 (01/03/2020 3 p.m.)',
+ ]
+ assert [x[2] for x in resp.form['booking'].options[2:]] == [
+ 'Jon Doe, t@test.org, +336123456780 (01/02/2020 3 p.m.)'
+ ] * 9
+
+ resp.form['booking'] = last_booking.pk
+ resp.form['msg_type'] = ['email', 'sms']
+ with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
+ mock_send.return_value = mock.Mock(status_code=200)
+ resp = resp.form.submit().follow()
+
+ body = json.loads(mock_send.call_args[0][0].body.decode())
+ assert 'Take receipt' in body['message']
+ assert set(body['to']) == {'+33122334455', '+33122334456'}
+
+ assert len(mailoutbox) == 2
+ mailoutbox.clear()
+
+ resp = app.get('/manage/agendas/%s/reminder/test/' % agenda.id)
+ resp.form['booking'] = last_booking.pk
+ resp.form['msg_type'] = ['sms']
+ resp.form['phone_number'] = '+33333333333'
+ with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
+ mock_send.return_value = mock.Mock(status_code=200)
+ resp = resp.form.submit().follow()
+
+ body = json.loads(mock_send.call_args[0][0].body.decode())
+ assert body['to'] == ['+33333333333']
+ assert len(mailoutbox) == 0
+
+
def test_manager_agenda_roles(app, admin_user, manager_user):
agenda = Agenda.objects.create(label='Events', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
--
2.30.2