From b68a3820aa2a39232489d55fb75859c9e5af1749 Mon Sep 17 00:00:00 2001
From: Dominik George <nik@naturalnet.de>
Date: Sat, 22 May 2021 13:05:15 +0200
Subject: [PATCH] Fix up unique constraints for uniqueness per site and others

---
 aleksis/apps/chronos/models.py | 59 ++++++++++++++++++++++++++++++----
 1 file changed, 52 insertions(+), 7 deletions(-)

diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 9842363c..77f27c92 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -116,7 +116,12 @@ class ValidityRange(ExtensibleModel):
     class Meta:
         verbose_name = _("Validity range")
         verbose_name_plural = _("Validity ranges")
-        unique_together = ["date_start", "date_end"]
+        constraints = [
+            # Heads up: Uniqueness per term implies uniqueness per site
+            models.UniqueConstraint(
+                fields=["school_term", "date_start", "date_end"], name="unique_dates_per_term"
+            ),
+        ]
         indexes = [
             models.Index(fields=["date_start", "date_end"], name="validity_date_start_date_end")
         ]
@@ -297,7 +302,12 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
         return period_choices
 
     class Meta:
-        unique_together = [["weekday", "period", "validity"]]
+        constraints = [
+            # Heads up: Uniqueness per validity range implies validity per site
+            models.UniqueConstraint(
+                fields=["weekday", "period", "validity"], name="unique_period_per_range"
+            ),
+        ]
         ordering = ["weekday", "period"]
         indexes = [models.Index(fields=["time_start", "time_end"])]
         verbose_name = _("Time period")
@@ -305,8 +315,8 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
 
 
 class Subject(ExtensibleModel):
-    short_name = models.CharField(verbose_name=_("Short name"), max_length=255, unique=True)
-    name = models.CharField(verbose_name=_("Long name"), max_length=255, unique=True)
+    short_name = models.CharField(verbose_name=_("Short name"), max_length=255)
+    name = models.CharField(verbose_name=_("Long name"), max_length=255)
 
     colour_fg = ColorField(verbose_name=_("Foreground colour"), blank=True)
     colour_bg = ColorField(verbose_name=_("Background colour"), blank=True)
@@ -318,10 +328,16 @@ class Subject(ExtensibleModel):
         ordering = ["name", "short_name"]
         verbose_name = _("Subject")
         verbose_name_plural = _("Subjects")
+        constraints = [
+            models.UniqueConstraint(
+                fields=["site_id", "short_name"], name="unique_short_name_per_site"
+            ),
+            models.UniqueConstraint(fields=["site_id", "name"], name="unique_name_per_site"),
+        ]
 
 
 class Room(ExtensibleModel):
-    short_name = models.CharField(verbose_name=_("Short name"), max_length=255, unique=True)
+    short_name = models.CharField(verbose_name=_("Short name"), max_length=255)
     name = models.CharField(verbose_name=_("Long name"), max_length=255)
 
     def __str__(self) -> str:
@@ -335,6 +351,11 @@ class Room(ExtensibleModel):
         ordering = ["name", "short_name"]
         verbose_name = _("Room")
         verbose_name_plural = _("Rooms")
+        constraints = [
+            models.UniqueConstraint(
+                fields=["site_id", "short_name"], name="unique_short_name_per_site"
+            ),
+        ]
 
 
 class Lesson(ValidityRangeRelatedExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin):
@@ -368,6 +389,7 @@ class Lesson(ValidityRangeRelatedExtensibleModel, GroupPropertiesMixin, TeacherP
         return f"{format_m2m(self.groups)}, {self.subject.short_name}, {format_m2m(self.teachers)}"
 
     class Meta:
+        # Heads up: Link to periods implies uniqueness per site
         ordering = ["validity__date_start", "subject"]
         verbose_name = _("Lesson")
         verbose_name_plural = _("Lessons")
@@ -416,7 +438,6 @@ class LessonSubstitution(ExtensibleModel, TeacherPropertiesMixin, WeekRelatedMix
         return f"{self.lesson_period}, {date_format(self.date)}"
 
     class Meta:
-        unique_together = [["lesson_period", "week"]]
         ordering = [
             "year",
             "week",
@@ -427,7 +448,11 @@ class LessonSubstitution(ExtensibleModel, TeacherPropertiesMixin, WeekRelatedMix
             models.CheckConstraint(
                 check=~Q(cancelled=True, subject__isnull=False),
                 name="either_substituted_or_cancelled",
-            )
+            ),
+            # Heads up: Link to period implies uniqueness per site
+            models.UniqueConstraint(
+                fields=["lesson_period", "week"], name="unique_period_per_week"
+            ),
         ]
         indexes = [
             models.Index(fields=["week", "year"], name="substitution_week_year"),
@@ -533,6 +558,7 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel)
         return self._equal_lessons.next_lesson(self, -1)
 
     class Meta:
+        # Heads up: Link to period implies uniqueness per site
         ordering = [
             "lesson__validity__date_start",
             "period__weekday",
@@ -599,6 +625,11 @@ class AbsenceReason(ExtensibleModel):
     class Meta:
         verbose_name = _("Absence reason")
         verbose_name_plural = _("Absence reasons")
+        constraints = [
+            models.UniqueConstraint(
+                fields=["site_id", "short_name"], name="unique_short_name_per_site"
+            ),
+        ]
 
 
 class Absence(SchoolTermRelatedExtensibleModel):
@@ -667,6 +698,7 @@ class Absence(SchoolTermRelatedExtensibleModel):
             return _("Unknown absence")
 
     class Meta:
+        # Heads up: Link to period implies uniqueness per site
         ordering = ["date_start"]
         indexes = [models.Index(fields=["date_start", "date_end"])]
         verbose_name = _("Absence")
@@ -698,6 +730,7 @@ class Exam(SchoolTermRelatedExtensibleModel):
     comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True)
 
     class Meta:
+        # Heads up: Link to period implies uniqueness per site
         ordering = ["date"]
         indexes = [models.Index(fields=["date"])]
         verbose_name = _("Exam")
@@ -765,6 +798,11 @@ class SupervisionArea(ExtensibleModel):
         ordering = ["name"]
         verbose_name = _("Supervision area")
         verbose_name_plural = _("Supervision areas")
+        constraints = [
+            models.UniqueConstraint(
+                fields=["site_id", "short_name"], name="unique_short_name_per_site"
+            ),
+        ]
 
 
 class Break(ValidityRangeRelatedExtensibleModel):
@@ -826,6 +864,11 @@ class Break(ValidityRangeRelatedExtensibleModel):
         indexes = [models.Index(fields=["after_period", "before_period"])]
         verbose_name = _("Break")
         verbose_name_plural = _("Breaks")
+        constraints = [
+            models.UniqueConstraint(
+                fields=["site_id", "short_name"], name="unique_short_name_per_site"
+            ),
+        ]
 
 
 class Supervision(ValidityRangeRelatedExtensibleModel, WeekAnnotationMixin):
@@ -1002,6 +1045,7 @@ class Event(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPrope
         return self.teachers
 
     class Meta:
+        # Heads up: Link to period implies uniqueness per site
         ordering = ["date_start"]
         indexes = [
             models.Index(
@@ -1061,6 +1105,7 @@ class ExtraLesson(
         return self.subject
 
     class Meta:
+        # Heads up: Link to period implies uniqueness per site
         verbose_name = _("Extra lesson")
         verbose_name_plural = _("Extra lessons")
         indexes = [models.Index(fields=["week", "year"], name="extra_lesson_week_year")]
-- 
GitLab