From 7936ea14df2077a897342891d40f72f2d332a435 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Wed, 10 Mar 2021 11:38:13 +0100
Subject: [PATCH] Add support for sending notifications in "All lessons" via
 actions

---
 aleksis/apps/alsijil/actions.py               | 56 +++++++++++++++++++
 aleksis/apps/alsijil/forms.py                 |  6 ++
 aleksis/apps/alsijil/tables.py                | 20 +++++++
 .../alsijil/class_register/all_objects.html   | 49 ++++++++++++----
 aleksis/apps/alsijil/util/alsijil_helpers.py  |  2 +
 aleksis/apps/alsijil/views.py                 | 31 ++++++++--
 6 files changed, 149 insertions(+), 15 deletions(-)
 create mode 100644 aleksis/apps/alsijil/actions.py

diff --git a/aleksis/apps/alsijil/actions.py b/aleksis/apps/alsijil/actions.py
new file mode 100644
index 000000000..cf276f386
--- /dev/null
+++ b/aleksis/apps/alsijil/actions.py
@@ -0,0 +1,56 @@
+from typing import Sequence
+
+from django.contrib import messages
+from django.contrib.humanize.templatetags.humanize import apnumber
+from django.http import HttpRequest
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+
+from aleksis.core.models import Notification
+
+
+def send_request_to_check_entry(modeladmin, request: HttpRequest, selected_items: Sequence[dict]):
+    """Send notifications to the teachers of the selected register objects.
+
+    Action for use with ``RegisterObjectTable`` and ``RegisterObjectActionForm``.
+    """
+    # Group class register entries by teachers so each teacher gets just one notification
+    grouped_by_teachers = {}
+    for entry in selected_items:
+        teachers = entry["register_object"].get_teachers().all()
+        for teacher in teachers:
+            grouped_by_teachers.setdefault(teacher, [])
+            grouped_by_teachers[teacher].append(entry)
+
+    for teacher, items in grouped_by_teachers.items():
+        title = _(
+            f"{request.user.person.addressing_name} wants you to check some class register entries."
+        )
+        msg = _("Please check if the following class register entries are complete and correct:\n")
+
+        # Add one line for each entry to check
+        for entry in items:
+            reg_object = entry["register_object"]
+            date = entry["date"]
+            msg += f"- {reg_object} ({date})\n"
+
+        Notification.objects.create(
+            title=title,
+            description=msg,
+            sender=request.user.person.addressing_name,
+            recipient=teacher,
+            link=request.build_absolute_uri(reverse("overview_me")),
+        )
+
+    count_teachers = len(grouped_by_teachers.keys())
+    count_items = len(list)
+    messages.success(
+        request,
+        _(
+            f"We have successfully sent notifications to "
+            f"{apnumber(count_teachers)} persons for {apnumber(count_items)} lessons."
+        ),
+    )
+
+
+send_request_to_check_entry.short_description = _("Notify teacher to check data")
diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py
index 0d741f56a..1f912fc2f 100644
--- a/aleksis/apps/alsijil/forms.py
+++ b/aleksis/apps/alsijil/forms.py
@@ -14,10 +14,12 @@ from material import Fieldset, Layout, Row
 
 from aleksis.apps.chronos.managers import TimetableType
 from aleksis.apps.chronos.models import Subject, TimePeriod
+from aleksis.core.forms import ListActionForm
 from aleksis.core.models import Group, Person, SchoolTerm
 from aleksis.core.util.core_helpers import get_site_preferences
 from aleksis.core.util.predicates import check_global_permission
 
+from .actions import send_request_to_check_entry
 from .models import (
     ExcuseType,
     ExtraMark,
@@ -312,3 +314,7 @@ class FilterRegisterObjectForm(forms.Form):
             Q(lessons__groups__in=groups) | Q(extra_lessons__groups__in=groups)
         ).distinct()
         self.fields["subject"].queryset = subject_qs
+
+
+class RegisterObjectActionForm(ListActionForm):
+    actions = [send_request_to_check_entry]
diff --git a/aleksis/apps/alsijil/tables.py b/aleksis/apps/alsijil/tables.py
index d78f899ba..c7d995c43 100644
--- a/aleksis/apps/alsijil/tables.py
+++ b/aleksis/apps/alsijil/tables.py
@@ -4,6 +4,8 @@ from django.utils.translation import gettext_lazy as _
 import django_tables2 as tables
 from django_tables2.utils import A
 
+from aleksis.core.tables import SelectColumn
+
 
 class ExtraMarkTable(tables.Table):
     class Meta:
@@ -81,6 +83,12 @@ class GroupRoleTable(tables.Table):
 
 
 class RegisterObjectTable(tables.Table):
+    """Table to show all register objects in an overview.
+
+    .. warning::
+        Works only with ``generate_list_of_all_register_objects``.
+    """
+
     class Meta:
         attrs = {"class": "highlight responsive-table"}
 
@@ -103,3 +111,15 @@ class RegisterObjectTable(tables.Table):
                 register_object=value,
             ),
         )
+
+
+class RegisterObjectSelectTable(RegisterObjectTable):
+    """Table to show all register objects with multi-select support.
+
+    More information at ``RegisterObjectTable``
+    """
+
+    selected = SelectColumn()
+
+    class Meta(RegisterObjectTable.Meta):
+        sequence = ("selected", "...")
diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html b/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html
index 7fd891a47..6a22b8a91 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html
@@ -14,17 +14,46 @@
 {% endblock %}
 
 {% block content %}
-  <h5>{% trans "Lesson filter" %}</h5>
-  <form action="" method="get">
-    {% form form=filter_form %}{% endform %}
-    <button type="submit" class="btn waves-effect waves-light">
-      <i class="material-icons left">refresh</i>
-      {% trans "Update filters" %}
-    </button>
-  </form>
-  <h5>{% trans "Lesson table" %}</h5>
+  <div class="card">
+    <div class="card-content">
+      <div class="card-title">{% trans "Lesson filter" %}</div>
+      <form action="" method="get">
+        {% form form=filter_form %}{% endform %}
+        <button type="submit" class="btn waves-effect waves-light">
+          <i class="material-icons left">refresh</i>
+          {% trans "Update filters" %}
+        </button>
+      </form>
+    </div>
+  </div>
+
   {% if table %}
-  {% render_table table %}
+    <div class="card">
+      <div class="card-content">
+        <form action="" method="post">
+          {% csrf_token %}
+          <div class="row">
+            <div class="col s12 m4 l4 xl6">
+              <div class="card-title">{% trans "Lesson table" %}</div>
+            </div>
+            <div class="col s12 m8 l8 xl6">
+              <div class="col s12 m8">
+                {% form form=action_form %}{% endform %}</div>
+              <div class="col s12 m4">
+
+                <button type="submit" class="btn waves-effect waves-primary">
+                  {% trans "Execute" %}
+                  <i class="material-icons right">send</i>
+                </button>
+              </div>
+            </div>
+          </div>
+          {% render_table table %}
 
+        </form>
+      </div>
+    </div>
   {% endif %}
+
+  <script src="{% static "js/multi_select.js" %}"></script>
 {% endblock %}
\ No newline at end of file
diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py
index d708589e5..8da8711e1 100644
--- a/aleksis/apps/alsijil/util/alsijil_helpers.py
+++ b/aleksis/apps/alsijil/util/alsijil_helpers.py
@@ -255,6 +255,7 @@ def generate_list_of_all_register_objects(filter_dict: Dict[str, Any]) -> List[d
 
                     # Build table entry
                     entry = {
+                        "pk": f"{lesson_period.pk}_{week.year}_{week.week}",
                         "week": week,
                         "has_documentation": has_documentation,
                         "substitution": sub,
@@ -306,6 +307,7 @@ def generate_list_of_all_register_objects(filter_dict: Dict[str, Any]) -> List[d
 
             # Build table entry
             entry = {
+                "pk": str(register_object.pk),
                 "has_documentation": has_documentation,
                 "register_object": register_object,
                 "date": day,
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index bf4261ddb..99910ee78 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -49,6 +49,7 @@ from .forms import (
     LessonDocumentationForm,
     PersonalNoteFormSet,
     RegisterAbsenceForm,
+    RegisterObjectActionForm,
     SelectForm,
 )
 from .models import (
@@ -59,7 +60,13 @@ from .models import (
     LessonDocumentation,
     PersonalNote,
 )
-from .tables import ExcuseTypeTable, ExtraMarkTable, GroupRoleTable, RegisterObjectTable
+from .tables import (
+    ExcuseTypeTable,
+    ExtraMarkTable,
+    GroupRoleTable,
+    RegisterObjectSelectTable,
+    RegisterObjectTable,
+)
 from .util.alsijil_helpers import (
     annotate_documentations,
     generate_list_of_all_register_objects,
@@ -1255,7 +1262,7 @@ class GroupRoleAssignmentDeleteView(
 class AllRegisterObjectsView(PermissionRequiredMixin, View):
     permission_required = "alsijil.view_register_objects_list"
 
-    def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
+    def get_context_data(self, request):
         context = {}
         # Filter selectable groups by permissions
         groups = Group.objects.all()
@@ -1274,8 +1281,22 @@ class AllRegisterObjectsView(PermissionRequiredMixin, View):
         context["filter_form"] = filter_form
 
         register_objects = generate_list_of_all_register_objects(filter_dict)
+
+        self.action_form = RegisterObjectActionForm(request, register_objects, request.POST or None)
+        context["action_form"] = self.action_form
+
         if register_objects:
-            table = RegisterObjectTable(register_objects)
-            RequestConfig(request,).configure(table)  # paginate={"per_page": 100}
-            context["table"] = table
+            self.table = RegisterObjectSelectTable(register_objects)
+            RequestConfig(request, paginate={"per_page": 100}).configure(self.table)
+            context["table"] = self.table
+        return context
+
+    def get(self, request: HttpRequest) -> HttpResponse:
+        context = self.get_context_data(request)
+        return render(request, "alsijil/class_register/all_objects.html", context)
+
+    def post(self, request: HttpRequest):
+        context = self.get_context_data(request)
+        if self.action_form.is_valid():
+            self.action_form.execute()
         return render(request, "alsijil/class_register/all_objects.html", context)
-- 
GitLab