From a3cfe7c0dfbacf78074375b457aa2d6e20cb2f85 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 8 Apr 2021 15:50:24 +0200
Subject: [PATCH] manager: mark all bookings without status (#52813)

---
 .../static/js/chrono_events.manager.js        |  5 +-
 .../templates/chrono/manager_event_check.html | 24 +++++++
 .../manager_event_check_booking_fragment.html |  6 +-
 chrono/manager/urls.py                        | 10 +++
 chrono/manager/views.py                       | 68 +++++++++++++++++++
 tests/manager/test_event.py                   | 64 +++++++++++++++++
 6 files changed, 173 insertions(+), 4 deletions(-)

diff --git a/chrono/manager/static/js/chrono_events.manager.js b/chrono/manager/static/js/chrono_events.manager.js
index 55f6abb..1bf025a 100644
--- a/chrono/manager/static/js/chrono_events.manager.js
+++ b/chrono/manager/static/js/chrono_events.manager.js
@@ -30,7 +30,7 @@ $(function() {
     });
   }
 
-  $(document).on('submit', '.booking form', function(event) {
+  $(document).on('submit', '.booking form.with-ajax', function(event) {
     var $form = $(this);
     var formData = {
       'csrfmiddlewaretoken': $('input[name=csrfmiddlewaretoken]', $form).val()
@@ -45,6 +45,9 @@ $(function() {
       data: formData
     }).done(function(html) {
       $form.parent().parent().html(html);
+      if (!$('.booking-status.without-status').length) {
+        $('tr.booking.all-bookings').hide();
+      }
     }).fail(function() {
       location.reload();
     });
diff --git a/chrono/manager/templates/chrono/manager_event_check.html b/chrono/manager/templates/chrono/manager_event_check.html
index 33b74d2..2c4bf86 100644
--- a/chrono/manager/templates/chrono/manager_event_check.html
+++ b/chrono/manager/templates/chrono/manager_event_check.html
@@ -16,6 +16,30 @@
   <div>
     <table class="main check-bookings">
       <tbody>
+      {% if booked_without_status %}
+        <tr class="booking all-bookings">
+          <td colspan="2"><b>{% trans "Mark all bookings without status:" %}</b></td>
+          <td class="booking-actions">
+            <form method="post" action="{% url 'chrono-manager-event-presence' pk=agenda.pk event_pk=object.pk %}">
+              {% csrf_token %}
+              <button class="submit-button">{% trans "Presence" %}</button>
+            </form>
+            <form method="post" action="{% url 'chrono-manager-event-absence' pk=agenda.pk event_pk=object.pk %}" id="all-bookings-absence">
+              {% csrf_token %}
+              <button class="submit-button">{% trans "Absence" %}</button>
+              {% if absence_form.reason.field.choices.1 %}{{ absence_form.reason }}{% endif %}
+              <script>
+              $(function() {
+                  $('#all-bookings-absence select').on('change',
+                      function() {
+                        $('#all-bookings-absence').submit();
+                  });
+              });
+              </script>
+            </form>
+          </td>
+        </tr>
+      {% endif %}
       {% for booking in booked %}
         <tr class="booking">
         {% include "chrono/manager_event_check_booking_fragment.html" %}
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..060fcce 100644
--- a/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html
+++ b/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html
@@ -1,20 +1,20 @@
 {% load i18n %}
 
 <td class="booking-username">{{ booking.user_name|default:booking.label|default:_('Unknown') }}</td>
-<td class="booking-status">
+<td class="booking-status {% if booking.user_was_present is None %}without-status{% endif %}">
   {{ booking.user_was_present|yesno:_('Present,Absent,-') }}
   {% if booking.user_was_present is False and booking.user_absence_reason %}
     ({{ booking.user_absence_reason }})
   {% endif %}
 </td>
 <td class="booking-actions" data-booking-id="{{ booking.id }}">
-  <form method="post" action="{% url 'chrono-manager-booking-presence' pk=agenda.pk booking_pk=booking.pk %}">
+  <form method="post" action="{% url 'chrono-manager-booking-presence' pk=agenda.pk booking_pk=booking.pk %}" class="with-ajax">
     {% csrf_token %}
     <button class="submit-button"
             {% if booking.user_was_present is True %}disabled{% endif %}
             >{% trans "Presence" %}</button>
   </form>
-  <form method="post" action="{% url 'chrono-manager-booking-absence' pk=agenda.pk booking_pk=booking.pk %}">
+  <form method="post" action="{% url 'chrono-manager-booking-absence' pk=agenda.pk booking_pk=booking.pk %}" class="with-ajax">
     {% csrf_token %}
     <button class="submit-button"
             {% if booking.user_was_present is False %}disabled{% endif %}
diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py
index 848e188..eb46931 100644
--- a/chrono/manager/urls.py
+++ b/chrono/manager/urls.py
@@ -211,6 +211,16 @@ urlpatterns = [
         views.event_check,
         name='chrono-manager-event-check',
     ),
+    url(
+        r'^agendas/(?P<pk>\d+)/events/(?P<event_pk>\d+)/presence$',
+        views.event_presence,
+        name='chrono-manager-event-presence',
+    ),
+    url(
+        r'^agendas/(?P<pk>\d+)/events/(?P<event_pk>\d+)/absence$',
+        views.event_absence,
+        name='chrono-manager-event-absence',
+    ),
     url(
         r'^agendas/(?P<pk>\d+)/event_cancellation_report/(?P<report_pk>\d+)/$',
         views.event_cancellation_report,
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index f7e39fa..bbb663e 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -1947,6 +1947,9 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
         context['booked'] = event.booking_set.filter(
             cancellation_datetime__isnull=True, in_waiting_list=False
         ).order_by('user_name')
+        context['booked_without_status'] = any(e.user_was_present is None for e in context['booked'])
+        if context['booked_without_status']:
+            context['absence_form'] = BookingAbsenceReasonForm(agenda=self.agenda)
         context['waiting'] = event.booking_set.filter(
             cancellation_datetime__isnull=True, in_waiting_list=True
         ).order_by('user_name')
@@ -1960,6 +1963,71 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
 event_check = EventCheckView.as_view()
 
 
+class EventCheckMixin(object):
+    def set_agenda(self, **kwargs):
+        self.agenda = get_object_or_404(
+            Agenda,
+            pk=kwargs.get('pk'),
+            kind='events',
+        )
+        self.event = get_object_or_404(
+            Event,
+            pk=kwargs.get('event_pk'),
+            agenda=self.agenda,
+            start_datetime__date__lte=now().date(),
+            cancelled=False,
+        )
+
+    def get_bookings(self):
+        return self.event.booking_set.filter(
+            event__agenda=self.agenda,
+            event__start_datetime__date__lte=now().date(),
+            event__cancelled=False,
+            cancellation_datetime__isnull=True,
+            in_waiting_list=False,
+            user_was_present__isnull=True,
+        )
+
+    def response(self, request):
+        return HttpResponseRedirect(
+            reverse(
+                'chrono-manager-event-check',
+                kwargs={'pk': self.agenda.pk, 'event_pk': self.event.pk},
+            )
+        )
+
+
+class EventPresenceView(EventCheckMixin, ViewableAgendaMixin, View):
+    def post(self, request, *args, **kwargs):
+        bookings = self.get_bookings()
+        bookings.update(user_absence_reason='', user_was_present=True)
+        return self.response(request)
+
+
+event_presence = EventPresenceView.as_view()
+
+
+class EventAbsenceView(EventCheckMixin, ViewableAgendaMixin, FormView):
+    form_class = BookingAbsenceReasonForm
+
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+        kwargs['agenda'] = self.agenda
+        return kwargs
+
+    def post(self, request, *args, **kwargs):
+        form = self.get_form()
+        qs_kwargs = {}
+        if form.is_valid():
+            qs_kwargs['user_absence_reason'] = form.cleaned_data['reason']
+        bookings = self.get_bookings()
+        bookings.update(user_was_present=False, **qs_kwargs)
+        return self.response(request)
+
+
+event_absence = EventAbsenceView.as_view()
+
+
 class AgendaAddResourceView(ManagedAgendaMixin, FormView):
     template_name = 'chrono/manager_agenda_resource_form.html'
     form_class = AgendaResourceForm
diff --git a/tests/manager/test_event.py b/tests/manager/test_event.py
index 9427b65..f982555 100644
--- a/tests/manager/test_event.py
+++ b/tests/manager/test_event.py
@@ -1269,3 +1269,67 @@ def test_event_check_booking_ajax(app, admin_user):
     assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
     assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text.startswith('Absence')
     assert '<option value="Foo reason" selected>Foo reason</option>' in resp
+
+
+def test_event_check_all_bookings(app, admin_user):
+    group = AbsenceReasonGroup.objects.create(label='Foo bar')
+    AbsenceReason.objects.create(label='Foo reason', group=group)
+    agenda = Agenda.objects.create(label='Events', kind='events', absence_reasons_group=group)
+    event = Event.objects.create(
+        label='xyz',
+        start_datetime=now() - datetime.timedelta(days=1),
+        places=10,
+        waiting_list_places=5,
+        agenda=agenda,
+    )
+    booking1 = Booking.objects.create(event=event, user_name='User 42')
+
+    login(app)
+    resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
+    token = resp.context['csrf_token']
+    assert 'Mark all bookings without status' in resp
+    assert '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk) in resp
+    assert '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk) in resp
+
+    resp = app.post(
+        '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token}
+    )
+    booking1.refresh_from_db()
+    assert booking1.user_was_present is False
+    assert booking1.user_absence_reason == ''
+
+    resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
+    assert 'Mark all bookings without status' not in resp
+    assert '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk) not in resp
+    assert '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk) not in resp
+
+    booking2 = Booking.objects.create(event=event, user_name='User 35')
+    resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
+    assert 'Mark all bookings without status' in resp
+    assert '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk) in resp
+    assert '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk) in resp
+
+    app.post(
+        '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token}
+    )
+    booking1.refresh_from_db()
+    assert booking1.user_was_present is False
+    assert booking1.user_absence_reason == ''
+    booking2.refresh_from_db()
+    assert booking2.user_was_present is True
+    assert booking2.user_absence_reason == ''
+
+    booking3 = Booking.objects.create(event=event, user_name='User 51')
+    app.post(
+        '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk),
+        params={'csrfmiddlewaretoken': token, 'reason': 'Foo reason'},
+    )
+    booking1.refresh_from_db()
+    assert booking1.user_was_present is False
+    assert booking1.user_absence_reason == ''
+    booking2.refresh_from_db()
+    assert booking2.user_was_present is True
+    assert booking2.user_absence_reason == ''
+    booking3.refresh_from_db()
+    assert booking3.user_was_present is False
+    assert booking3.user_absence_reason == 'Foo reason'
-- 
2.30.2

