Projet

Général

Profil

0001-reminders-allow-template-syntax-in-message-extra-inf.patch

Valentin Deniaud, 15 février 2022 14:05

Télécharger (13,1 ko)

Voir les différences:

Subject: [PATCH 1/3] reminders: allow template syntax in message extra info
 (#61234)

 .../commands/send_booking_reminders.py        | 11 +++-
 .../migrations/0062_auto_20200915_1401.py     | 20 ++++++-
 chrono/agendas/models.py                      | 29 +++++++++-
 .../agendas/events_reminder_body.html         |  2 +-
 .../agendas/meetings_reminder_body.html       |  2 +-
 tests/manager/test_all.py                     | 39 ++++++++++++++
 tests/test_agendas.py                         | 54 +++++++++++++++++++
 7 files changed, 149 insertions(+), 8 deletions(-)
chrono/agendas/management/commands/send_booking_reminders.py
23 23
from django.core.management.base import BaseCommand
24 24
from django.db.models import F
25 25
from django.db.transaction import atomic
26
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist
26 27
from django.template.loader import render_to_string
27 28
from django.utils import timezone, translation
28 29
from django.utils.translation import ugettext_lazy as _
......
88 89
            'booking': booking,
89 90
            'in_x_days': _('tomorrow') if days == 1 else _('in %s days') % days,
90 91
            'date': booking.event.start_datetime,
91
            'email_extra_info': agenda.reminder_settings.email_extra_info,
92
            'sms_extra_info': agenda.reminder_settings.sms_extra_info,
93 92
        }
94 93
        ctx.update(settings.TEMPLATE_VARS)
95 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

  
96 103
        if msg_type == 'email':
97 104
            emails = set(booking.extra_emails)
98 105
            if booking.user_email:
chrono/agendas/migrations/0062_auto_20200915_1401.py
3 3
import django.db.models.deletion
4 4
from django.db import migrations, models
5 5

  
6
import chrono
7

  
6 8

  
7 9
class Migration(migrations.Migration):
8 10

  
......
41 43
                    'email_extra_info',
42 44
                    models.TextField(
43 45
                        blank=True,
44
                        help_text='Basic information such as event name, time and date are already included.',
46
                        validators=[chrono.agendas.models.booking_template_validator],
47
                        help_text=(
48
                            'Basic information such as event name, time and date are already included. '
49
                            'Booking object can be accessed using standard template syntax. '
50
                            'This allows to access agenda name via {{ booking.event.agenda.label }}, '
51
                            'meeting type name via {{ booking.event.meeting_type.label }}, or any extra '
52
                            'parameter passed on booking creation via {{ booking.extra_data.xxx }}.'
53
                        ),
45 54
                        verbose_name='Additional text to include in emails',
46 55
                    ),
47 56
                ),
......
50 59
                    'sms_extra_info',
51 60
                    models.TextField(
52 61
                        blank=True,
53
                        help_text='Basic information such as event name, time and date are already included.',
62
                        validators=[chrono.agendas.models.booking_template_validator],
63
                        help_text=(
64
                            'Basic information such as event name, time and date are already included. '
65
                            'Booking object can be accessed using standard template syntax. '
66
                            'This allows to access agenda name via {{ booking.event.agenda.label }}, '
67
                            'meeting type name via {{ booking.event.meeting_type.label }}, or any extra '
68
                            'parameter passed on booking creation via {{ booking.extra_data.xxx }}.'
69
                        ),
54 70
                        verbose_name='Additional text to include in SMS',
55 71
                    ),
56 72
                ),
chrono/agendas/models.py
154 154
        raise ValidationError(_('syntax error: %s') % e)
155 155

  
156 156

  
157
def booking_template_validator(value):
158
    example_event = Event(
159
        start_datetime=now(),
160
        publication_datetime=now(),
161
        recurrence_end_date=now().date(),
162
        places=1,
163
        duration=1,
164
    )
165
    example_booking = Booking(event=example_event)
166
    try:
167
        Template(value).render(Context({'booking': example_booking}))
168
    except TemplateSyntaxError as e:
169
        raise ValidationError(_('syntax error: %s') % e)
170
    except VariableDoesNotExist:
171
        pass
172

  
173

  
157 174
class ICSError(Exception):
158 175
    pass
159 176

  
......
2830 2847
    email_extra_info = models.TextField(
2831 2848
        blank=True,
2832 2849
        verbose_name=_('Additional text to include in emails'),
2833
        help_text=_('Basic information such as event name, time and date are already included.'),
2850
        validators=[booking_template_validator],
2851
        help_text=_(
2852
            'Basic information such as event name, time and date are already included. '
2853
            'Booking object can be accessed using standard template syntax. '
2854
            'This allows to access agenda name via {{ booking.event.agenda.label }}, '
2855
            'meeting type name via {{ booking.event.meeting_type.label }}, or any extra '
2856
            'parameter passed on booking creation via {{ booking.extra_data.xxx }}.'
2857
        ),
2834 2858
    )
2835 2859
    days_before_sms = models.IntegerField(
2836 2860
        null=True,
......
2845 2869
    sms_extra_info = models.TextField(
2846 2870
        blank=True,
2847 2871
        verbose_name=_('Additional text to include in SMS'),
2848
        help_text=_('Basic information such as event name, time and date are already included.'),
2872
        validators=[booking_template_validator],
2873
        help_text=email_extra_info.help_text,
2849 2874
    )
2850 2875

  
2851 2876
    def display_info(self):
chrono/agendas/templates/agendas/events_reminder_body.html
11 11
</p>
12 12

  
13 13
{% if email_extra_info %}
14
<p>{{ email_extra_info }}</p>
14
<p>{{ email_extra_info|force_escape|linebreaks }}</p>
15 15
{% endif %}
16 16

  
17 17
{% if booking.event.description %}
chrono/agendas/templates/agendas/meetings_reminder_body.html
17 17
</p>
18 18

  
19 19
{% if email_extra_info %}
20
<p>{{ email_extra_info }}</p>
20
<p>{{ email_extra_info|force_escape|linebreaks }}</p>
21 21
{% endif %}
22 22

  
23 23
{% if booking.form_url %}
tests/manager/test_all.py
2899 2899
    assert not 'Booking reminders' in resp.text
2900 2900

  
2901 2901

  
2902
@override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO', TIME_ZONE='UTC')
2903
@pytest.mark.parametrize('extra_info_field', ('sms_extra_info', 'email_extra_info'))
2904
def test_manager_reminders_templated_extra_info(app, admin_user, extra_info_field):
2905
    agenda = Agenda.objects.create(label='Events', kind='events')
2906
    Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
2907

  
2908
    login(app)
2909
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
2910
    resp = resp.click('Configure', href='reminder')
2911

  
2912
    extra_info = 'test {{ booking.extra_data.xxx }} {{ booking.event.label|default:booking.extra_data.yyy }}'
2913
    resp.form[extra_info_field] = extra_info
2914
    resp = resp.form.submit().follow()
2915
    assert getattr(agenda.reminder_settings, extra_info_field) == extra_info
2916

  
2917
    invalid_templates = [
2918
        '{{ syntax error }}',
2919
        '{{ booking.label|invalidfilter }}',
2920
    ]
2921
    for template in invalid_templates:
2922
        resp = app.get('/manage/agendas/%s/reminder' % agenda.id)
2923
        resp.form[extra_info_field] = template
2924
        resp = resp.form.submit()
2925
        assert 'syntax error' in resp.text
2926

  
2927

  
2902 2928
def test_manager_reminders_preview(app, admin_user):
2903 2929
    agenda = Agenda.objects.create(label='Events', kind='events')
2904 2930
    Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
......
2937 2963
        in resp.text
2938 2964
    )
2939 2965

  
2966
    # templates in extra info should not be interpreted
2967
    agenda.reminder_settings.sms_extra_info = '{{ booking.extra_data.xxx }}'
2968
    agenda.reminder_settings.email_extra_info = '{{ booking.extra_data.xxx }}'
2969
    agenda.reminder_settings.save()
2970

  
2971
    resp = resp.click('Return to settings')
2972
    resp = resp.click('Preview SMS')
2973
    assert '{{ booking.extra_data.xxx }}' in resp.text
2974

  
2975
    resp = resp.click('Return to settings')
2976
    resp = resp.click('Preview email')
2977
    assert '{{ booking.extra_data.xxx }}' in resp.text
2978

  
2940 2979

  
2941 2980
def test_manager_agenda_roles(app, admin_user, manager_user):
2942 2981
    agenda = Agenda.objects.create(label='Events', kind='events')
tests/test_agendas.py
1925 1925
    )
1926 1926

  
1927 1927

  
1928
@override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO', TIME_ZONE='UTC')
1929
def test_agenda_reminders_templated_content(mailoutbox, freezer):
1930
    freezer.move_to('2020-01-01 14:00')
1931
    agenda = Agenda.objects.create(label='Main Center', kind='events')
1932
    AgendaReminderSettings.objects.create(
1933
        agenda=agenda,
1934
        days_before_email=1,
1935
        days_before_sms=1,
1936
        email_extra_info='Go to {{ booking.event.agenda.label }}.\nTake your {{ booking.extra_data.document_type }}.',
1937
        sms_extra_info='Take your {{ booking.extra_data.document_type }}.',
1938
    )
1939
    start_datetime = now() + datetime.timedelta(days=2)
1940
    event = Event.objects.create(agenda=agenda, start_datetime=start_datetime, places=10, label='Pool party')
1941

  
1942
    Booking.objects.create(
1943
        event=event,
1944
        user_email='t@test.org',
1945
        user_phone_number='+336123456789',
1946
        extra_data={'document_type': '"receipt"'},
1947
    )
1948

  
1949
    freezer.move_to('2020-01-02 15:00')
1950
    with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
1951
        mock_response = mock.Mock(status_code=200)
1952
        mock_send.return_value = mock_response
1953
        call_command('send_booking_reminders')
1954

  
1955
    mail = mailoutbox[0]
1956
    assert 'Go to Main Center.\nTake your "receipt".' in mail.body
1957
    assert '<p>Go to Main Center.<br>Take your &quot;receipt&quot;.</p>' in mail.alternatives[0][0]
1958

  
1959
    body = json.loads(mock_send.call_args[0][0].body.decode())
1960
    assert 'Take your "receipt".' in body['message']
1961

  
1962
    # in case of invalid template, send anyway
1963
    freezer.move_to('2020-01-01 14:00')
1964
    Booking.objects.create(event=event, user_email='t@test.org', user_phone_number='+336123456789')
1965
    agenda.reminder_settings.email_extra_info = 'Take your {{ syntax error }}'
1966
    agenda.reminder_settings.sms_extra_info = 'Take your {{ syntax error }}'
1967
    agenda.reminder_settings.save()
1968

  
1969
    freezer.move_to('2020-01-02 15:00')
1970
    with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
1971
        mock_response = mock.Mock(status_code=200)
1972
        mock_send.return_value = mock_response
1973
        call_command('send_booking_reminders')
1974

  
1975
    assert len(mailoutbox) == 2
1976
    assert 'Take your' not in mailoutbox[1].body
1977

  
1978
    body = json.loads(mock_send.call_args[0][0].body.decode())
1979
    assert 'Take your' not in body['message']
1980

  
1981

  
1928 1982
@override_settings(TIME_ZONE='UTC')
1929 1983
def test_agenda_reminders_meetings(mailoutbox, freezer):
1930 1984
    freezer.move_to('2020-01-01 11:00')
1931
-