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..ac454086a8592a5dfb8882049c56522f7a983bc6 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -359,3 +359,68 @@ 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}: {', '.join(str(a) for a in getattr(self, field_mm).all())}")
+        # 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')
+
+    def __str__(self):
+        return f"{self.get_level_display()}: {self.get_type_display()} [{self.get_details()}]"