diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py
index 45aecf55b7553f0bd61c74cc4fee19e65f05b8d9..3b9d107346d823ccfd24761db78b28c949f3c0ac 100644
--- a/aleksis/apps/chronos/managers.py
+++ b/aleksis/apps/chronos/managers.py
@@ -946,3 +946,7 @@ class LessonEventQuerySet(RecurrencePolymorphicQuerySet):
     def not_amending(self) -> "LessonEventQuerySet":
         """Get all lesson events that are not amending other events."""
         return self.filter(amends__isnull=True)
+
+    def amending(self) -> "LessonEventQuerySet":
+        """Get all lesson events that are amending other events."""
+        return self.filter(amends__isnull=False)
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 547978fc3759a83c54492d7b14fddcfc0c7e06b1..4d61b16c611de2dcee4a0f63e58cb52a2833d90e 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -1222,8 +1222,6 @@ class AutomaticPlan(LiveDocument):
 
         context = get_substitutions_context_data(
             wanted_day=date.today(),
-            request=None,
-            is_print=True,
             number_of_days=self.number_of_days,
             show_header_box=self.show_header_box,
         )
@@ -1562,6 +1560,7 @@ class LessonEvent(CalendarEvent):
             type_ = params.get("type", None)
             not_amended = params.get("not_amended", False)
             not_amending = params.get("not_amending", False)
+            amending = params.get("amending", False)
             own = params.get("own", False)
 
             if not_amended:
@@ -1570,6 +1569,9 @@ class LessonEvent(CalendarEvent):
             if not_amending:
                 objs = objs.not_amending()
 
+            if amending:
+                objs = objs.amending()
+
             if request and "own" in params:
                 if own:
                     objs = objs.for_person(request.user.person)
diff --git a/aleksis/apps/chronos/rules.py b/aleksis/apps/chronos/rules.py
index 85a0d1c4a6b7e4bdef21edcaf1a294aa22d473c3..d814dd5b22adc24acdbbfc4c496875983bb1a626 100644
--- a/aleksis/apps/chronos/rules.py
+++ b/aleksis/apps/chronos/rules.py
@@ -44,7 +44,6 @@ add_perm("chronos.delete_substitution_rule", delete_substitution_predicate)
 # View substitutions
 view_substitutions_predicate = has_person & (
     has_global_perm("chronos.view_lessonsubstitution")
-    | has_any_object("chronos.view_lessonsubstitution", LessonSubstitution)
 )
 add_perm("chronos.view_substitutions_rule", view_substitutions_predicate)
 
diff --git a/aleksis/apps/chronos/util/build.py b/aleksis/apps/chronos/util/build.py
index 7946a3eaac461662fc34f57c454338dde7d52c00..62cdb0847a9707156975d681e603bae9e6429935 100644
--- a/aleksis/apps/chronos/util/build.py
+++ b/aleksis/apps/chronos/util/build.py
@@ -1,9 +1,8 @@
 from collections import OrderedDict
-from datetime import date
+from datetime import date, datetime, time
 from typing import Union
 
 from django.apps import apps
-from django.db.models import Q
 
 from calendarweek import CalendarWeek
 
@@ -382,37 +381,37 @@ def build_timetable(
     return rows
 
 
-def build_substitutions_list(wanted_day: date) -> list[dict]:
+def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person], set[Group]]:
     rows = []
-
-    subs = (
-        LessonEvent.objects.exclude(amends=None)
-        .filter(Q(datetime_start__date=wanted_day) | Q(date_start=wanted_day))
-        .order_by("datetime_start", "date_start")
+    affected_teachers = set()
+    affected_groups = set()
+
+    lesson_events = LessonEvent.get_single_events(
+        datetime.combine(wanted_day, time.min),
+        datetime.combine(wanted_day, time.max),
+        params={"amending": True},
+        with_reference_object=True,
     )
 
-    for i, sub in enumerate(subs):
-        sort_a = sub.group_names
-
-        # FIXME? Looks hacky. sub.amends returns a CalendarEvent, but a LessonEvent is needed
-        sub.amends = LessonEvent.objects.get(pk=sub.amends.pk)
+    for lesson_event in lesson_events:
+        affected_teachers.update(lesson_event["REFERENCE_OBJECT"].teachers.all())
+        affected_teachers.update(lesson_event["REFERENCE_OBJECT"].amends.teachers.all())
+        affected_groups.update(lesson_event["REFERENCE_OBJECT"].groups.all())
+        affected_groups.update(lesson_event["REFERENCE_OBJECT"].amends.groups.all())
 
         row = {
             "type": "substitution",
-            "sort_a": sort_a,
-            "sort_b": str(sub.datetime_start if sub.datetime_start else sub.date_start),
-            "el": sub,
+            "sort_a": lesson_event["REFERENCE_OBJECT"].group_names,
+            "sort_b": str(lesson_event["DTSTART"]),
+            "el": lesson_event,
         }
 
         rows.append(row)
 
-    # Sort all items
-    def sorter(row: dict):
-        return row["sort_a"] + row["sort_b"]
+    rows.sort(key=lambda row: row["sort_a"] + row["sort_b"])
 
-    rows.sort(key=sorter)
-
-    return rows
+    print(rows)
+    return rows, affected_teachers, affected_groups
 
 
 def build_weekdays(
diff --git a/aleksis/apps/chronos/util/chronos_helpers.py b/aleksis/apps/chronos/util/chronos_helpers.py
index 81dc4fc9e979a1f09135c4a2eee08174ef8ac5fa..67e7626f65bbb4bfde2280e4aab64af236eb03f5 100644
--- a/aleksis/apps/chronos/util/chronos_helpers.py
+++ b/aleksis/apps/chronos/util/chronos_helpers.py
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Optional
 from django.db.models import Count, Q
 from django.http import HttpRequest, HttpResponseNotFound
 from django.shortcuts import get_object_or_404
-from django.urls import reverse
 
 from guardian.core import ObjectPermissionChecker
 
@@ -21,7 +20,6 @@ from ..models import (
     SupervisionSubstitution,
 )
 from .build import build_substitutions_list
-from .js import date_unix
 
 if TYPE_CHECKING:
     from django.contrib.auth import get_user_model
@@ -159,8 +157,6 @@ def get_rooms(user: "User"):
 
 def get_substitutions_context_data(
     wanted_day: date,
-    request: Optional[HttpRequest] = None,
-    is_print: bool = False,
     number_of_days: Optional[int] = None,
     show_header_box: Optional[bool] = None,
 ):
@@ -177,33 +173,26 @@ def get_substitutions_context_data(
     )
     day_contexts = {}
 
-    if is_print:
-        next_day = wanted_day
-        for _i in range(day_number):
-            day_contexts[next_day] = {"day": next_day}
-            next_day = next_day + timedelta(days=1)
-    else:
-        day_contexts = {wanted_day: {"day": wanted_day}}
+    day = wanted_day
+    for _i in range(day_number):
+        day_contexts[day] = {"day": day}
 
-    for day in day_contexts:
-        subs = build_substitutions_list(day)
+        subs, affected_teachers, affected_groups = build_substitutions_list(day)
         day_contexts[day]["substitutions"] = subs
 
-        day_contexts[day]["announcements"] = Announcement.for_timetables().on_date(day)
+        day_contexts[day]["announcements"] = Announcement.objects.on_date(day)
 
         if show_header_box:
             absences = Absence.objects.on_day(day)
             day_contexts[day]["absent_teachers"] = absences.absent_teachers()
             day_contexts[day]["absent_groups"] = absences.absent_groups()
+            day_contexts[day]["affected_teachers"] = sorted(
+                affected_teachers, key=lambda t: t.short_name or t.full_name
+            )
+            day_contexts[day]["affected_groups"] = affected_groups
 
-    if not is_print:
-        context = day_contexts[wanted_day]
-        context["datepicker"] = {
-            "date": date_unix(wanted_day),
-            "dest": reverse("substitutions"),
-        }
+        day = day + timedelta(days=1)
 
-    else:
-        context["days"] = day_contexts
+    context["days"] = day_contexts
 
     return context