From 63dcaaa264798ba46ac25302830260477e75e23d Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Wed, 28 Feb 2024 19:59:11 +0100
Subject: [PATCH] Drop substitution models

---
 aleksis/apps/lesrooster/apps.py               |   8 +-
 .../migrations/0016_remove_substitutions.py   |  35 ++++
 aleksis/apps/lesrooster/models.py             | 197 ------------------
 aleksis/apps/lesrooster/rules.py              |  35 ----
 .../apps/lesrooster/util/signal_handlers.py   |  15 +-
 5 files changed, 40 insertions(+), 250 deletions(-)
 create mode 100644 aleksis/apps/lesrooster/migrations/0016_remove_substitutions.py

diff --git a/aleksis/apps/lesrooster/apps.py b/aleksis/apps/lesrooster/apps.py
index 7f07c184..17c326c2 100644
--- a/aleksis/apps/lesrooster/apps.py
+++ b/aleksis/apps/lesrooster/apps.py
@@ -26,13 +26,11 @@ class DefaultConfig(AppConfig):
         # Configure change tracking for models to sync changes with LessonEvent in Chronos
         from .models import (
             Lesson,
-            Substitution,
             Supervision,
-            SupervisionSubstitution,
             ValidityRange,
         )
 
-        models = [Lesson, Supervision, Substitution, SupervisionSubstitution]
+        models = [Lesson, Supervision]
 
         for model in models:
             signals.post_save.connect(
@@ -53,10 +51,6 @@ class DefaultConfig(AppConfig):
             m2m_changed_handler,
             sender=Supervision.rooms.through,
         )
-        signals.m2m_changed.connect(
-            m2m_changed_handler,
-            sender=Substitution.rooms.through,
-        )
 
         signals.post_save.connect(create_time_grid_for_new_validity_range, sender=ValidityRange)
 
diff --git a/aleksis/apps/lesrooster/migrations/0016_remove_substitutions.py b/aleksis/apps/lesrooster/migrations/0016_remove_substitutions.py
new file mode 100644
index 00000000..d070033c
--- /dev/null
+++ b/aleksis/apps/lesrooster/migrations/0016_remove_substitutions.py
@@ -0,0 +1,35 @@
+# Generated by Django 4.2.10 on 2024-02-28 18:58
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lesrooster', '0015_drop_site'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='supervisionsubstitution',
+            name='subject',
+        ),
+        migrations.RemoveField(
+            model_name='supervisionsubstitution',
+            name='supervision',
+        ),
+        migrations.RemoveField(
+            model_name='supervisionsubstitution',
+            name='supervision_event',
+        ),
+        migrations.RemoveField(
+            model_name='supervisionsubstitution',
+            name='teachers',
+        ),
+        migrations.DeleteModel(
+            name='Substitution',
+        ),
+        migrations.DeleteModel(
+            name='SupervisionSubstitution',
+        ),
+    ]
diff --git a/aleksis/apps/lesrooster/models.py b/aleksis/apps/lesrooster/models.py
index e4007c2d..a87c4de4 100644
--- a/aleksis/apps/lesrooster/models.py
+++ b/aleksis/apps/lesrooster/models.py
@@ -469,203 +469,6 @@ class Supervision(TeacherPropertiesMixin, RoomPropertiesMixin, ExtensibleModel):
         return supervision_event
 
 
-class Substitution(RoomPropertiesMixin, TeacherPropertiesMixin, ExtensibleModel):
-    """A substitution is a lesson that is changed."""
-
-    lesson_event = models.OneToOneField(
-        LessonEvent,
-        on_delete=models.SET_NULL,
-        related_name="substitution",
-        verbose_name=_("Linked lesson event"),
-        blank=True,
-        null=True,
-    )
-
-    lesson = models.ForeignKey(Lesson, models.CASCADE, "substitutions", verbose_name=_("Lesson"))
-
-    date = models.DateField(verbose_name=_("Date"))
-
-    rooms = models.ManyToManyField(
-        Room,
-        verbose_name=_("Rooms"),
-        related_name="lr_substitutions",
-        blank=True,
-    )
-    teachers = models.ManyToManyField(
-        Person,
-        verbose_name=_("Teachers"),
-        related_name="lr_substitutions",
-        blank=True,
-    )
-    subject = models.ForeignKey(
-        Subject,
-        on_delete=models.CASCADE,
-        verbose_name=_("Subject"),
-        related_name="substitutions",
-        blank=True,
-        null=True,
-    )
-
-    cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled?"))
-
-    comment = models.TextField(verbose_name=_("Comment"), blank=True)
-
-    def get_teachers(self) -> QuerySet[Person]:
-        return self.teachers.all()
-
-    def get_rooms(self) -> QuerySet[Room]:
-        return self.rooms.all()
-
-    def clean(self) -> None:
-        if self.subject and self.cancelled:
-            raise ValidationError(_("Lessons can only be either substituted or cancelled."))
-
-    @property
-    def time_range(self) -> (timezone.datetime, timezone.datetime):
-        """Get the time range of this substitution."""
-        return timezone.datetime.combine(
-            self.date, self.lesson_period.period.time_start
-        ), timezone.datetime.combine(self.date, self.lesson_period.period.time_end)
-
-    def __str__(self):
-        return f"{self.lesson}, {date_format(self.date)}"
-
-    def sync(self) -> LessonEvent:
-        """Sync the lesson with its lesson event."""
-        week = CalendarWeek.from_date(self.date)
-        if not self.lesson.lesson_event:
-            return None
-
-        lesson_event = self.lesson_event if self.lesson_event else LessonEvent()
-
-        lesson_event.amends = self.lesson.lesson_event
-        lesson_event.course = self.lesson.course
-        lesson_event.subject = self.subject
-        lesson_event.datetime_start = self.lesson.slot_start.get_datetime_start(week)
-        lesson_event.datetime_end = self.lesson.slot_end.get_datetime_end(week)
-        lesson_event.cancelled = self.cancelled
-        lesson_event.comment = self.comment
-        lesson_event.save()
-
-        lesson_event.groups.set(self.lesson.course.groups.all())
-        lesson_event.teachers.set(self.teachers.all())
-        lesson_event.rooms.set(self.rooms.all())
-
-        if self.lesson_event != lesson_event:
-            self.lesson_event = lesson_event
-            self.save()
-
-        return lesson_event
-
-    class Meta:
-        ordering = [
-            "date",
-            "lesson__slot_start__weekday",
-            "lesson__slot_start__period",
-        ]
-        constraints = [
-            models.CheckConstraint(
-                check=~Q(cancelled=True, subject__isnull=False),
-                name="lr_either_substituted_or_cancelled",
-            ),
-            models.UniqueConstraint(fields=["lesson", "date"], name="unique_lesson_per_date"),
-        ]
-        indexes = [
-            models.Index(fields=["date"], name="substitution_date"),
-            models.Index(fields=["lesson"], name="substitution_lesson"),
-        ]
-        verbose_name = _("Substitution")
-        verbose_name_plural = _("Substitutions")
-
-
-class SupervisionSubstitution(TeacherPropertiesMixin, ExtensibleModel):
-    """A supervision substitution is a supervision that is changed."""
-
-    supervision_event = models.OneToOneField(
-        SupervisionEvent,
-        on_delete=models.SET_NULL,
-        related_name="supervision_substitution",
-        verbose_name=_("Linked supervision event"),
-        blank=True,
-        null=True,
-    )
-
-    supervision = models.ForeignKey(
-        Supervision, models.CASCADE, "substitutions", verbose_name=_("Supervision")
-    )
-
-    date = models.DateField(verbose_name=_("Date"))
-
-    teachers = models.ManyToManyField(
-        Person,
-        verbose_name=_("Teachers"),
-        related_name="lr_supervision_substitutions",
-        blank=True,
-    )
-    subject = models.ForeignKey(
-        Subject,
-        on_delete=models.CASCADE,
-        verbose_name=_("Subject"),
-        related_name="lr_supervision_substitutions",
-        blank=True,
-        null=True,
-    )
-
-    cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled?"))
-
-    comment = models.TextField(verbose_name=_("Comment"), blank=True)
-
-    def get_teachers(self) -> QuerySet[Person]:
-        return self.teachers.all()
-
-    @property
-    def time_range(self) -> (timezone.datetime, timezone.datetime):
-        """Get the time range of this supervision."""
-        return timezone.datetime.combine(
-            self.date, self.supervision.break_slot.period.time_start
-        ), timezone.datetime.combine(self.date, self.supervision.break_slot.period.time_end)
-
-    def __str__(self):
-        return f"{self.supervision}, {date_format(self.date)}"
-
-    def sync(self):
-        """Sync the supervision with its supervision event."""
-        week = CalendarWeek.from_date(self.date)
-        if not self.supervision.supervision_event:
-            return None
-
-        supervision_event = self.supervision_event if self.supervision_event else SupervisionEvent()
-
-        supervision_event.amends = self.supervision.supervision_event
-        supervision_event.datetime_start = self.supervision.break_slot.get_datetime_start(week)
-        supervision_event.datetime_end = self.supervision.break_slot.get_datetime_end(week)
-        supervision_event.cancelled = self.cancelled
-        supervision_event.comment = self.comment
-        supervision_event.subject = self.subject
-        supervision_event.save()
-
-        supervision_event.teachers.set(self.teachers.all())
-
-        if self.supervision_event != supervision_event:
-            self.supervision_event = supervision_event
-            self.save()
-
-        return supervision_event
-
-    class Meta:
-        ordering = [
-            "date",
-            "supervision__break_slot__weekday",
-            "supervision__break_slot__period",
-        ]
-        indexes = [
-            models.Index(fields=["date"], name="super_substitution_date"),
-            models.Index(fields=["supervision"], name="super_substitution_supervision"),
-        ]
-        verbose_name = _("Supervision Substitution")
-        verbose_name_plural = _("Supervision Substitutions")
-
-
 class TimeboundCourseConfig(ExtensibleModel):
     """A timebound course config is the specific configuration of a course.
 
diff --git a/aleksis/apps/lesrooster/rules.py b/aleksis/apps/lesrooster/rules.py
index 13146da9..416deee4 100644
--- a/aleksis/apps/lesrooster/rules.py
+++ b/aleksis/apps/lesrooster/rules.py
@@ -12,7 +12,6 @@ from .models import (
     Lesson,
     Slot,
     Supervision,
-    SupervisionSubstitution,
     TimeboundCourseConfig,
     TimeGrid,
     ValidityRange,
@@ -162,40 +161,6 @@ delete_supervision_predicate = view_supervision_predicate & (
 add_perm("lesrooster.delete_supervision_rule", delete_supervision_predicate)
 
 
-# Supervision substitutions
-view_supervision_substitutions_predicate = has_person & (
-    has_global_perm("lesrooster.view_supervisionsubstitution")
-    | has_any_object("lesrooster.view_supervisionsubstitution", SupervisionSubstitution)
-)
-add_perm("lesrooster.view_supervisionsubstitutions_rule", view_supervision_substitutions_predicate)
-
-view_supervision_substitution_predicate = has_person & (
-    has_global_perm("lesrooster.view_supervisionsubstitution")
-    | has_object_perm("lesrooster.view_supervisionsubstitution")
-)
-add_perm("lesrooster.view_supervisionsubstitution_rule", view_supervision_substitution_predicate)
-
-create_supervision_substitution_predicate = has_person & has_global_perm(
-    "lesrooster.add_supervisionsubstitution"
-)
-add_perm(
-    "lesrooster.create_supervisionsubstitution_rule", create_supervision_substitution_predicate
-)
-
-edit_supervision_substitution_predicate = view_supervision_substitution_predicate & (
-    has_global_perm("lesrooster.change_supervisionsubstitution")
-    | has_object_perm("lesrooster.change_supervisionsubstitution")
-)
-add_perm("lesrooster.edit_supervisionsubstitution_rule", edit_supervision_substitution_predicate)
-
-delete_supervision_substitution_predicate = view_supervision_substitution_predicate & (
-    has_global_perm("lesrooster.delete_supervisionsubstitution")
-    | has_object_perm("lesrooster.delete_supervisionsubstitution")
-)
-add_perm(
-    "lesrooster.delete_supervisionsubstitution_rule", delete_supervision_substitution_predicate
-)
-
 # Timebound course configs
 
 view_timebound_course_configs_predicate = has_person & (
diff --git a/aleksis/apps/lesrooster/util/signal_handlers.py b/aleksis/apps/lesrooster/util/signal_handlers.py
index 43e424f7..6240a524 100644
--- a/aleksis/apps/lesrooster/util/signal_handlers.py
+++ b/aleksis/apps/lesrooster/util/signal_handlers.py
@@ -38,19 +38,12 @@ def create_time_grid_for_new_validity_range(sender, instance, created, **kwargs)
 
 
 def publish_validity_range(sender, instance, created, **kwargs):
-    from ..models import Lesson, Substitution, Supervision, SupervisionSubstitution
+    from ..models import Lesson, Supervision
 
     # FIXME Move this to a background job
-    objs_to_update = (
-        list(Lesson.objects.filter(slot_start__time_grid__validity_range=instance))
-        + list(Supervision.objects.filter(break_slot__time_grid__validity_range=instance))
-        + list(Substitution.objects.filter(lesson__slot_start__time_grid__validity_range=instance))
-        + list(
-            SupervisionSubstitution.objects.filter(
-                supervision__break_slot__time_grid__validity_range=instance
-            )
-        )
-    )
+    objs_to_update = list(
+        Lesson.objects.filter(slot_start__time_grid__validity_range=instance)
+    ) + list(Supervision.objects.filter(break_slot__time_grid__validity_range=instance))
     for obj in objs_to_update:
         logging.info(f"Syncing object {obj} ({type(obj)}, {obj.pk})")
         obj.sync()
-- 
GitLab