diff --git a/AKModel/admin.py b/AKModel/admin.py
index 0e7cef569f152c31b71d0f9232e8b1af25fc828c..b0bae79c1269e6b68f8d6354d296077e33b6cd67 100644
--- a/AKModel/admin.py
+++ b/AKModel/admin.py
@@ -12,7 +12,8 @@ from simple_history.admin import SimpleHistoryAdmin
 
 from AKModel.availability.forms import AvailabilitiesFormMixin
 from AKModel.availability.models import Availability
-from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room, AKOrgaMessage
+from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \
+    ConstraintViolation
 from AKModel.views import EventStatusView, AKCSVExportView, AKWikiExportView, AKMessageDeleteView
 
 
@@ -259,3 +260,10 @@ class AKOrgaMessageAdmin(admin.ModelAdmin):
     list_display = ['timestamp', 'ak', 'text']
     list_filter = ['ak__event']
     readonly_fields = ['timestamp', 'ak', 'text']
+
+
+@admin.register(ConstraintViolation)
+class ConstraintViolationAdmin(admin.ModelAdmin):
+    list_display = ['type', 'level', 'get_details']
+    list_filter = ['event']
+    readonly_fields = ['timestamp']
diff --git a/AKModel/migrations/0041_constraint_violation.py b/AKModel/migrations/0041_constraint_violation.py
new file mode 100644
index 0000000000000000000000000000000000000000..c323bea7d688682d9ef2f640c9c80e5906e77299
--- /dev/null
+++ b/AKModel/migrations/0041_constraint_violation.py
@@ -0,0 +1,37 @@
+# Generated by Django 3.0.6 on 2020-11-03 23:27
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('AKModel', '0040_event_reso_deadline'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ConstraintViolation',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('type', models.CharField(choices=[('ots', 'Owner has two parallel slots'), ('soa', "AK Slot was scheduled outside the AK's availabilities"), ('rts', 'Room has two AK slots scheduled at the same time'), ('rng', 'Room does not satisfy the requirement of the scheduled AK'), ('acc', 'AK Slot is scheduled at the same time as an AK listed as a conflict'), ('abp', 'AK Slot is scheduled before an AK listed as a prerequisite'), ('aar', 'AK Slot for AK with intention to submit a resolution is scheduled after resolution deadline'), ('acm', 'AK Slot in a category is outside that categories availabilities'), ('asc', 'Two AK Slots for the same AK scheduled at the same time'), ('rce', 'AK Slot is scheduled in a room with less space than interest'), ('soe', "AK Slot is scheduled outside the event's availabilities")], help_text='Type of violation, i.e. what kind of constraint was violated', max_length=3, verbose_name='Type')),
+                ('level', models.PositiveSmallIntegerField(choices=[(1, 'Warning'), (10, 'Violation')], help_text='Severity level of the violation', verbose_name='Level')),
+                ('comment', models.TextField(blank=True, help_text='Comment or further details for this violation', verbose_name='Comment')),
+                ('timestamp', models.DateTimeField(auto_now_add=True, help_text='Time of creation', verbose_name='Timestamp')),
+                ('manually_resolved', models.BooleanField(default=False, help_text='Mark this violation manually as resolved', verbose_name='Manually Resolved')),
+                ('ak_owner', models.ForeignKey(blank=True, help_text='AK Owner belonging to this constraint', null=True, on_delete=django.db.models.deletion.CASCADE, to='AKModel.AKOwner', verbose_name='AK Owner')),
+                ('ak_slots', models.ManyToManyField(blank=True, help_text='AK Slot(s) belonging to this constraint', to='AKModel.AKSlot', verbose_name='AK Slots')),
+                ('aks', models.ManyToManyField(blank=True, help_text='AK(s) belonging to this constraint', to='AKModel.AK', verbose_name='AKs')),
+                ('category', models.ForeignKey(blank=True, help_text='AK Category belonging to this constraint', null=True, on_delete=django.db.models.deletion.CASCADE, to='AKModel.AKCategory', verbose_name='AK Category')),
+                ('event', models.ForeignKey(help_text='Associated event', on_delete=django.db.models.deletion.CASCADE, to='AKModel.Event', verbose_name='Event')),
+                ('requirement', models.ForeignKey(blank=True, help_text='AK Requirement belonging to this constraint', null=True, on_delete=django.db.models.deletion.CASCADE, to='AKModel.AKRequirement', verbose_name='AK Requirement')),
+                ('room', models.ForeignKey(blank=True, help_text='Room belonging to this constraint', null=True, on_delete=django.db.models.deletion.CASCADE, to='AKModel.Room', verbose_name='Room')),
+            ],
+            options={
+                'verbose_name': 'Constraint Violation',
+                'verbose_name_plural': 'Constraint Violations',
+                'ordering': ['-timestamp'],
+            },
+        ),
+    ]
diff --git a/AKModel/models.py b/AKModel/models.py
index 2d5a72c0cb654fae8e35482ad585a5b7c597c3ed..5a257cd56d25ceb187f7d4337395d87ae6638ae5 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -346,6 +346,9 @@ class AKSlot(models.Model):
         """
         return (timezone.now() - self.updated).total_seconds()
 
+    def overlaps(self, other: "AKSlot"):
+        return self.start <= other.end  <= self.end or self.start <= other.start <= self.end
+
 
 class AKOrgaMessage(models.Model):
     ak = models.ForeignKey(to=AK, on_delete=models.CASCADE, verbose_name=_('AK'), help_text=_('AK this message belongs to'))
@@ -359,3 +362,106 @@ class AKOrgaMessage(models.Model):
 
     def __str__(self):
         return f'AK Orga Message for "{self.ak}" @ {self.timestamp}'
+
+
+class ConstraintViolation(models.Model):
+    class Meta:
+        verbose_name = _('Constraint Violation')
+        verbose_name_plural = _('Constraint Violations')
+        ordering = ['-timestamp']
+
+    class ViolationType(models.TextChoices):
+        OWNER_TWO_SLOTS = 'ots', _('Owner has two parallel slots')
+        SLOT_OUTSIDE_AVAIL = 'soa', _('AK Slot was scheduled outside the AK\'s availabilities')
+        ROOM_TWO_SLOTS = 'rts', _('Room has two AK slots scheduled at the same time')
+        REQUIRE_NOT_GIVEN = 'rng', _('Room does not satisfy the requirement of the scheduled AK')
+        AK_CONFLICT_COLLISION = 'acc', _('AK Slot is scheduled at the same time as an AK listed as a conflict')
+        AK_BEFORE_PREREQUISITE = 'abp', _('AK Slot is scheduled before an AK listed as a prerequisite')
+        AK_AFTER_RESODEADLINE = 'aar', _('AK Slot for AK with intention to submit a resolution is scheduled after resolution deadline')
+        AK_CATEGORY_MISMATCH = 'acm', _('AK Slot in a category is outside that categories availabilities')
+        AK_SLOT_COLLISION = 'asc', _('Two AK Slots for the same AK scheduled at the same time')
+        ROOM_CAPACITY_EXCEEDED = 'rce', _('AK Slot is scheduled in a room with less space than interest')
+        SLOT_OUTSIDE_EVENT = 'soe', _('AK Slot is scheduled outside the event\'s availabilities')
+
+    class ViolationLevel(models.IntegerChoices):
+        WARNING = 1, _('Warning')
+        VIOLATION = 10, _('Violation')
+
+    type = models.CharField(verbose_name=_('Type'), max_length=3, choices=ViolationType.choices, help_text=_('Type of violation, i.e. what kind of constraint was violated'))
+    level = models.PositiveSmallIntegerField(verbose_name=_('Level'), choices=ViolationLevel.choices, help_text=_('Severity level of the violation'))
+
+    event = models.ForeignKey(to=Event, on_delete=models.CASCADE, verbose_name=_('Event'), help_text=_('Associated event'))
+
+    aks = models.ManyToManyField(to=AK, blank=True, verbose_name=_('AKs'), help_text=_('AK(s) belonging to this constraint'))
+    ak_slots = models.ManyToManyField(to=AKSlot, blank=True, verbose_name=_('AK Slots'), help_text=_('AK Slot(s) belonging to this constraint'))
+    ak_owner = models.ForeignKey(to=AKOwner, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('AK Owner'), help_text=_('AK Owner belonging to this constraint'))
+    room = models.ForeignKey(to=Room, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('Room'), help_text=_('Room belonging to this constraint'))
+    requirement = models.ForeignKey(to=AKRequirement, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('AK Requirement'), help_text=_('AK Requirement belonging to this constraint'))
+    category = models.ForeignKey(to=AKCategory, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('AK Category'), help_text=_('AK Category belonging to this constraint'))
+
+    comment = models.TextField(verbose_name=_('Comment'), help_text=_('Comment or further details for this violation'), blank=True)
+
+    timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_('Timestamp'), help_text=_('Time of creation'))
+    manually_resolved = models.BooleanField(verbose_name=_('Manually Resolved'), default=False, help_text=_('Mark this violation manually as resolved'))
+
+    FIELDS = ['ak_owner', 'room', 'requirement', 'category']
+    FIELDS_MM = ['_aks', '_ak_slots']
+
+    def get_details(self):
+        """
+        Get details of this constraint (all fields connected to it)
+        :return: string of details
+        :rtype: str
+        """
+        output = []
+        # Stringify all ManyToMany fields
+        for field_mm in self.FIELDS_MM:
+            output.append(f"{field_mm[1:]}: {', '.join(str(a) for a in getattr(self, field_mm))}")
+        # Stringify all other fields
+        for field in self.FIELDS:
+            a = getattr(self, field, None)
+            if a is not None:
+                output.append(f"{field}: {a}")
+        return ", ".join(output)
+    get_details.short_description = _('Details')
+
+    # TODO Automatically save this
+    aks_tmp = set()
+    @property
+    def _aks(self):
+        """
+        Get all AKs belonging to this constraint violation
+
+        The distinction between real and tmp relationships is needed since many to many
+        relations only work for objects already persisted in the database
+
+        :return: set of all AKs belonging to this constraint violation
+        :rtype: set(AK)
+        """
+        if self.pk and self.pk > 0:
+            return set(self.aks.all())
+        return self.aks_tmp
+
+    # TODO Automatically save this
+    ak_slots_tmp = set()
+    @property
+    def _ak_slots(self):
+        """
+        Get all AK Slots belonging to this constraint violation
+
+        The distinction between real and tmp relationships is needed since many to many
+        relations only work for objects already persisted in the database
+
+        :return: set of all AK Slots belonging to this constraint violation
+        :rtype: set(AKSlot)
+        """
+        if self.pk and self.pk > 0:
+            return set(self.ak_slots.all())
+        return self.ak_slots_tmp
+
+    def __str__(self):
+        return f"{self.get_level_display()}: {self.get_type_display()} [{self.get_details()}]"
+
+    def __eq__(self, other):
+        # TODO Check if FIELDS and FIELDS_MM are equal
+        return super().__eq__(other)
diff --git a/AKScheduling/models.py b/AKScheduling/models.py
index 6b2021999398416a78191ac543b7e0e34d86bc2c..990bcd4a74b6efebdc865830abd5cafb8600ae21 100644
--- a/AKScheduling/models.py
+++ b/AKScheduling/models.py
@@ -1 +1,80 @@
-# Create your models here.
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+
+from AKModel.availability.models import Availability
+from AKModel.models import AK, AKSlot, Room, Event, AKOwner, ConstraintViolation
+
+
+@receiver(post_save, sender=AK)
+def ak_changed_handler(sender, instance: AK, **kwargs):
+    # Changes might affect: Owner(s), Requirements, Conflicts, Prerequisites, Category, Interest
+    print(f"{instance} changed")
+
+    event = instance.event
+
+    # Owner might have changed: Might affect multiple AKs by the same owner at the same time
+    conflicts = []
+    type = ConstraintViolation.ViolationType.OWNER_TWO_SLOTS
+    # For all owners...
+    for owner in instance.owners.all():
+        # ...find overlapping AKs...
+        slots_by_owner : [AKSlot] = []
+        slots_by_owner_this_ak : [AKSlot] = []
+        aks_by_owner = owner.ak_set.all()
+        for ak in aks_by_owner:
+            if ak != instance:
+                slots_by_owner.extend(ak.akslot_set.filter(start__isnull=False))
+            else:
+                # ToDo Fill this outside of loop?
+                slots_by_owner_this_ak.extend(ak.akslot_set.filter(start__isnull=False))
+        for slot in slots_by_owner_this_ak:
+            for other_slot in slots_by_owner:
+                if slot.overlaps(other_slot):
+                    # TODO Create ConstraintViolation here
+                    c = ConstraintViolation(
+                        type=type,
+                        level=ConstraintViolation.ViolationLevel.VIOLATION,
+                        event=event,
+                        ak_owner=owner
+                    )
+                    c.aks_tmp.add(instance)
+                    c.aks_tmp.add(other_slot.ak)
+                    c.ak_slots_tmp.add(slot)
+                    c.ak_slots_tmp.add(other_slot)
+                    conflicts.append(c)
+        print(f"{owner} has the following conflicts: {conflicts}")
+    # ... and compare to/update list of existing violations of this type:
+    current_violations = instance.constraintviolation_set.filter(type=type)
+    for conflict in conflicts:
+        pass
+        # TODO Remove from list of current_violations if an equal new one is found
+        # TODO Otherwise, store this conflict in db
+    # TODO Remove all violations still in current_violations
+
+
+@receiver(post_save, sender=AKSlot)
+def akslot_changed_handler(sender, instance, **kwargs):
+    # Changes might affect: Duplicate parallel, Two in room
+    print(f"{sender} changed")
+    # TODO Replace with real handling
+
+
+@receiver(post_save, sender=Room)
+def room_changed_handler(sender, **kwargs):
+    # Changes might affect: Room size, Requirement
+    print(f"{sender} changed")
+    # TODO Replace with real handling
+
+
+@receiver(post_save, sender=Availability)
+def availability_changed_handler(sender, **kwargs):
+    # Changes might affect: category availability, AK availability, Room availability
+    print(f"{sender} changed")
+    # TODO Replace with real handling
+
+
+@receiver(post_save, sender=Event)
+def room_changed_handler(sender, **kwargs):
+    # Changes might affect: Reso-Deadline
+    print(f"{sender} changed")
+    # TODO Replace with real handling