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 %} +
+ {% csrf_token %} + {{ form|with_template }} +
+ + {% trans 'Cancel' %} +
+
+{% 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