From 9c1cafb7e8d9263ae341207e60a4b90cd827c6b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 24 May 2022 10:18:13 +0200
Subject: [PATCH 1/2] manager: user_block used also in event detail page
 (#63915)

---
 .../0086_booking_user_block_template.py       |  1 -
 chrono/agendas/models.py                      | 10 +--
 chrono/manager/forms.py                       | 20 ++++--
 .../chrono/manager_event_detail_fragment.html |  4 +-
 .../manager_events_agenda_settings.html       | 27 ++++++--
 chrono/manager/urls.py                        |  5 ++
 chrono/manager/views.py                       | 15 ++++
 tests/manager/test_all.py                     | 68 +++++++++++--------
 tests/manager/test_event.py                   | 47 ++++++++++---
 9 files changed, 141 insertions(+), 56 deletions(-)

diff --git a/chrono/agendas/migrations/0086_booking_user_block_template.py b/chrono/agendas/migrations/0086_booking_user_block_template.py
index 7c350125..bbc35cd9 100644
--- a/chrono/agendas/migrations/0086_booking_user_block_template.py
+++ b/chrono/agendas/migrations/0086_booking_user_block_template.py
@@ -15,7 +15,6 @@ class Migration(migrations.Migration):
             name='booking_user_block_template',
             field=models.TextField(
                 blank=True,
-                help_text='Displayed for each booking in event check page',
                 verbose_name='User block template',
                 validators=[chrono.agendas.models.django_template_validator],
             ),
diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 016dd4aa..c2770973 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -45,6 +45,7 @@ from django.utils.dates import WEEKDAYS
 from django.utils.encoding import force_text
 from django.utils.formats import date_format
 from django.utils.functional import cached_property
+from django.utils.html import escape
 from django.utils.module_loading import import_string
 from django.utils.safestring import mark_safe
 from django.utils.text import slugify
@@ -226,7 +227,6 @@ class Agenda(models.Model):
     )
     booking_user_block_template = models.TextField(
         _('User block template'),
-        help_text=_('Displayed for each booking in event check page'),
         blank=True,
         validators=[django_template_validator],
     )
@@ -2030,14 +2030,14 @@ class Booking(models.Model):
             self.event.set_is_checked()
 
     def get_user_block(self):
-        template_vars = Context(settings.TEMPLATE_VARS)
+        template_vars = Context(settings.TEMPLATE_VARS, autoescape=False)
         template_vars.update(
             {
                 'booking': self,
             }
         )
         try:
-            return Template(self.event.agenda.get_booking_user_block_template()).render(template_vars)
+            return escape(Template(self.event.agenda.get_booking_user_block_template()).render(template_vars))
         except (VariableDoesNotExist, TemplateSyntaxError):
             return
 
@@ -3181,14 +3181,14 @@ class Subscription(models.Model):
         return _('Subscription')
 
     def get_user_block(self):
-        template_vars = Context(settings.TEMPLATE_VARS)
+        template_vars = Context(settings.TEMPLATE_VARS, autoescape=False)
         template_vars.update(
             {
                 'booking': self,
             }
         )
         try:
-            return Template(self.agenda.get_booking_user_block_template()).render(template_vars)
+            return escape(Template(self.agenda.get_booking_user_block_template()).render(template_vars))
         except (VariableDoesNotExist, TemplateSyntaxError):
             return
 
diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index c1b7669d..cc6bc86e 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -133,7 +133,6 @@ class AgendaEditForm(forms.ModelForm):
             'anonymize_delay',
             'default_view',
             'booking_form_url',
-            'event_display_template',
             'events_type',
         ]
 
@@ -141,7 +140,6 @@ class AgendaEditForm(forms.ModelForm):
         super().__init__(*args, **kwargs)
         if kwargs['instance'].kind != 'events':
             del self.fields['booking_form_url']
-            del self.fields['event_display_template']
             del self.fields['events_type']
             self.fields['default_view'].choices = [
                 (k, v) for k, v in self.fields['default_view'].choices if k != 'open_events'
@@ -1305,17 +1303,31 @@ class EventCancelForm(forms.ModelForm):
         fields = []
 
 
+class AgendaDisplaySettingsForm(forms.ModelForm):
+    class Meta:
+        model = Agenda
+        fields = [
+            'event_display_template',
+            'booking_user_block_template',
+        ]
+        widgets = {'booking_user_block_template': forms.Textarea(attrs={'rows': 3})}
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['booking_user_block_template'].help_text = (
+            _('Displayed for each booking in event page and check page'),
+        )
+
+
 class AgendaBookingCheckSettingsForm(forms.ModelForm):
     class Meta:
         model = Agenda
         fields = [
             'check_type_group',
             'booking_check_filters',
-            'booking_user_block_template',
             'mark_event_checked_auto',
             'disable_check_update',
         ]
-        widgets = {'booking_user_block_template': forms.Textarea(attrs={'rows': 3})}
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
diff --git a/chrono/manager/templates/chrono/manager_event_detail_fragment.html b/chrono/manager/templates/chrono/manager_event_detail_fragment.html
index 5803b2cb..75294c69 100644
--- a/chrono/manager/templates/chrono/manager_event_detail_fragment.html
+++ b/chrono/manager/templates/chrono/manager_event_detail_fragment.html
@@ -29,7 +29,7 @@
   <ul class="objects-list single-links">
     {% for booking in booked %}
     <li>
-    <a {% if booking.get_backoffice_url %}href="{{ booking.get_backoffice_url }}"{% endif %}>{{ booking.events_display }}</a>
+    <a {% if booking.get_backoffice_url %}href="{{ booking.get_backoffice_url }}"{% endif %}>{{ booking.get_user_block }}, {{ booking.creation_datetime|date:"DATETIME_FORMAT" }}</a>
     {% if not booking.primary_booking %}
     <a rel="popup" class="delete" href="{% url 'chrono-manager-booking-cancel' pk=agenda.id booking_pk=booking.id %}?next={{ request.path }}">{% trans "Cancel" %}</a>
     {% else %}
@@ -53,7 +53,7 @@
 <div>
   <ul class="objects-list single-links">
     {% for booking in waiting %}
-    <li><a {% if booking.get_backoffice_url %}href="{{ booking.get_backoffice_url }}"{% endif %}>{{ booking.events_display }}</a></li>
+    <li><a {% if booking.get_backoffice_url %}href="{{ booking.get_backoffice_url }}"{% endif %}>{{ booking.get_user_block }}, {{ booking.creation_datetime|date:"DATETIME_FORMAT" }}</a></li>
     {% endfor %}
   </ul>
 </div>
diff --git a/chrono/manager/templates/chrono/manager_events_agenda_settings.html b/chrono/manager/templates/chrono/manager_events_agenda_settings.html
index a1ef50ad..d1b75a48 100644
--- a/chrono/manager/templates/chrono/manager_events_agenda_settings.html
+++ b/chrono/manager/templates/chrono/manager_events_agenda_settings.html
@@ -33,6 +33,28 @@
 </div>
 </div>
 
+<div class="section">
+<h3>{% trans "Display options" %}
+    <a rel="popup" class="button" href="{% url 'chrono-manager-agenda-display-settings' pk=object.pk %}">{% trans 'Configure' %}</a>
+</h3>
+<div>
+    <ul>
+        <li>
+            {% if agenda.event_display_template %}
+                {% trans "Event display template:" %}
+                <pre>{{ agenda.event_display_template }}</pre>
+            {% else %}
+            {% trans "No event display template configured for this agenda." %}
+            {% endif %}
+        </li>
+        <li>
+            {% trans "Booking display template:" %}
+            <pre>{{ agenda.get_booking_user_block_template }}</pre>
+        </li>
+    </ul>
+</div>
+</div>
+
 <div class="section">
 <h3>{% trans "Booking check options" %}
     <a rel="popup" class="button" href="{% url 'chrono-manager-agenda-booking-check-settings' pk=object.pk %}">{% trans 'Configure' %}</a>
@@ -82,11 +104,6 @@
         {% endif %}
     {% endwith %}
 
-        <li>
-            {% trans "User block template:" %}
-            <pre>{{ agenda.get_booking_user_block_template }}</pre>
-        </li>
-
         <li>{% trans "Automatically mark event as checked when all bookings have been checked:" %} {{ agenda.mark_event_checked_auto|yesno }}</li>
         <li>{% trans "Prevent the check of bookings when event was marked as checked:" %} {{ agenda.disable_check_update|yesno }}</li>
     </ul>
diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py
index 24dff30e..4109aed2 100644
--- a/chrono/manager/urls.py
+++ b/chrono/manager/urls.py
@@ -166,6 +166,11 @@ urlpatterns = [
         name='chrono-manager-agenda-booking-delays',
     ),
     url(r'^agendas/(?P<pk>\d+)/roles$', views.agenda_roles, name='chrono-manager-agenda-roles'),
+    url(
+        r'^agendas/(?P<pk>\d+)/display-options$',
+        views.agenda_display_settings,
+        name='chrono-manager-agenda-display-settings',
+    ),
     url(
         r'^agendas/(?P<pk>\d+)/check-options$',
         views.agenda_booking_check_settings,
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index ebe61881..5344ef41 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -94,6 +94,7 @@ from .forms import (
     AgendaAddForm,
     AgendaBookingCheckSettingsForm,
     AgendaBookingDelaysForm,
+    AgendaDisplaySettingsForm,
     AgendaDuplicateForm,
     AgendaEditForm,
     AgendaNotificationsForm,
@@ -1241,6 +1242,20 @@ class AgendaRolesView(AgendaEditView):
 agenda_roles = AgendaRolesView.as_view()
 
 
+class AgendaDisplaySettingsView(AgendaEditView):
+    form_class = AgendaDisplaySettingsForm
+    title = _("Configure display options")
+
+    def set_agenda(self, **kwargs):
+        self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
+
+    def get_initial(self):
+        return {'booking_user_block_template': self.agenda.get_booking_user_block_template()}
+
+
+agenda_display_settings = AgendaDisplaySettingsView.as_view()
+
+
 class AgendaBookingCheckSettingsView(AgendaEditView):
     form_class = AgendaBookingCheckSettingsForm
     title = _("Configure booking check options")
diff --git a/tests/manager/test_all.py b/tests/manager/test_all.py
index ad1cacda..d87fa424 100644
--- a/tests/manager/test_all.py
+++ b/tests/manager/test_all.py
@@ -387,7 +387,6 @@ def test_options_agenda(app, admin_user):
     assert resp.context['form'].initial['default_view'] == 'month'
     assert 'open_events' in [k for k, v in resp.context['form'].fields['default_view'].choices]
     assert 'booking_form_url' in resp.context['form'].fields
-    assert 'event_display_template' in resp.context['form'].fields
     resp = resp.form.submit()
     assert resp.location.endswith('/manage/agendas/%s/settings' % agenda_events.pk)
     resp = resp.follow()
@@ -401,7 +400,6 @@ def test_options_agenda(app, admin_user):
     assert 'default_view' in resp.context['form'].fields
     assert 'open_events' not in [k for k, v in resp.context['form'].fields['default_view'].choices]
     assert 'booking_form_url' not in resp.context['form'].fields
-    assert 'event_display_template' not in resp.context['form'].fields
 
     resp.form['default_view'] = 'month'
     resp.form.submit()
@@ -416,7 +414,6 @@ def test_options_agenda(app, admin_user):
     assert 'default_view' in resp.context['form'].fields
     assert 'open_events' not in [k for k, v in resp.context['form'].fields['default_view'].choices]
     assert 'booking_form_url' not in resp.context['form'].fields
-    assert 'event_display_template' not in resp.context['form'].fields
 
 
 def test_options_events_agenda_events_type(app, admin_user):
@@ -529,9 +526,11 @@ def test_options_virtual_agenda_delays(app, admin_user):
     assert agenda.maximal_booking_delay is None
 
 
-def test_options_agenda_booking_check_options(app, admin_user):
+def test_options_agenda_booking_display_options(app, admin_user):
     agenda = Agenda.objects.create(label='Foo bar', kind='events')
 
+    app = login(app)
+
     # check user template
     assert agenda.booking_user_block_template == ''
     assert (
@@ -539,8 +538,7 @@ def test_options_agenda_booking_check_options(app, admin_user):
         == '{{ booking.user_name|default:booking.label|default:"Anonymous" }}'
     )
 
-    app = login(app)
-    url = '/manage/agendas/%s/check-options' % agenda.pk
+    url = '/manage/agendas/%s/display-options' % agenda.pk
     resp = app.get(url)
     resp.form['booking_user_block_template'] = '{{ booking.user_name }} Foo Bar'
     resp = resp.form.submit()
@@ -558,10 +556,44 @@ def test_options_agenda_booking_check_options(app, admin_user):
         == '{{ booking.user_name|default:booking.label|default:"Anonymous" }}'
     )
 
+    resp = app.get(url)
+    valid_template = '{{ event.label|default:event.slug }} - {{ event.remaining_places|add:"5" }} / {{ event.start_datetime|date }} - {{ event.agenda.name }}'
+    resp.form['event_display_template'] = valid_template
+    resp = resp.form.submit().follow()
+
+    agenda.refresh_from_db()
+    assert agenda.event_display_template == valid_template
+
+    invalid_templates = [
+        '{{ syntax error }}',
+        '{{ event.label|invalidfilter }}',
+        '{{ event.label|default:notexist }}',
+    ]
+    for template in invalid_templates:
+        resp = app.get(url)
+        resp.form['event_display_template'] = template
+        resp = resp.form.submit()
+        assert 'syntax error' in resp.text
+
+    # check kind
+    agenda.kind = 'meetings'
+    agenda.save()
+    app.get(url, status=404)
+    agenda.kind = 'virtual'
+    agenda.save()
+    app.get(url, status=404)
+
+
+def test_options_agenda_booking_check_options(app, admin_user):
+    agenda = Agenda.objects.create(label='Foo bar', kind='events')
+
+    app = login(app)
+
     # check filters
     assert agenda.booking_check_filters == ''
     assert agenda.get_booking_check_filters() == []
 
+    url = '/manage/agendas/%s/check-options' % agenda.pk
     resp = app.get(url)
     resp.form['booking_check_filters'] = 'foo,bar,baz'
     resp = resp.form.submit()
@@ -594,30 +626,6 @@ def test_options_agenda_booking_check_options(app, admin_user):
     app.get(url, status=404)
 
 
-def test_options_agenda_event_display_template(app, admin_user):
-    agenda = Agenda.objects.create(label='Foo bar', kind='events')
-    Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
-    app = login(app)
-    resp = app.get('/manage/agendas/%s/edit' % agenda.pk)
-    valid_template = '{{ event.label|default:event.slug }} - {{ event.remaining_places|add:"5" }} / {{ event.start_datetime|date }} - {{ event.agenda.name }}'
-    resp.form['event_display_template'] = valid_template
-    resp = resp.form.submit().follow()
-
-    agenda.refresh_from_db()
-    assert agenda.event_display_template == valid_template
-
-    invalid_templates = [
-        '{{ syntax error }}',
-        '{{ event.label|invalidfilter }}',
-        '{{ event.label|default:notexist }}',
-    ]
-    for template in invalid_templates:
-        resp = app.get('/manage/agendas/%s/edit' % agenda.pk)
-        resp.form['event_display_template'] = template
-        resp = resp.form.submit()
-        assert 'syntax error' in resp.text
-
-
 def test_options_agenda_as_manager(app, manager_user):
     agenda = Agenda(label='Foo bar')
     agenda.view_role = manager_user.groups.all()[0]
diff --git a/tests/manager/test_event.py b/tests/manager/test_event.py
index 754d0801..ee69aae7 100644
--- a/tests/manager/test_event.py
+++ b/tests/manager/test_event.py
@@ -1112,6 +1112,35 @@ def test_import_events_wrong_kind(app, admin_user):
     app.get('/manage/agendas/%s/import-events' % agenda.id, status=404)
 
 
+@pytest.mark.freeze_time('2022-05-24')
+def test_event_detail(app, admin_user):
+    agenda = Agenda.objects.create(label='Events', kind='events')
+    event = Event.objects.create(
+        label='xyz',
+        start_datetime=now() + datetime.timedelta(days=1),
+        places=10,
+        waiting_list_places=2,
+        agenda=agenda,
+    )
+    Booking.objects.create(event=event, user_last_name="User's 1")
+    Booking.objects.create(event=event, user_last_name='User 2', in_waiting_list=True)
+
+    login(app)
+    resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk))
+    assert 'Bookings (1/10)' in resp.text
+    assert 'User&#39;s 1, May 24, 2022, 2 a.m.' in resp.text
+    assert 'Waiting List (1/2): 1 remaining place' in resp.text
+    assert 'User 2, May 24, 2022, 2 a.m.' in resp.text
+
+    agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
+    agenda.save()
+    resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk))
+    assert 'Bookings (1/10)' in resp.text
+    assert '&lt;b&gt;User&#39;s 1&lt;/b&gt; Foo Bar, May 24, 2022, 2 a.m.' in resp.text
+    assert 'Waiting List (1/2): 1 remaining place' in resp.text
+    assert '&lt;b&gt;User 2&lt;/b&gt; Foo Bar, May 24, 2022, 2 a.m.' in resp.text
+
+
 def test_event_cancellation(app, admin_user):
     agenda = Agenda.objects.create(label='Events', kind='events')
     event = Event.objects.create(
@@ -1125,8 +1154,8 @@ def test_event_cancellation(app, admin_user):
     resp = resp.click('Cancel', href='/cancel')
     assert 'related bookings' not in resp.text
 
-    Booking.objects.create(event=event)
-    Booking.objects.create(event=event)
+    Booking.objects.create(event=event, user_last_name='User 1')
+    Booking.objects.create(event=event, user_last_name='User 2')
 
     resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk))
     assert 'Bookings (2/10)' in resp.text
@@ -1366,7 +1395,7 @@ def test_event_check(app, admin_user):
         event=event, user_external_id='user:1', user_first_name='User', user_last_name='42'
     )
     Booking.objects.create(
-        event=event, user_external_id='user:2', user_first_name='User', user_last_name='01'
+        event=event, user_external_id='user:2', user_first_name="User's", user_last_name='01'
     )
     Booking.objects.create(
         event=event, user_external_id='user:3', user_first_name='User', user_last_name='17'
@@ -1424,7 +1453,7 @@ def test_event_check(app, admin_user):
     resp = resp.click('Check')
     assert (
         resp.text.index('Bookings (6/10)')
-        < resp.text.index('User 01')
+        < resp.text.index("User&#39;s 01")
         < resp.text.index('User 05')
         < resp.text.index('User 17')
         < resp.text.index('User 35')
@@ -1486,7 +1515,7 @@ def test_event_check(app, admin_user):
     resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
     assert (
         resp.text.index('Bookings (6/10)')
-        < resp.text.index('User 01')
+        < resp.text.index("User&#39;s 01")
         < resp.text.index('User 05')
         < resp.text.index('User 12 Cancelled')
         < resp.text.index('Subscription 14')
@@ -1504,12 +1533,12 @@ def test_event_check(app, admin_user):
     assert 'Subscription too soon' not in resp
     assert 'Subscription too late' not in resp
 
-    agenda.booking_user_block_template = '{{ booking.user_name }} Foo Bar'
+    agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
     agenda.save()
     resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
-    assert 'User 01 Foo Bar' in resp
-    assert 'Subscription 14 Foo Bar' in resp
-    assert 'User Waiting Foo Bar' in resp
+    assert '&lt;b&gt;User&#39;s 01&lt;/b&gt; Foo Bar' in resp
+    assert '&lt;b&gt;Subscription 14&lt;/b&gt; Foo Bar' in resp
+    assert '&lt;b&gt;User Waiting&lt;/b&gt; Foo Bar' in resp
 
     # cancelled booking
     token = resp.context['csrf_token']
-- 
2.35.1

