From c4cd9d592bd091a369556abb3c703b2252dc1993 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 19 Apr 2021 15:47:38 +0200
Subject: [PATCH] manager: configuer user block on check page (#53237)

---
 .../0080_booking_user_block_template.py       | 23 +++++++++++
 chrono/agendas/models.py                      | 24 ++++++++++++
 chrono/manager/forms.py                       | 16 +++++++-
 .../templates/chrono/manager_event_check.html | 16 ++++----
 .../manager_event_check_booking_fragment.html |  2 +-
 .../manager_events_agenda_settings.html       | 31 ++++++++-------
 chrono/manager/urls.py                        |  6 +--
 chrono/manager/views.py                       | 13 ++++---
 tests/manager/test_absence_reason.py          |  7 ++--
 tests/manager/test_all.py                     | 38 ++++++++++++++++++-
 tests/manager/test_event.py                   |  6 +++
 11 files changed, 146 insertions(+), 36 deletions(-)
 create mode 100644 chrono/agendas/migrations/0080_booking_user_block_template.py

diff --git a/chrono/agendas/migrations/0080_booking_user_block_template.py b/chrono/agendas/migrations/0080_booking_user_block_template.py
new file mode 100644
index 0000000..e27d6e8
--- /dev/null
+++ b/chrono/agendas/migrations/0080_booking_user_block_template.py
@@ -0,0 +1,23 @@
+from django.db import migrations, models
+
+import chrono.agendas.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('agendas', '0079_text_to_jsonb'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='agenda',
+            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 5f3ba05..ec4661d 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -203,6 +203,12 @@ class Agenda(models.Model):
         _('Booking form URL'), max_length=200, blank=True, validators=[django_template_validator]
     )
     desk_simple_management = models.BooleanField(default=False)
+    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],
+    )
 
     class Meta:
         ordering = ['label']
@@ -663,6 +669,12 @@ class Agenda(models.Model):
         except (VariableDoesNotExist, TemplateSyntaxError):
             return
 
+    def get_booking_user_block_template(self):
+        return (
+            self.booking_user_block_template
+            or '{{ booking.user_name|default:booking.label|default:"%s" }}' % _('Unknown')
+        )
+
     def prefetch_desks_and_exceptions(self, with_sources=False):
         if self.kind == 'meetings':
             desks = self.desk_set.all()
@@ -1547,6 +1559,18 @@ class Booking(models.Model):
             self.secondary_booking_set.update(in_waiting_list=True)
             self.save()
 
+    def get_user_block(self):
+        template_vars = Context(settings.TEMPLATE_VARS)
+        template_vars.update(
+            {
+                'booking': self,
+            }
+        )
+        try:
+            return Template(self.event.agenda.get_booking_user_block_template()).render(template_vars)
+        except (VariableDoesNotExist, TemplateSyntaxError):
+            return
+
     @classmethod
     def anonymize_bookings(cls, bookings_queryset):
         bookings_queryset.update(
diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index 185248e..b4f4e3a 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -27,12 +27,12 @@ from django.db import transaction
 from django.forms import ValidationError
 from django.utils.encoding import force_text
 from django.utils.six import StringIO
-from django.utils.timezone import make_aware, make_naive, now
+from django.utils.timezone import make_aware, now
 from django.utils.translation import ugettext_lazy as _
 
 from chrono.agendas.models import (
     WEEKDAYS_LIST,
-    AbsenceReason,
+    AbsenceReasonGroup,
     Agenda,
     AgendaNotificationsSettings,
     AgendaReminderSettings,
@@ -729,6 +729,18 @@ class EventCancelForm(forms.ModelForm):
         fields = []
 
 
+class AgendaBookingCheckSettingsForm(forms.ModelForm):
+    class Meta:
+        model = Agenda
+        fields = ['absence_reasons_group', 'booking_user_block_template']
+        widgets = {'booking_user_block_template': forms.Textarea(attrs={'rows': 3})}
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        if not AbsenceReasonGroup.objects.exists():
+            del self.fields['absence_reasons_group']
+
+
 class AgendaNotificationsForm(forms.ModelForm):
     class Meta:
         model = AgendaNotificationsSettings
diff --git a/chrono/manager/templates/chrono/manager_event_check.html b/chrono/manager/templates/chrono/manager_event_check.html
index 33b74d2..c113bf5 100644
--- a/chrono/manager/templates/chrono/manager_event_check.html
+++ b/chrono/manager/templates/chrono/manager_event_check.html
@@ -31,13 +31,15 @@
   <h3>
     {% blocktrans with places=object.waiting_list_places booked_places=waiting|length %}Waiting List ({{ booked_places }}/{{ places }}){% endblocktrans %}
   </h3>
-<div>
-  <ul class="objects-list single-links">
-    {% for booking in waiting %}
-    <li><a>{{ booking.user_name|default:booking.label|default:_('Unknown') }}</a></li>
-    {% endfor %}
-  </ul>
-</div>
+  <div>
+    <table class="main check-bookings">
+      <tbody>
+        {% for booking in waiting %}
+        <tr><td class="booking-username">{{ booking.get_user_block }}</td></tr>
+        {% endfor %}
+      </tbody>
+    </table>
+  </div>
 </div>
 {% endif %}
 {% endblock %}
diff --git a/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html b/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html
index 4add784..3dbc2bd 100644
--- a/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html
+++ b/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html
@@ -1,6 +1,6 @@
 {% load i18n %}
 
-<td class="booking-username">{{ booking.user_name|default:booking.label|default:_('Unknown') }}</td>
+<td class="booking-username">{{ booking.get_user_block }}</td>
 <td class="booking-status">
   {{ booking.user_was_present|yesno:_('Present,Absent,-') }}
   {% if booking.user_was_present is False and booking.user_absence_reason %}
diff --git a/chrono/manager/templates/chrono/manager_events_agenda_settings.html b/chrono/manager/templates/chrono/manager_events_agenda_settings.html
index aff91b4..07f8a26 100644
--- a/chrono/manager/templates/chrono/manager_events_agenda_settings.html
+++ b/chrono/manager/templates/chrono/manager_events_agenda_settings.html
@@ -30,25 +30,28 @@
 </div>
 </div>
 
-{% if has_absence_reasons %}
 <div class="section">
-<h3>{% trans "Absence reasons" %}
-<a rel="popup" class="button" href="{% url 'chrono-manager-agenda-absence-reasons' pk=object.id %}">{% trans 'Configure' %}</a>
+<h3>{% trans "Booking check options" %}
+    <a rel="popup" class="button" href="{% url 'chrono-manager-agenda-booking-check-settings' pk=object.pk %}">{% trans 'Configure' %}</a>
 </h3>
 <div>
-{% if agenda.absence_reasons_group %}
-<p>{% trans "Absence reasons group:" %} {{ agenda.absence_reasons_group }}</p>
-<ul>
-  {% for reason in agenda.absence_reasons_group.absence_reasons.all %}
-  <li>{{ reason }}</li>
-  {% endfor %}
-</ul>
-{% else %}
-<p>{% trans "No absence reasons configured for this agenda." %}</p>
-{% endif %}
+    {% if has_absence_reasons %}
+        {% if agenda.absence_reasons_group %}
+        <p>{% trans "Absence reasons group:" %} {{ agenda.absence_reasons_group }}</p>
+        <ul>
+          {% for reason in agenda.absence_reasons_group.absence_reasons.all %}
+          <li>{{ reason }}</li>
+          {% endfor %}
+        </ul>
+        {% else %}
+        <p>{% trans "No absence reasons configured for this agenda." %}</p>
+        {% endif %}
+    {% endif %}
+
+    <p>{% trans "User block template" %}:</p>
+    <pre>{{ agenda.get_booking_user_block_template }}</pre>
 </div>
 </div>
-{% endif %}
 
 <div class="section">
 <h3>{% trans "Notifications" %}
diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py
index 848e188..1626de5 100644
--- a/chrono/manager/urls.py
+++ b/chrono/manager/urls.py
@@ -148,9 +148,9 @@ urlpatterns = [
     ),
     url(r'^agendas/(?P<pk>\d+)/roles$', views.agenda_roles, name='chrono-manager-agenda-roles'),
     url(
-        r'^agendas/(?P<pk>\d+)/absence-reasons$',
-        views.agenda_absence_reasons,
-        name='chrono-manager-agenda-absence-reasons',
+        r'^agendas/(?P<pk>\d+)/check-options$',
+        views.agenda_booking_check_settings,
+        name='chrono-manager-agenda-booking-check-settings',
     ),
     url(r'^agendas/(?P<pk>\d+)/delete$', views.agenda_delete, name='chrono-manager-agenda-delete'),
     url(r'^agendas/(?P<pk>\d+)/export$', views.agenda_export, name='chrono-manager-agenda-export'),
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index f7e39fa..5f92c2e 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -77,6 +77,7 @@ from chrono.agendas.models import (
 
 from .forms import (
     AgendaAddForm,
+    AgendaBookingCheckSettingsForm,
     AgendaBookingDelaysForm,
     AgendaDuplicateForm,
     AgendaEditForm,
@@ -912,16 +913,18 @@ class AgendaRolesView(AgendaEditView):
 agenda_roles = AgendaRolesView.as_view()
 
 
-class AgendaAbsenceReasonsView(AgendaEditView):
-    form_class = None
-    fields = ['absence_reasons_group']
-    title = _('Configure absence reasons')
+class AgendaBookingCheckSettingsView(AgendaEditView):
+    form_class = AgendaBookingCheckSettingsForm
+    title = _("Configure booking check 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_absence_reasons = AgendaAbsenceReasonsView.as_view()
+
+agenda_booking_check_settings = AgendaBookingCheckSettingsView.as_view()
 
 
 class AgendaDeleteView(DeleteView):
diff --git a/tests/manager/test_absence_reason.py b/tests/manager/test_absence_reason.py
index d6db734..f3468d5 100644
--- a/tests/manager/test_absence_reason.py
+++ b/tests/manager/test_absence_reason.py
@@ -198,16 +198,17 @@ def test_agenda_group(app, admin_user):
     resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
     assert 'has_absence_reasons' in resp.context
     assert resp.context['has_absence_reasons'] is False
-    assert 'Absence reasons' not in resp
+    assert 'No absence reasons configured for this agenda.' not in resp
     assert '/manage/agendas/%s/absence-reasons' % agenda.pk not in resp
+    resp = resp.click(href='/manage/agendas/%s/check-options' % agenda.pk)
+    assert 'absence_reasons_group' not in resp.context['form'].fields
 
     group = AbsenceReasonGroup.objects.create(label='Foo bar')
     resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
     assert 'has_absence_reasons' in resp.context
     assert resp.context['has_absence_reasons'] is True
-    assert 'Absence reasons' in resp
     assert 'No absence reasons configured for this agenda.' in resp
-    resp = resp.click(href='/manage/agendas/%s/absence-reasons' % agenda.pk)
+    resp = resp.click(href='/manage/agendas/%s/check-options' % agenda.pk)
     resp.form['absence_reasons_group'] = group.pk
     resp = resp.form.submit().follow()
     assert 'Absence reasons group: Foo bar' in resp
diff --git a/tests/manager/test_all.py b/tests/manager/test_all.py
index 46a1d70..a33387c 100644
--- a/tests/manager/test_all.py
+++ b/tests/manager/test_all.py
@@ -480,7 +480,7 @@ def test_options_agenda_cant_unset_delays(app, admin_user):
     assert agenda.minimal_booking_delay == 1
 
 
-def test_options_virtuale_agenda_can_unset_delays(app, admin_user):
+def test_options_virtual_agenda_can_unset_delays(app, admin_user):
     agenda = Agenda.objects.create(label=u'Foo bar', kind='virtual', maximal_booking_delay=2)
     assert agenda.maximal_booking_delay == 2
     app = login(app)
@@ -492,6 +492,42 @@ def test_options_virtuale_agenda_can_unset_delays(app, admin_user):
     assert agenda.maximal_booking_delay is None
 
 
+def test_options_agenda_booking_user_block_template(app, admin_user):
+    agenda = Agenda.objects.create(label='Foo bar', kind='events')
+    assert agenda.booking_user_block_template == ''
+    assert (
+        agenda.get_booking_user_block_template()
+        == '{{ booking.user_name|default:booking.label|default:"Unknown" }}'
+    )
+
+    app = login(app)
+    url = '/manage/agendas/%s/check-options' % agenda.pk
+    resp = app.get(url)
+    resp.form['booking_user_block_template'] = '{{ booking.user_name }} Foo Bar'
+    resp = resp.form.submit()
+    agenda.refresh_from_db()
+    assert agenda.booking_user_block_template == '{{ booking.user_name }} Foo Bar'
+    assert agenda.get_booking_user_block_template() == '{{ booking.user_name }} Foo Bar'
+
+    resp = app.get(url)
+    resp.form['booking_user_block_template'] = ''
+    resp = resp.form.submit()
+    agenda.refresh_from_db()
+    assert agenda.booking_user_block_template == ''
+    assert (
+        agenda.get_booking_user_block_template()
+        == '{{ booking.user_name|default:booking.label|default:"Unknown" }}'
+    )
+
+    # 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_as_manager(app, manager_user):
     agenda = Agenda(label=u'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 9427b65..248faf0 100644
--- a/tests/manager/test_event.py
+++ b/tests/manager/test_event.py
@@ -1102,6 +1102,12 @@ def test_event_check(app, admin_user):
     )  # user ordering is not optimal ...
     assert 'User Cancelled' not in resp
 
+    agenda.booking_user_block_template = '{{ booking.user_name }} Foo Bar'
+    agenda.save()
+    resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
+    assert 'User 1 Foo Bar' in resp
+    assert 'User Waiting Foo Bar' in resp
+
     # cancelled booking
     token = resp.context['csrf_token']
     app.post(
-- 
2.30.2

