From 55d1b8821410540d0abd996bcccfc47b9c6f2728 Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Sun, 29 Nov 2020 15:11:45 +0100
Subject: [PATCH] Implement multiple selection in person overview

---
 .../alsijil/static/css/alsijil/person.css     |  11 ++
 .../alsijil/class_register/person.html        | 116 +++++++++++-------
 aleksis/apps/alsijil/views.py                 |  31 ++++-
 3 files changed, 114 insertions(+), 44 deletions(-)
 create mode 100644 aleksis/apps/alsijil/static/css/alsijil/person.css

diff --git a/aleksis/apps/alsijil/static/css/alsijil/person.css b/aleksis/apps/alsijil/static/css/alsijil/person.css
new file mode 100644
index 000000000..bed4e7e3f
--- /dev/null
+++ b/aleksis/apps/alsijil/static/css/alsijil/person.css
@@ -0,0 +1,11 @@
+span.input-field.inline > .select-wrapper > input {
+	color: red;
+	padding: 14px 0 0 0;
+	line-height: 2px;
+	height: 36px;
+	vertical-align: middle;
+}
+
+span.input-field.inline > .select-wrapper .caret {
+	top: 12px !important;
+}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
index 7254e0e24..f4551ed96 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html
@@ -4,6 +4,11 @@
 {% load data_helpers %}
 {% load week_helpers %}
 {% load i18n %}
+{% load static %}
+
+{% block extra_head %}
+    <link rel="stylesheet" href="{% static "css/alsijil/person.css" %}">
+{% endblock %}
 
 {% block browser_title %}{% blocktrans %}Class register: person{% endblocktrans %}{% endblock %}
 
@@ -33,51 +38,76 @@
 
   <div class="row">
   <div class="col s12 m12 l6">
-    <h5>{% trans "Unexcused absences" %}</h5>
 
-    <ul class="collection">
-      {% for note in unexcused_absences %}
-        <li class="collection-item">
-          {% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
-          {% if can_edit_personal_note %}
-            <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
+      <h5>{% trans "Unexcused absences" %}</h5>
+      <h6>
+          <form method="POST" id="excuse-multiple-form">
               {% csrf_token %}
-              {% trans "Mark as" %}
-              <input type="hidden" value="{{ note.pk }}" name="personal_note">
-              {% include "alsijil/partials/mark_as_buttons.html" %}
-              <a class="btn-flat red-text" title="{% trans "Delete note" %}"
-                 href="{% url "delete_personal_note" note.pk %}">
-                <i class="material-icons center">cancel</i>
-              </a>
-            </form>
-          {% endif %}
-          <i class="material-icons left red-text">warning</i>
-          <p class="no-margin">
-            <a href="{{ note.get_absolute_url }}">{{ note.date }}, {{ note.lesson_period }}</a>
-          </p>
-          {% if note.remarks %}
-            <p class="no-margin"><em>{{ note.remarks }}</em></p>
-          {% endif %}
-          {% if can_edit_personal_note %}
-            <form action="" method="post" class="hide-on-med-and-up">
-              {% csrf_token %}
-              {% trans "Mark as" %}
-              <input type="hidden" value="{{ note.pk }}" name="personal_note">
-              {% include "alsijil/partials/mark_as_buttons.html" %}
-              <a class="btn-flat red-text" title="{% trans "Delete note" %}"
-                 href="{% url "delete_personal_note" note.pk %}">
-                <i class="material-icons center">cancel</i>
-              </a>
-            </form>
-          {% endif %}
-        </li>
-      {% empty %}
-        <li class="collection-item avatar valign-wrapper">
-          <i class="material-icons left materialize-circle green white-text">check</i>
-          <span class="title">{% trans "There are no unexcused lessons." %}</span>
-        </li>
-      {% endfor %}
-    </ul>
+              {% trans "Mark selected as" %}
+              <span class="input-field inline">
+                  <select id="excuse-type-multiple" name="excuse_type">
+                      <option value="e" selected>{% trans "Excused" %}</option>
+                      {% for excuse_type in excuse_types %}
+                      <option value="{{ excuse_type.pk }}">{{ excuse_type.name }}</option>
+                      {% endfor %}
+                  </select>
+                  <label for="excuse-type-multiple" class="hide">{% trans "Excuse type" %}</label>
+              </span>
+              <button type="submit" class="btn secondary-color" name="excuse_multiple" value="1">
+                  {% trans "Submit" %}<i class="material-icons right">send</i>
+              </button>
+          </form>
+      </h6>
+
+      <ul class="collection">
+          {% for note in unexcused_absences %}
+              {% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
+              <li class="collection-item">
+                  {% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
+                  {% if can_edit_personal_note %}
+                      <label class="left">
+                          <input type="checkbox" name="selected_notes" value="{{ note.pk }}"
+                                 form="excuse-multiple-form"/>
+                          <span></span>
+                      </label>
+                      <form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
+                          {% csrf_token %}
+                          {% trans "Mark as" %}
+                          <input type="hidden" value="{{ note.pk }}" name="personal_note">
+                          {% include "alsijil/partials/mark_as_buttons.html" %}
+                          <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                             href="{% url "delete_personal_note" note.pk %}">
+                              <i class="material-icons center">cancel</i>
+                          </a>
+                      </form>
+                  {% endif %}
+                  <i class="material-icons left red-text">warning</i>
+                  <p class="no-margin">
+                      <a href="{% url "lesson_by_week_and_period" note.year note.week note.lesson_period.pk %}">{{ note_date }}, {{ note.lesson_period }}</a>
+                  </p>
+                  {% if note.remarks %}
+                      <p class="no-margin"><em>{{ note.remarks }}</em></p>
+                  {% endif %}
+                  {% if can_edit_personal_note %}
+                      <form action="" method="post" class="hide-on-med-and-up">
+                          {% csrf_token %}
+                          {% trans "Mark as" %}
+                          <input type="hidden" value="{{ note.pk }}" name="personal_note">
+                          {% include "alsijil/partials/mark_as_buttons.html" %}
+                          <a class="btn-flat red-text" title="{% trans "Delete note" %}"
+                             href="{% url "delete_personal_note" note.pk %}">
+                              <i class="material-icons center">cancel</i>
+                          </a>
+                      </form>
+                  {% endif %}
+              </li>
+              {% empty %}
+              <li class="collection-item avatar valign-wrapper">
+                  <i class="material-icons left circle green white-text">check</i>
+                  <span class="title">{% trans "There are no unexcused lessons." %}</span>
+              </li>
+          {% endfor %}
+      </ul>
     {% if stats %}
       <h5>{% trans "Statistics on absences, tardiness and remarks" %}</h5>
       <ul class="collapsible">
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index 75c07ebec..7f0cfbea3 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -1,5 +1,6 @@
 from contextlib import nullcontext
 from copy import deepcopy
+from contextlib import suppress
 from datetime import date, datetime, timedelta
 from typing import Any, Dict, Optional
 
@@ -736,7 +737,35 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
                     pass
 
             if found:
-                if request.POST.get("date"):
+                if request.POST.get("excuse_multiple") and request.POST.get("selected_notes"):
+                    if not request.user.has_perm(
+                            "alsijil.edit_person_overview_personalnote", person
+                    ):
+                        raise PermissionDenied()
+
+                    lesson_pks = request.POST.getlist("selected_notes")
+
+                    def convert_to_int_optional(x):
+                        with suppress(ValueError):
+                            return int(x)
+
+                    lesson_pks = [convert_to_int_optional(pk) for pk in lesson_pks]
+
+                    notes = person.personal_notes.filter(
+                        pk__in=lesson_pks,
+                        absent=True,
+                        excused=False,
+                    )
+                    for note in notes:
+                        note.excused = True
+                        note.excuse_type = excuse_type
+                        with reversion.create_revision():
+                            reversion.set_user(request.user)
+                            note.save()
+
+                    messages.success(request, _("The absences have been marked as excused."))
+
+                elif request.POST.get("date"):
                     # Mark absences on date as excused
                     try:
                         date = datetime.strptime(request.POST["date"], "%Y-%m-%d").date()
-- 
GitLab