From ef1f1904b827f7b06c8b1321966695d5e2140860 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Wed, 13 Jan 2021 17:38:43 +0100
Subject: [PATCH] Include support for events and extra lessons in week view

---
 .../alsijil/class_register/week_view.html     | 101 +++++-----
 aleksis/apps/alsijil/util/alsijil_helpers.py  |  47 ++++-
 aleksis/apps/alsijil/views.py                 | 174 +++++++++++-------
 3 files changed, 213 insertions(+), 109 deletions(-)

diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
index 0f55e11ea..c60257e88 100644
--- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
+++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html
@@ -73,17 +73,14 @@
         </ul>
       </div>
       <div class="col s12" id="week-overview">
-        {% regroup lesson_periods by period.get_weekday_display as periods_by_day %}
-        {% for weekday, periods in periods_by_day %}
+        {% for weekday, objects in regrouped_objects.items %}
           {% with weekdays|get_dict:forloop.counter0 as advanced_weekday %}
-            {% weekday_to_date week periods.0.period.weekday as current_date %}
-
             {% if advanced_weekday.holiday and not request.site.preferences.alsijil__allow_entries_in_holidays %}
               <div class="card">
                 <div class="card-content">
-                  {% weekday_to_date week periods.0.period.weekday as current_date %}
                   <span class="card-title">
-                    {{ weekday }}, {{ current_date }} <span class="badge new blue no-float">{{ advanced_weekday.holiday }}</span>
+                    {{ advanced_weekday.name }}, {{ advanced_weekday.date }} <span
+                      class="badge new blue no-float">{{ advanced_weekday.holiday }}</span>
                   </span>
                 </div>
               </div>
@@ -91,7 +88,7 @@
               <div class="card show-on-extra-large">
                 <div class="card-content">
                   <span class="card-title">
-                    {{ weekday }}, {{ current_date }}
+                    {{ advanced_weekday.name }}, {{ advanced_weekday.date }}
                   </span>
                   <table class="striped datatable">
                     <thead>
@@ -109,55 +106,76 @@
                     </tr>
                     </thead>
                     <tbody>
-                    {% for period in periods %}
-                      {% has_perm "alsijil.view_lessondocumentation" user period as can_view_lesson_documentation %}
+                    {% for register_object in objects %}
+                      {% has_perm "alsijil.view_lessondocumentation" user register_object as can_view_lesson_documentation %}
                       {% if can_view_lesson_documentation %}
                         <tr>
                           <td class="center-align">
-                            {% include "alsijil/partials/lesson_status_icon.html" with period=period %}
+                            {% include "alsijil/partials/lesson_status_icon.html" with register_object=register_object %}
                           </td>
                           <td class="tr-link">
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {{ period.period.period }}.
+                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                              {% if register_object.period %}
+                                {{ register_object.period.period }}.
+                              {% else %}
+                                {{ register_object.date_start|date:"SHORT_DATE_FORMAT" }}
+                                {{ register_object.period_from.period }}.–<br/>
+                                {{ register_object.date_end|date:"SHORT_DATE_FORMAT" }}
+                                {{ register_object.period_to.period }}.
+                              {% endif %}
                             </a>
                           </td>
                           {% if not group %}
                             <td>
                               <a class="tr-link"
-                                 href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                                {{ period.lesson.group_names }}
+                                 href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                                {% if register_object.lesson %}
+                                  {{ register_object.lesson.group_names }}
+                                {% else %}
+                                  {{ register_object.group_names }}
+                                {% endif %}
                               </a>
                             </td>
                           {% endif %}
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {{ period.get_subject.name }}
+                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                              {% if register_object.get_subject %}
+                                {{ register_object.get_subject.name }}
+                              {% elif register_object.subject %}
+                                {{ register_object.subject }}
+                              {% else %}
+                                {% trans "Event" %}
+                              {% endif %}
                             </a>
                           </td>
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {{ period.get_teacher_names }}
+                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                              {% if register_object.get_teacher_names %}
+                                {{ register_object.get_teacher_names }}
+                              {% else %}
+                                {{ register_object.teacher_names }}
+                              {% endif %}
                             </a>
                           </td>
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {% firstof period.get_lesson_documentation.topic "–" %}
+                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                              {% firstof register_object.get_lesson_documentation.topic "–" %}
                             </a>
                           </td>
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {% firstof period.get_lesson_documentation.homework "–" %}
+                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                              {% firstof register_object.get_lesson_documentation.homework "–" %}
                             </a>
                           </td>
                           <td>
                             <a class="tr-link"
-                               href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                              {% firstof period.get_lesson_documentation.group_note "–" %}
+                               href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                              {% firstof register_object.get_lesson_documentation.group_note "–" %}
                             </a>
                           </td>
                         </tr>
@@ -169,48 +187,47 @@
               </div>
               <ul class="collapsible hide-on-extra-large-only">
                 <li class="">
-                  {% weekday_to_date week periods.0.period.weekday as current_date %}
                   <div class="collapsible-header flow-text">
-                    {{ weekday }}, {{ current_date }} <i class="material-icons collapsible-icon-right">expand_more</i>
+                    {{ weekday }}, {{ advanced_weekday.date }} <i class="material-icons collapsible-icon-right">expand_more</i>
                   </div>
                   <div class="collapsible-body">
                     <div class="collection">
-                      {% for period in periods %}
-                        {% has_perm "alsijil.view_lessondocumentation" user period as can_view_lesson_documentation %}
+                      {% for register_object in objects %}
+                        {% has_perm "alsijil.view_lessondocumentation" user register_object as can_view_lesson_documentation %}
                         {% if can_view_lesson_documentation %}
                           <a class="collection-item avatar"
-                             href="{% url 'lesson_by_week_and_period' week.year week.week period.id %}">
-                            {% include "alsijil/partials/lesson_status_icon.html" with period=period css_class="materialize-circle" color_suffix=" " %}
+                             href="{% url 'lesson_by_week_and_period' week.year week.week register_object.id %}">
+                            {% include "alsijil/partials/lesson_status_icon.html" with register_object=register_object css_class="materialize-circle" color_suffix=" " %}
                             <table class="hide-on-med-and-down">
                               <tr>
                                 <th>{% trans "Subject" %}</th>
-                                <td>{{ period.period.period }}. {{ period.get_subject.name }}</td>
+                                <td>{{ register_object.period.period }}. {{ register_object.get_subject.name }}</td>
                               </tr>
                               {% if not group %}
                                 <tr>
                                   <th>{% trans "Group" %}</th>
-                                  <td>{{ period.lesson.group_names }}</td>
+                                  <td>{{ register_object.lesson.group_names }}</td>
                                 </tr>
                               {% endif %}
                               <tr>
                                 <th>{% trans "Teachers" %}</th>
-                                <td>{{ period.lesson.teacher_names }}</td>
+                                <td>{{ register_object.lesson.teacher_names }}</td>
                               </tr>
                               <tr>
                                 <th>{% trans "Lesson topic" %}</th>
-                                <td>{% firstof period.get_lesson_documentation.topic "–" %}</td>
+                                <td>{% firstof register_object.get_lesson_documentation.topic "–" %}</td>
                               </tr>
                               {% with period.get_lesson_documentation as lesson_documentation %}
                                 {% if lesson_documentation.homework %}
                                   <tr>
                                     <th>{% trans "Homework" %}</th>
-                                    <td>{% firstof period.get_lesson_documentation.homework "–" %}</td>
+                                    <td>{% firstof register_object.get_lesson_documentation.homework "–" %}</td>
                                   </tr>
                                 {% endif %}
                                 {% if lesson_documentation.group_note %}
                                   <tr>
                                     <th>{% trans "Group note" %}</th>
-                                    <td>{% firstof period.get_lesson_documentation.group_note "–" %}</td>
+                                    <td>{% firstof register_object.get_lesson_documentation.group_note "–" %}</td>
                                   </tr>
                                 {% endif %}
                               {% endwith %}
@@ -218,32 +235,32 @@
                             <div class="hide-on-large-only">
                               <ul class="collection">
                                 <li class="collection-item">
-                                  {{ period.period.period }}. {{ period.get_subject.name }}
+                                  {{ register_object.period.period }}. {{ register_object.get_subject.name }}
                                 </li>
                                 {% if not group %}
                                   <li class="collection-item">
 
-                                    {{ period.lesson.group_names }}
+                                    {{ register_object.lesson.group_names }}
 
                                   </li>
                                 {% endif %}
                                 <li class="collection-item">
-                                  {{ period.lesson.teacher_names }}
+                                  {{ register_object.lesson.teacher_names }}
                                 </li>
                                 <li class="collection-item">
-                                  {{ period.get_lesson_documentation.topic }}
+                                  {{ register_object.get_lesson_documentation.topic }}
                                 </li>
                                 {% with period.get_lesson_documentation as lesson_documentation %}
                                   {% if lesson_documentation.homework %}
                                     <li class="collection-item">
                                       <strong>{% trans "Homework" %}</strong>
-                                      {% firstof period.get_lesson_documentation.homework "–" %}
+                                      {% firstof register_object.get_lesson_documentation.homework "–" %}
                                     </li>
                                   {% endif %}
                                   {% if lesson_documentation.group_note %}
                                     <li class="collection-item">
                                       <strong>{% trans "Group note" %}</strong>
-                                      {% firstof period.get_lesson_documentation.group_note "–" %}
+                                      {% firstof register_object.get_lesson_documentation.group_note "–" %}
                                     </li>
                                   {% endif %}
                                 {% endwith %}
diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py
index 432afb18f..24e04320e 100644
--- a/aleksis/apps/alsijil/util/alsijil_helpers.py
+++ b/aleksis/apps/alsijil/util/alsijil_helpers.py
@@ -1,10 +1,14 @@
-from typing import Optional
+from typing import List, Optional, Union
 
+from django.db.models.expressions import Exists, OuterRef
+from django.db.models.query import Prefetch, QuerySet
+from django.db.models.query_utils import Q
 from django.http import HttpRequest
 
 from calendarweek import CalendarWeek
 
-from aleksis.apps.chronos.models import LessonPeriod
+from aleksis.apps.alsijil.models import LessonDocumentation
+from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
 from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
 
 
@@ -44,3 +48,42 @@ def get_timetable_instance_by_pk(
         return get_el_by_pk(request, type_, id_)
     elif hasattr(request, "user") and hasattr(request.user, "person"):
         return request.user.person
+
+
+def annotate_documentations(
+    klass: Union[Event, LessonPeriod, ExtraLesson], wanted_week: CalendarWeek, pks: List[int]
+) -> QuerySet:
+    instances = klass.objects.prefetch_related(
+        Prefetch(
+            "documentations",
+            queryset=LessonDocumentation.objects.filter(
+                week=wanted_week.week, year=wanted_week.year
+            ),
+        )
+    ).filter(pk__in=pks)
+    if klass == LessonPeriod:
+        instances = instances.annotate_week(wanted_week)
+    if klass in (LessonPeriod, ExtraLesson):
+        instances = instances.order_by("period__weekday", "period__period")
+    else:
+        instances = instances.order_by("period_from__weekday", "period_from__period")
+    args = {Event: "event", LessonPeriod: "lesson_period", ExtraLesson: "extra_lesson"}
+    instances = instances.annotate(
+        has_documentation=Exists(
+            LessonDocumentation.objects.filter(
+                ~Q(topic__exact=""), week=wanted_week.week, year=wanted_week.year,
+            ).filter(**{args[klass]: OuterRef("pk")})
+        )
+    )
+
+    return instances
+
+
+def register_objects_sorter(register_object: Union[LessonPeriod, Event, ExtraLesson]) -> int:
+    """Sort key for sorted/sort for sorting a list of class register objects."""
+    if hasattr(register_object, "period"):
+        return register_object.period.period
+    elif isinstance(register_object, Event):
+        return register_object.period_from_on_day
+    else:
+        return 0
diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py
index c5616940d..d5c1d44ae 100644
--- a/aleksis/apps/alsijil/views.py
+++ b/aleksis/apps/alsijil/views.py
@@ -1,3 +1,4 @@
+from copy import deepcopy
 from datetime import date, datetime, timedelta
 from typing import Optional
 
@@ -18,7 +19,7 @@ from reversion.views import RevisionMixin
 from rules.contrib.views import PermissionRequiredMixin, permission_required
 
 from aleksis.apps.chronos.managers import TimetableType
-from aleksis.apps.chronos.models import Holiday, LessonPeriod, TimePeriod
+from aleksis.apps.chronos.models import Event, ExtraLesson, Holiday, LessonPeriod, TimePeriod
 from aleksis.apps.chronos.util.build import build_weekdays
 from aleksis.apps.chronos.util.date import get_weeks_for_year, week_weekday_to_date
 from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
@@ -34,9 +35,14 @@ from .forms import (
     RegisterAbsenceForm,
     SelectForm,
 )
-from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
+from .models import ExcuseType, ExtraMark, PersonalNote
 from .tables import ExcuseTypeTable, ExtraMarkTable
-from .util.alsijil_helpers import get_lesson_period_by_pk, get_timetable_instance_by_pk
+from .util.alsijil_helpers import (
+    annotate_documentations,
+    get_lesson_period_by_pk,
+    get_timetable_instance_by_pk,
+    register_objects_sorter,
+)
 
 
 @permission_required("alsijil.view_lesson", fn=get_lesson_period_by_pk)
@@ -199,8 +205,10 @@ def week_view(
         "lesson__groups__parent_groups",
         "lesson__groups__parent_groups__owners",
     )
+    events = Event.objects.in_week(wanted_week)
+    extra_lessons = ExtraLesson.objects.in_week(wanted_week)
 
-    lesson_periods_query_exists = True
+    query_exists = True
     if type_ and id_:
         if isinstance(instance, HttpResponseNotFound):
             return HttpResponseNotFound()
@@ -208,15 +216,26 @@ def week_view(
         type_ = TimetableType.from_string(type_)
 
         lesson_periods = lesson_periods.filter_from_type(type_, instance)
+        events = events.filter_from_type(type_, instance)
+        extra_lessons = extra_lessons.filter_from_Type(type_, instance)
+
     elif hasattr(request, "user") and hasattr(request.user, "person"):
         if request.user.person.lessons_as_teacher.exists():
             lesson_periods = lesson_periods.filter_teacher(request.user.person)
+            events = events.filter_teacher(request.user.person)
+            extra_lessons = extra_lessons.filter_teacher(request.user.person)
+
             type_ = TimetableType.TEACHER
         else:
             lesson_periods = lesson_periods.filter_participant(request.user.person)
+            events = events.filter_participant(request.user.person)
+            extra_lessons = extra_lessons.filter_participant(request.user.person)
+
     else:
-        lesson_periods_query_exists = False
+        query_exists = False
         lesson_periods = None
+        events = None
+        extra_lessons = None
 
     # Add a form to filter the view
     if type_:
@@ -245,35 +264,21 @@ def week_view(
 
     extra_marks = ExtraMark.objects.all()
 
-    if lesson_periods_query_exists:
+    if query_exists:
         lesson_periods_pk = list(lesson_periods.values_list("pk", flat=True))
-        lesson_periods = (
-            LessonPeriod.objects.prefetch_related(
-                Prefetch(
-                    "documentations",
-                    queryset=LessonDocumentation.objects.filter(
-                        week=wanted_week.week, year=wanted_week.year
-                    ),
-                )
-            )
-            .filter(pk__in=lesson_periods_pk)
-            .annotate_week(wanted_week)
-            .annotate(
-                has_documentation=Exists(
-                    LessonDocumentation.objects.filter(
-                        ~Q(topic__exact=""),
-                        lesson_period=OuterRef("pk"),
-                        week=wanted_week.week,
-                        year=wanted_week.year,
-                    )
-                )
-            )
-            .order_by("period__weekday", "period__period")
-        )
+        lesson_periods = annotate_documentations(LessonPeriod, wanted_week, lesson_periods_pk)
+
+        events_pk = list(events.values_list("pk", flat=True))
+        events = annotate_documentations(Event, wanted_week, events_pk)
+
+        extra_lessons_pk = list(extra_lessons.values_list("pk", flat=True))
+        extra_lessons = annotate_documentations(ExtraLesson, wanted_week, extra_lessons_pk)
     else:
         lesson_periods_pk = []
+        events_pk = []
+        extra_lessons_pk = []
 
-    if lesson_periods_pk:
+    if lesson_periods_pk or events_pk or extra_lessons_pk:
         # Aggregate all personal notes for this group and week
         persons_qs = Person.objects.filter(is_active=True)
 
@@ -282,7 +287,29 @@ def week_view(
         elif group:
             persons_qs = persons_qs.filter(member_of=group)
         else:
-            persons_qs = persons_qs.filter(member_of__lessons__lesson_periods__in=lesson_periods_pk)
+            persons_qs = persons_qs.filter(
+                Q(member_of__lessons__lesson_periods__in=lesson_periods_pk)
+                | Q(member_of__events__in=events_pk)
+                | Q(member_of__extra_lessons__in=extra_lessons_pk)
+            )
+
+        personal_notes_q = (
+            Q(
+                personal_notes__week=wanted_week.week,
+                personal_notes__year=wanted_week.year,
+                personal_notes__lesson_period__in=lesson_periods_pk,
+            )
+            | Q(
+                personal_notes__event__date_start__lte=wanted_week[6],
+                personal_notes__event__date_end__gte=wanted_week[0],
+                personal_notes__event__in=events_pk,
+            )
+            | Q(
+                personal_notes__extra_lesson__week=wanted_week.week,
+                personal_notes__extra_lesson__year=wanted_week.year,
+                personal_notes__extra_lesson__in=extra_lessons_pk,
+            )
+        )
 
         persons_qs = (
             persons_qs.distinct()
@@ -290,9 +317,21 @@ def week_view(
                 Prefetch(
                     "personal_notes",
                     queryset=PersonalNote.objects.filter(
-                        week=wanted_week.week,
-                        year=wanted_week.year,
-                        lesson_period__in=lesson_periods_pk,
+                        Q(
+                            week=wanted_week.week,
+                            year=wanted_week.year,
+                            lesson_period__in=lesson_periods_pk,
+                        )
+                        | Q(
+                            event__date_start__lte=wanted_week[6],
+                            event__date_end__gte=wanted_week[0],
+                            event__in=events_pk,
+                        )
+                        | Q(
+                            extra_lesson__week=wanted_week.week,
+                            extra_lesson__year=wanted_week.year,
+                            extra_lesson__in=extra_lessons_pk,
+                        )
                     ),
                 ),
                 "member_of__owners",
@@ -300,44 +339,25 @@ def week_view(
             .annotate(
                 absences_count=Count(
                     "personal_notes",
-                    filter=Q(
-                        personal_notes__lesson_period__in=lesson_periods_pk,
-                        personal_notes__week=wanted_week.week,
-                        personal_notes__year=wanted_week.year,
-                        personal_notes__absent=True,
-                    ),
+                    filter=personal_notes_q & Q(personal_notes__absent=True,),
                     distinct=True,
                 ),
                 unexcused_count=Count(
                     "personal_notes",
-                    filter=Q(
-                        personal_notes__lesson_period__in=lesson_periods_pk,
-                        personal_notes__week=wanted_week.week,
-                        personal_notes__year=wanted_week.year,
-                        personal_notes__absent=True,
-                        personal_notes__excused=False,
-                    ),
+                    filter=personal_notes_q
+                    & Q(personal_notes__absent=True, personal_notes__excused=False,),
                     distinct=True,
                 ),
                 tardiness_sum=Subquery(
-                    Person.objects.filter(
-                        pk=OuterRef("pk"),
-                        personal_notes__lesson_period__in=lesson_periods_pk,
-                        personal_notes__week=wanted_week.week,
-                        personal_notes__year=wanted_week.year,
-                    )
+                    Person.objects.filter(personal_notes_q)
+                    .filter(pk=OuterRef("pk"),)
                     .distinct()
                     .annotate(tardiness_sum=Sum("personal_notes__late"))
                     .values("tardiness_sum")
                 ),
                 tardiness_count=Count(
                     "personal_notes",
-                    filter=Q(
-                        personal_notes__lesson_period__in=lesson_periods_pk,
-                        personal_notes__week=wanted_week.week,
-                        personal_notes__year=wanted_week.year,
-                    )
-                    & ~Q(personal_notes__late=0),
+                    filter=personal_notes_q & ~Q(personal_notes__late=0),
                     distinct=True,
                 ),
             )
@@ -348,12 +368,7 @@ def week_view(
                 **{
                     extra_mark.count_label: Count(
                         "personal_notes",
-                        filter=Q(
-                            personal_notes__lesson_period__in=lesson_periods_pk,
-                            personal_notes__week=wanted_week.week,
-                            personal_notes__year=wanted_week.year,
-                            personal_notes__extra_marks=extra_mark,
-                        ),
+                        filter=personal_notes_q & Q(personal_notes__extra_marks=extra_mark,),
                         distinct=True,
                     )
                 }
@@ -368,13 +383,42 @@ def week_view(
     context["extra_marks"] = extra_marks
     context["week"] = wanted_week
     context["weeks"] = get_weeks_for_year(year=wanted_week.year)
+
     context["lesson_periods"] = lesson_periods
+    context["events"] = events
+    context["extra_lessons"] = extra_lessons
+
     context["persons"] = persons
     context["group"] = group
     context["select_form"] = select_form
     context["instance"] = instance
     context["weekdays"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES, wanted_week)
 
+    regrouped_objects = {}
+
+    for register_object in list(lesson_periods) + list(extra_lessons):
+        regrouped_objects.setdefault(register_object.period.weekday, [])
+        regrouped_objects[register_object.period.weekday].append(register_object)
+
+    for event in events:
+        weekday_from = event.get_start_weekday(wanted_week)
+        weekday_to = event.get_end_weekday(wanted_week)
+        print(weekday_from, weekday_to)
+
+        for weekday in range(weekday_from, weekday_to + 1):
+            # Make a copy in order to keep the annotation only on this weekday
+            event_copy = deepcopy(event)
+            event.annotate_day(wanted_week[weekday])
+
+            regrouped_objects.setdefault(weekday, [])
+            regrouped_objects[weekday].append(event_copy)
+
+    # Sort register objects
+    for weekday in regrouped_objects.keys():
+        to_sort = regrouped_objects[weekday]
+        regrouped_objects[weekday] = sorted(to_sort, key=register_objects_sorter)
+    context["regrouped_objects"] = regrouped_objects
+
     week_prev = wanted_week - 1
     week_next = wanted_week + 1
     args_prev = [week_prev.year, week_prev.week]
-- 
GitLab