diff --git a/aleksis/apps/chronos/migrations/0001_initial.py b/aleksis/apps/chronos/migrations/0001_initial.py
index f7fe1b4949e026eeb5d51246b761d4b5c53d0a68..883b25bbf97f4bb0660e787d0a787b754b5efec4 100644
--- a/aleksis/apps/chronos/migrations/0001_initial.py
+++ b/aleksis/apps/chronos/migrations/0001_initial.py
@@ -305,8 +305,7 @@ class Migration(migrations.Migration):
                 "verbose_name_plural": "Supervision substitutions",
                 "ordering": ["date", "supervision"],
             },
-            managers=[
-            ],
+            managers=[],
         ),
         migrations.CreateModel(
             name="SupervisionArea",
diff --git a/aleksis/apps/chronos/migrations/0007_unique_constraints.py b/aleksis/apps/chronos/migrations/0007_unique_constraints.py
new file mode 100644
index 0000000000000000000000000000000000000000..754c3ae1c1c9c7a8a4e3ab6f616a459cf65d5298
--- /dev/null
+++ b/aleksis/apps/chronos/migrations/0007_unique_constraints.py
@@ -0,0 +1,85 @@
+# Generated by Django 3.2.3 on 2021-05-22 12:06
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0006_indexes'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='chronosglobalpermissions',
+            options={'managed': False, 'permissions': (('view_all_room_timetables', 'Can view all room timetables'), ('view_all_group_timetables', 'Can view all group timetables'), ('view_all_person_timetables', 'Can view all person timetables'), ('view_timetable_overview', 'Can view timetable overview'), ('view_lessons_day', 'Can view all lessons per day'))},
+        ),
+        migrations.AlterField(
+            model_name='room',
+            name='short_name',
+            field=models.CharField(max_length=255, verbose_name='Short name'),
+        ),
+        migrations.AlterField(
+            model_name='subject',
+            name='name',
+            field=models.CharField(max_length=255, verbose_name='Long name'),
+        ),
+        migrations.AlterField(
+            model_name='subject',
+            name='short_name',
+            field=models.CharField(max_length=255, verbose_name='Short name'),
+        ),
+        migrations.AlterField(
+            model_name='timeperiod',
+            name='weekday',
+            field=models.PositiveSmallIntegerField(choices=[(0, 'Montag'), (1, 'Dienstag'), (2, 'Mittwoch'), (3, 'Donnerstag'), (4, 'Freitag'), (5, 'Samstag'), (6, 'Sonntag')], verbose_name='Week day'),
+        ),
+        migrations.AlterUniqueTogether(
+            name='lessonsubstitution',
+            unique_together=set(),
+        ),
+        migrations.AlterUniqueTogether(
+            name='timeperiod',
+            unique_together=set(),
+        ),
+        migrations.AlterUniqueTogether(
+            name='validityrange',
+            unique_together=set(),
+        ),
+        migrations.AddConstraint(
+            model_name='absencereason',
+            constraint=models.UniqueConstraint(fields=('site_id', 'short_name'), name='unique_short_name_per_site_absence_reason'),
+        ),
+        migrations.AddConstraint(
+            model_name='break',
+            constraint=models.UniqueConstraint(fields=('site_id', 'short_name'), name='unique_short_name_per_site_break'),
+        ),
+        migrations.AddConstraint(
+            model_name='lessonsubstitution',
+            constraint=models.UniqueConstraint(fields=('lesson_period', 'week'), name='unique_period_per_week'),
+        ),
+        migrations.AddConstraint(
+            model_name='room',
+            constraint=models.UniqueConstraint(fields=('site_id', 'short_name'), name='unique_short_name_per_site_room'),
+        ),
+        migrations.AddConstraint(
+            model_name='subject',
+            constraint=models.UniqueConstraint(fields=('site_id', 'short_name'), name='unique_short_name_per_site_subject'),
+        ),
+        migrations.AddConstraint(
+            model_name='subject',
+            constraint=models.UniqueConstraint(fields=('site_id', 'name'), name='unique_name_per_site'),
+        ),
+        migrations.AddConstraint(
+            model_name='supervisionarea',
+            constraint=models.UniqueConstraint(fields=('site_id', 'short_name'), name='unique_short_name_per_site_supervision_area'),
+        ),
+        migrations.AddConstraint(
+            model_name='timeperiod',
+            constraint=models.UniqueConstraint(fields=('weekday', 'period', 'validity'), name='unique_period_per_range'),
+        ),
+        migrations.AddConstraint(
+            model_name='validityrange',
+            constraint=models.UniqueConstraint(fields=('school_term', 'date_start', 'date_end'), name='unique_dates_per_term'),
+        ),
+    ]
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 9842363ca301fb7bf3ddcc9a3f7bd96a51f7f375..97f701ee1c3bc2fd591a7572dff96e3844c03654 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_subject"
+            ),
+            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_room"
+            ),
+        ]
 
 
 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_absence_reason"
+            ),
+        ]
 
 
 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_supervision_area"
+            ),
+        ]
 
 
 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_break"
+            ),
+        ]
 
 
 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")]