From b5c52a83d4c4e2b654c3b69499c25e7655d11572 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Thu, 9 Apr 2020 17:59:03 +0200
Subject: [PATCH] Create new build mechanism for substitution lists (in order
 to implement events etc. later)

- Use new template for cancellation badge (badge.html)
- Show more details like comment or specific cancellation types
- Fix some rules in substitution templates
- Fix cycle mechanism in substitutions_print.html
---
 aleksis/apps/chronos/models.py                | 16 +++-
 .../templates/chronos/partials/lesson.html    |  3 +-
 .../chronos/partials/subs/badge.html          |  7 ++
 .../templates/chronos/partials/subs/room.html | 79 ++++++++-----------
 .../chronos/partials/subs/subject.html        | 46 +++++------
 .../chronos/partials/subs/teachers.html       | 27 +++----
 .../templates/chronos/substitutions.html      | 31 ++++----
 .../chronos/substitutions_print.html          | 35 ++++----
 aleksis/apps/chronos/util/build.py            | 28 ++++++-
 aleksis/apps/chronos/views.py                 |  6 +-
 10 files changed, 155 insertions(+), 123 deletions(-)
 create mode 100644 aleksis/apps/chronos/templates/chronos/partials/subs/badge.html

diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index ebb3fb74..194b5fc9 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -4,6 +4,7 @@ from collections import OrderedDict
 from datetime import date, datetime, timedelta, time
 from typing import Dict, Optional, Tuple, Union
 
+from constance import config
 from django.core import validators
 from django.core.exceptions import ValidationError
 from django.db import models
@@ -442,6 +443,18 @@ class Lesson(ExtensibleModel):
     def group_names(self, sep: Optional[str] = ", ") -> str:
         return sep.join([group.short_name for group in self.groups.all()])
 
+    @property
+    def groups_to_show(self) -> models.QuerySet:
+        groups = self.groups.all()
+        if groups.count() == 1 and groups[0].parent_groups.all() and config.CHRONOS_USE_PARENT_GROUPS:
+            return groups[0].parent_groups.all()
+        else:
+            return groups
+
+    @property
+    def groups_to_show_names(self, sep: Optional[str] = ", ") -> str:
+        return sep.join([group.short_name for group in self.groups_to_show])
+
     def get_calendar_week(self, week: int):
         year = self.date_start.year
         if week < int(self.date_start.strftime("%V")):
@@ -485,9 +498,10 @@ class LessonSubstitution(ExtensibleModel):
 
     @property
     def type_(self):
-        # TODO: Add cases events and supervisions
         if self.cancelled:
             return "cancellation"
+        elif self.cancelled_for_teachers:
+            return "cancellation_for_teachers"
         else:
             return "substitution"
 
diff --git a/aleksis/apps/chronos/templates/chronos/partials/lesson.html b/aleksis/apps/chronos/templates/chronos/partials/lesson.html
index 2c5bcd7b..66da0b62 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/lesson.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/lesson.html
@@ -34,8 +34,7 @@
           {% endif %}
 
           {# Badge #}
-          <span class="badge new green darken-2">{% if sub.cancelled_for_teachers %}
-            {% trans "Cancelled for teachers" %}{% else %}{% trans "Cancelled" %}{% endif %}</span>
+          {% include "chronos/partials/subs/badge.html" with sub=sub %}
 
         {% else %}
           {# Display sub #}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html b/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
new file mode 100644
index 00000000..c04c9dd3
--- /dev/null
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/badge.html
@@ -0,0 +1,7 @@
+{% load i18n %}
+
+{% if sub.cancelled %}
+  <span class="badge new green">{% trans "Cancelled" %}</span>
+{% elif item.el.cancelled_for_teachers %}
+  <span class="badge new green">{% trans "Cancelled for teachers" %}</span>
+{% endif %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/room.html b/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
index cf637b92..e7535af9 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/room.html
@@ -1,47 +1,36 @@
-{% if not sub.is_event %}
-  {% if sub.sub.type == 3 %}
-    {# Supervisement #}
-    {{ sub.sub.corridor.name }}
-  {% elif sub.sub.type == 1 or sub.sub.type == 2 %}
-    {# Canceled lesson: no room #}
-  {% elif sub.room and sub.lesson_period.room %}
-    {# New and old room available #}
-    <span class="tooltipped" data-position="bottom"
-          data-tooltip="{{ sub.lesson_period.room.name }} → {{ sub.lesson_period.room.name }}">
-            <a href="{% url "timetable" "room" sub.lesson_period.room.pk %}">
-                <s>{{ sub.lesson_period.room.short_name }}</s>
-            </a>
-            →
-            <a href="{% url "timetable" "room" sub.room.pk %}">
-                <strong>{{ sub.room.short_name }}</strong>
-            </a>
-        </span>
-  {% elif sub.room and not sub.lesson_period.room %}
-    {# Only new room available #}
-    <span class="tooltipped" data-position="bottom"
-          data-tooltip="{{ sub.room.name }}">
-            <a href="{% url "timetable" "room" sub.room.pk %}">
-                {{ sub.room.short_name }}
-            </a>
-        </span>
-  {% elif not sub.room and not sub.lesson_period.room %}
-    {# Nothing to view #}
-  {% else %}
-    {# Only old room available #}
-    <span class="tooltipped" data-position="bottom"
-          data-tooltip="{{ sub.lesson_period.room.name }}">
-            <a href="{% url "timetable" "room" sub.lesson_period.room.pk %}">
-                {{ sub.lesson_period.room.short_name }}
-            </a>
-        </span>
-  {% endif %}
+{% if sub.cancelled or sub.cancelled_for_teachers %}
+  {# Canceled lesson: no room #}
+{% elif sub.room and sub.lesson_period.room %}
+  {# New and old room available #}
+  <span class="tooltipped" data-position="bottom"
+        data-tooltip="{{ sub.lesson_period.room.name }} → {{ sub.lesson_period.room.name }}"
+        title="{{ sub.lesson_period.room.name }} → {{ sub.lesson_period.room.name }}">
+    <a href="{% url "timetable" "room" sub.lesson_period.room.pk %}">
+        <s>{{ sub.lesson_period.room.short_name }}</s>
+    </a>
+    →
+    <a href="{% url "timetable" "room" sub.room.pk %}">
+        <strong>{{ sub.room.short_name }}</strong>
+    </a>
+  </span>
+{% elif sub.room and not sub.lesson_period.room %}
+  {# Only new room available #}
+  <span class="tooltipped" data-position="bottom"
+        data-tooltip="{{ sub.room.name }}"
+        title="{{ sub.room.name }}">
+    <a href="{% url "timetable" "room" sub.room.pk %}">
+        {{ sub.room.short_name }}
+    </a>
+  </span>
+{% elif not sub.room and not sub.lesson_period.room %}
+  {# Nothing to view #}
 {% else %}
-  {% for room in sub.rooms %}
-    <span class="tooltipped" data-position="bottom"
-          data-tooltip="{{ room.name }}">
-            <a href="{% url "timetable_smart_plan" "room" room.id %}">
-                <strong>{{ room.short_name }}{% if not forloop.last %},{% endif %}</strong>
-            </a>
-        </span>
-  {% endfor %}
+  {# Only old room available #}
+  <span class="tooltipped" data-position="bottom"
+        data-tooltip="{{ sub.lesson_period.room.name }}"
+        title="{{ sub.lesson_period.room.name }}">
+    <a href="{% url "timetable" "room" sub.lesson_period.room.pk %}">
+        {{ sub.lesson_period.room.short_name }}
+    </a>
+  </span>
 {% endif %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html b/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
index 6fe8eb5a..2e61d415 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/subject.html
@@ -1,28 +1,24 @@
 {% load i18n %}
 
-{% if not sub.sub.is_event %}
-  {% if sub.sub.type == 3 %}
-    <strong>{% trans "Supervision" %}</strong>
-  {% elif not sub.lesson_period.lesson.subject and not sub.subject %}
-  {% elif sub.sub.type == 1 or sub.sub.type == 2 %}
-    <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.lesson_period.lesson.subject.name }}">
-      <s>{{ sub.lesson_period.lesson.subject.abbrev }}</s>
-    </span>
-  {% elif sub.subject and sub.lesson_period.lesson.subject %}
-    <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.lesson_period.lesson.subject.name }}">
-      <s>{{ sub.lesson_period.lesson.subject.abbrev }}</s>
-    </span>
-    →
-    <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.subject.name }}">
-      <strong>{{ sub.subject.abbrev }}</strong>
-    </span>
-  {% elif sub.subject and not sub.lesson_period.lesson.subject %}
-    <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.subject.name }}">
-      <strong>{{ sub.subject.abbrev }}</strong>
-    </span>
-  {% else %}
-    <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.lesson_period.lesson.subject.name }}">
-      <strong>{{ sub.lesson_period.lesson.subject.abbrev }}</strong>
-    </span>
-  {% endif %}
+{% if not sub.lesson_period.lesson.subject and not sub.subject %}
+{% elif sub.cancelled or sub.cancelled_for_teachers %}
+  <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.lesson_period.lesson.subject.name }}">
+    <s>{{ sub.lesson_period.lesson.subject.abbrev }}</s>
+  </span>
+{% elif sub.subject and sub.lesson_period.lesson.subject %}
+  <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.lesson_period.lesson.subject.name }}">
+    <s>{{ sub.lesson_period.lesson.subject.abbrev }}</s>
+  </span>
+  →
+  <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.subject.name }}">
+    <strong>{{ sub.subject.abbrev }}</strong>
+  </span>
+{% elif sub.subject and not sub.lesson_period.lesson.subject %}
+  <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.subject.name }}">
+    <strong>{{ sub.subject.abbrev }}</strong>
+  </span>
+{% else %}
+  <span data-position="bottom" class="tooltipped" data-tooltip="{{ sub.lesson_period.lesson.subject.name }}">
+    <strong>{{ sub.lesson_period.lesson.subject.abbrev }}</strong>
+  </span>
 {% endif %}
diff --git a/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html b/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html
index ec17d8b8..69002c5d 100644
--- a/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html
+++ b/aleksis/apps/chronos/templates/chronos/partials/subs/teachers.html
@@ -1,19 +1,16 @@
-{% if not sub.is_event %}
-  {% if sub.sub.type == 1 and sub.lesson_period.lesson.teachers.all %}
+{% if sub.cancelled and sub.lesson_period.lesson.teachers.all %}
+  {% include "chronos/partials/teachers.html" with teachers=sub.lesson_period.lesson.teachers.all %}
+{% elif sub.teachers.all and sub.lesson_period.lesson.teachers.all %}
+  <s>
     {% include "chronos/partials/teachers.html" with teachers=sub.lesson_period.lesson.teachers.all %}
-  {% elif sub.teachers.all and sub.lesson_period.lesson.teachers.all %}
-    <s>
-      {% include "chronos/partials/teachers.html" with teachers=sub.lesson_period.lesson.teachers.all %}
-    </s>
-    →
-    <strong>
-      {% include "chronos/partials/teachers.html" with teachers=sub.teachers.all %}
-    </strong>
-  {% elif sub.teachers.all and not sub.lesson_period.lesson.teachers.all %}
+  </s>
+  →
+  <strong>
     {% include "chronos/partials/teachers.html" with teachers=sub.teachers.all %}
-  {% elif sub.lesson_period.lesson.teachers.all %}
-    {% include "chronos/partials/teachers.html" with teachers=sub.lesson_period.lesson.teachers.all %}
-  {% endif %}
-{% else %}
+  </strong>
+{% elif sub.teachers.all and not sub.lesson_period.lesson.teachers.all %}
   {% include "chronos/partials/teachers.html" with teachers=sub.teachers.all %}
+{% elif sub.lesson_period.lesson.teachers.all %}
+  {% include "chronos/partials/teachers.html" with teachers=sub.lesson_period.lesson.teachers.all %}
 {% endif %}
+
diff --git a/aleksis/apps/chronos/templates/chronos/substitutions.html b/aleksis/apps/chronos/templates/chronos/substitutions.html
index febc6aeb..931eac1d 100644
--- a/aleksis/apps/chronos/templates/chronos/substitutions.html
+++ b/aleksis/apps/chronos/templates/chronos/substitutions.html
@@ -57,37 +57,38 @@
         </p>
       </td>
     {% endif %}
-    {% for sub in substitutions %}
-      <tr class="{% if sub.type_ == "cancellation" %}green-text{% else %}black-text{% endif %}">
+    {% for item in substitutions %}
+      <tr class="
+      {% if item.type == "substitution" %}
+        {% if item.el.cancelled or item.el.cancelled_for_teachers %}green-text{% else %}black-text{% endif %}
+      {% endif %}
+      ">
         {# TODO: Extend support for blue and purple (supervisions and events) #}
         <td>
-          {% include "chronos/partials/groups.html" with groups=sub.lesson_period.lesson.groups.all %}
+          {% include "chronos/partials/groups.html" with groups=item.el.lesson_period.lesson.groups.all %}
         </td>
         <td>
           <strong>
-            {{ sub.lesson_period.period.period }}.
+            {{ item.el.lesson_period.period.period }}.
           </strong>
         </td>
         <td>
-          {% include "chronos/partials/subs/teachers.html" %}
+          {% include "chronos/partials/subs/teachers.html" with sub=item.el %}
         </td>
         <td>
-          {% include "chronos/partials/subs/subject.html" %}
+          {% include "chronos/partials/subs/subject.html" with sub=item.el %}
         </td>
         <td>
-          {% include "chronos/partials/subs/room.html" %}
+          {% include "chronos/partials/subs/room.html" with sub=item.el %}
         </td>
         <td>
-          {% if sub.cancelled %}
-            {# TODO: Support other cases#}
-            <span class="badge new green hide-on-med-and-up">{% trans "Cancelled" %}</span>
-          {% endif %}
-          {#          <em>{{ sub.text|default:"" }}</em>#}
+          <span class="hide-on-med-and-up">
+            {% include "chronos/partials/subs/badge.html" with sub=item.el %}
+          </span>
+          <em>{{ sub.comment|default:"" }}</em>
         </td>
         <td class="hide-on-small-and-down">
-          {% if sub.cancelled %}
-            <span class="badge new green darken-2">{% trans "Cancelled" %}</span>
-          {% endif %}
+          {% include "chronos/partials/subs/badge.html" with sub=item.el %}
         </td>
       </tr>
     {% endfor %}
diff --git a/aleksis/apps/chronos/templates/chronos/substitutions_print.html b/aleksis/apps/chronos/templates/chronos/substitutions_print.html
index 400124f2..1692efe7 100644
--- a/aleksis/apps/chronos/templates/chronos/substitutions_print.html
+++ b/aleksis/apps/chronos/templates/chronos/substitutions_print.html
@@ -46,38 +46,39 @@
       {% endif %}
 
       <tbody>
-      {% for sub in c.substitutions %}
-        <tr class="{% if sub.type_ == "cancellation" %}green-text{% else %}black-text{% endif %} ">
+      {% for item in c.substitutions %}
+        {% ifchanged item.el.lesson_period.lesson.groups_to_show_names %}
+          </tbody>
+          <tbody class="{% cycle "striped" "not-striped" %}">
+        {% endifchanged %}
+
+        <tr class="
+        {% if item.type == "substitution" %}
+          {% if item.el.cancelled or item.el.cancelled_for_teachers %}green-text{% else %}black-text{% endif %}
+        {% endif %}
+        ">
           <td>
-            {% include "chronos/partials/groups.html" with groups=sub.lesson_period.lesson.groups.all %}
+            {% include "chronos/partials/groups.html" with groups=item.el.lesson_period.lesson.groups.all %}
           </td>
           <td>
             <strong>
-              {{ sub.lesson_period.period.period }}.
+              {{ item.el.lesson_period.period.period }}.
             </strong>
           </td>
           <td>
-            {% include "chronos/partials/subs/teachers.html" %}
+            {% include "chronos/partials/subs/teachers.html" with sub=item.el %}
           </td>
           <td>
-            {% include "chronos/partials/subs/subject.html" %}
+            {% include "chronos/partials/subs/subject.html" with sub=item.el %}
           </td>
           <td>
-            {% include "chronos/partials/subs/room.html" %}
+            {% include "chronos/partials/subs/room.html" with sub=item.el %}
           </td>
           <td>
-            {% if sub.cancelled %}
-              {# TODO: Support other cases#}
-              <span class="badge new green">{% trans "Cancelled" %}</span>
-            {% endif %}
-            {#          <em>{{ sub.text|default:"" }}</em>#}
+            {% include "chronos/partials/subs/badge.html" with sub=item.el %}
+            <em>{{ sub.comment|default:"" }}</em>
           </td>
         </tr>
-
-        {% ifchanged sub.lesson_period.lesson.groups %}
-          </tbody>
-          <tbody class="{% cycle "not-striped" "striped" %}">
-        {% endifchanged %}
       {% endfor %}
       </tbody>
     </table>
diff --git a/aleksis/apps/chronos/util/build.py b/aleksis/apps/chronos/util/build.py
index b45b8510..57cd4707 100644
--- a/aleksis/apps/chronos/util/build.py
+++ b/aleksis/apps/chronos/util/build.py
@@ -1,6 +1,6 @@
 from collections import OrderedDict
 from datetime import date
-from typing import Union
+from typing import Union, List
 
 from calendarweek import CalendarWeek
 from django.apps import apps
@@ -11,6 +11,7 @@ LessonPeriod = apps.get_model("chronos", "LessonPeriod")
 TimePeriod = apps.get_model("chronos", "TimePeriod")
 Break = apps.get_model("chronos", "Break")
 Supervision = apps.get_model("chronos", "Supervision")
+LessonSubstitution = apps.get_model("chronos", "LessonSubstitution")
 
 
 def build_timetable(
@@ -155,3 +156,28 @@ def build_timetable(
             rows.append(row)
 
     return rows
+
+
+def build_substitutions_list(wanted_day: date) -> List[dict]:
+    rows = []
+
+    subs = LessonSubstitution.objects.on_day(wanted_day).order_by(
+        "lesson_period__lesson__groups", "lesson_period__period"
+    )
+
+    for sub in subs:
+        if not sub.cancelled_for_teachers:
+            sort_a = sub.lesson_period.lesson.group_names
+        else:
+            sort_a = "Z.{}".format(sub.lesson_period.lesson.teacher_names)
+
+        row = {
+            "type": "substitution",
+            "sort_a": sort_a,
+            "sort_b": "{}".format(sub.lesson_period.period.period),
+            "el": sub,
+        }
+
+        rows.append(row)
+
+    return rows
diff --git a/aleksis/apps/chronos/views.py b/aleksis/apps/chronos/views.py
index de931825..b59262ac 100644
--- a/aleksis/apps/chronos/views.py
+++ b/aleksis/apps/chronos/views.py
@@ -18,7 +18,7 @@ from aleksis.core.util import messages
 from .forms import LessonSubstitutionForm
 from .models import LessonPeriod, LessonSubstitution, TimePeriod, Room
 from .tables import LessonsTable
-from .util.build import build_timetable
+from .util.build import build_timetable, build_substitutions_list
 from .util.js import date_unix
 from .util.date import CalendarWeek, get_weeks_for_year
 from aleksis.core.util.core_helpers import has_person
@@ -295,12 +295,14 @@ def substitutions(
         day_contexts = {wanted_day: {"day": wanted_day}}
 
     for day in day_contexts:
-        subs = LessonSubstitution.objects.on_day(day).order_by("lesson_period__lesson__groups", "lesson_period__period")
+        subs = build_substitutions_list(day)
         day_contexts[day]["substitutions"] = subs
 
         day_contexts[day]["announcements"] = Announcement.for_timetables().on_date(day).filter(show_in_timetables=True)
 
         if config.CHRONOS_SUBSTITUTIONS_SHOW_HEADER_BOX:
+            subs = LessonSubstitution.objects.on_day(day).order_by("lesson_period__lesson__groups",
+                                                                   "lesson_period__period")
             day_contexts[day]["affected_teachers"] = subs.affected_teachers()
             day_contexts[day]["affected_groups"] = subs.affected_groups()
 
-- 
GitLab