diff --git a/aleksis/apps/chronos/migrations/0007_advanced_models_from_untis.py b/aleksis/apps/chronos/migrations/0007_advanced_models_from_untis.py
new file mode 100644
index 0000000000000000000000000000000000000000..87b517a8beb8d3ee4dd097981ed53f95cc2b4ad4
--- /dev/null
+++ b/aleksis/apps/chronos/migrations/0007_advanced_models_from_untis.py
@@ -0,0 +1,280 @@
+# Generated by Django 3.0.3 on 2020-03-08 20:16
+
+import colorfield.fields
+import django.contrib.postgres.fields.jsonb
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0006_extended_data'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Absence',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+                ('date_start', models.DateField(null=True, verbose_name='Effective start date of absence')),
+                ('date_end', models.DateField(null=True, verbose_name='Effective end date of absence')),
+                ('comment', models.TextField(verbose_name='Comment', null=True, blank=True)),
+            ],
+            options={
+                'verbose_name': 'Absence',
+                'verbose_name_plural': 'Absences',
+                'ordering': ['date_start'],
+            },
+        ),
+        migrations.CreateModel(
+            name='AbsenceReason',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+                ('title', models.CharField(max_length=50, verbose_name='Title')),
+                ('description', models.TextField(verbose_name='Description', null=True, blank=True)),
+            ],
+            options={
+                'verbose_name': 'Absence reason',
+                'verbose_name_plural': 'Absence reasons',
+            },
+        ),
+        migrations.CreateModel(
+            name='Event',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+                ('title', models.CharField(max_length=50, verbose_name='Title')),
+                ('date_start', models.DateField(null=True, verbose_name='Effective start date of event')),
+                ('date_end', models.DateField(null=True, verbose_name='Effective end date of event')),
+            ],
+            options={
+                'verbose_name': 'Events',
+                'verbose_name_plural': 'Events',
+                'ordering': ['date_start'],
+            },
+        ),
+        migrations.CreateModel(
+            name='Exam',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+                ('date', models.DateField(null=True, verbose_name='Date of exam')),
+                ('title', models.CharField(max_length=50, verbose_name='Title')),
+                ('comment', models.TextField(verbose_name='Comment', null=True, blank=True)),
+            ],
+            options={
+                'verbose_name': 'Exam',
+                'verbose_name_plural': 'Exams',
+                'ordering': ['date'],
+            },
+        ),
+        migrations.CreateModel(
+            name='Holiday',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+                ('title', models.CharField(max_length=50, verbose_name='Title of the holidays')),
+                ('date_start', models.DateField(null=True, verbose_name='Effective start date of holidays')),
+                ('date_end', models.DateField(null=True, verbose_name='Effective end date of holidays')),
+                ('comments', models.TextField(verbose_name='Comments', null=True, blank=True)),
+            ],
+            options={
+                'verbose_name': 'Holiday',
+                'verbose_name_plural': 'Holidays',
+                'ordering': ['date_start'],
+            },
+        ),
+        migrations.CreateModel(
+            name='SupervisionArea',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+                ('short_name', models.CharField(max_length=10, verbose_name='Short name')),
+                ('name', models.CharField(max_length=50, verbose_name='Long name')),
+                ('colour_fg', colorfield.fields.ColorField(default='#000000', max_length=18)),
+                ('colour_bg', colorfield.fields.ColorField(default='#FFFFFF', max_length=18)),
+            ],
+            options={
+                'verbose_name': 'Supervision areas',
+                'verbose_name_plural': 'Supervision areas',
+                'ordering': ['name'],
+            },
+        ),
+        migrations.CreateModel(
+            name='Break',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+                ('short_name', models.CharField(max_length=10, verbose_name='Short name')),
+                ('name', models.CharField(max_length=50, verbose_name='Long name')),
+                ('weekday', models.PositiveSmallIntegerField(
+                    choices=[(0, 'Montag'), (1, 'Dienstag'), (2, 'Mittwoch'), (3, 'Donnerstag'), (4, 'Freitag'),
+                             (5, 'Samstag'), (6, 'Sonntag')], verbose_name='Week day')),
+                ('time_start', models.TimeField(verbose_name='Start time')),
+                ('time_end', models.TimeField(verbose_name='End time')),
+            ],
+            options={
+                'verbose_name': 'Break',
+                'verbose_name_plural': 'Breaks',
+                'ordering': ['weekday', 'time_start'],
+            },
+        ),
+        migrations.CreateModel(
+            name='Supervision',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+            ],
+            options={
+                'verbose_name': 'Supervision',
+                'verbose_name_plural': 'Supervisions',
+                'ordering': ['area', 'break_item'],
+            },
+        ),
+        migrations.CreateModel(
+            name='SupervisionSubstitution',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
+                ('date', models.DateField(verbose_name='Date')),
+            ],
+            options={
+                'verbose_name': 'Supervision substitution',
+                'verbose_name_plural': 'Supervision substitutions',
+                'ordering': ['date', 'supervision'],
+            },
+        ),
+        migrations.AddIndex(
+            model_name='holiday',
+            index=models.Index(fields=['date_start', 'date_end'], name='chronos_hol_date_st_a47004_idx'),
+        ),
+        migrations.AddField(
+            model_name='exam',
+            name='lesson',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='exams', to='chronos.Lesson'),
+        ),
+        migrations.AddField(
+            model_name='exam',
+            name='period_from',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective start period of exam'),
+        ),
+        migrations.AddField(
+            model_name='exam',
+            name='period_to',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective end period of exam'),
+        ),
+        migrations.AddField(
+            model_name='event',
+            name='absence_reason',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absence_reason', to='chronos.AbsenceReason', verbose_name='Absence reason'),
+        ),
+        migrations.AddField(
+            model_name='event',
+            name='teachers',
+            field=models.ManyToManyField(related_name='events', to='core.Person', verbose_name='Teachers'),
+        ),
+        migrations.AddField(
+            model_name='event',
+            name='period_from',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective start period of event'),
+        ),
+        migrations.AddField(
+            model_name='event',
+            name='period_to',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective end period of event'),
+        ),
+        migrations.AddField(
+            model_name='event',
+            name='rooms',
+            field=models.ManyToManyField(related_name='events', to='chronos.Room', verbose_name='Rooms'),
+        ),
+        migrations.AddField(
+            model_name='absence',
+            name='period_from',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective start period of absence'),
+        ),
+        migrations.AddField(
+            model_name='absence',
+            name='period_to',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective end period of absence'),
+        ),
+        migrations.AddField(
+            model_name='absence',
+            name='person',
+            field=models.ManyToManyField(related_name='absences', to='core.Person'),
+        ),
+        migrations.AddField(
+            model_name='absence',
+            name='reason',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='chronos.AbsenceReason'),
+        ),
+        migrations.AddIndex(
+            model_name='exam',
+            index=models.Index(fields=['date'], name='chronos_exa_date_5ba442_idx'),
+        ),
+        migrations.AddIndex(
+            model_name='event',
+            index=models.Index(fields=['period_from', 'period_to', 'date_start', 'date_end'], name='chronos_eve_periodf_56eb18_idx'),
+        ),
+        migrations.AddIndex(
+            model_name='absence',
+            index=models.Index(fields=['date_start', 'date_end'], name='chronos_abs_date_st_337ff5_idx'),
+        ),
+        migrations.AddField(
+            model_name='lessonsubstitution',
+            name='cancelled_for_teachers',
+            field=models.BooleanField(default=False, verbose_name='Cancelled for teachers?'),
+        ),
+        migrations.AddField(
+            model_name='lessonsubstitution',
+            name='comment',
+            field=models.TextField(blank=True, null=True, verbose_name='Comment'),
+        ),
+        migrations.AlterField(
+            model_name='lessonsubstitution',
+            name='cancelled',
+            field=models.BooleanField(default=False, verbose_name='Cancelled?'),
+        ),
+        migrations.AddField(
+            model_name='event',
+            name='groups',
+            field=models.ManyToManyField(related_name='events', to='core.Group', verbose_name='Groups'),
+        ),
+        migrations.AddField(
+            model_name='supervisionsubstitution',
+            name='supervision',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='substitutions',
+                                    to='chronos.Supervision', verbose_name='Supervision'),
+        ),
+        migrations.AddField(
+            model_name='supervisionsubstitution',
+            name='teacher',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+                                    related_name='substituted_supervisions', to='core.Person', verbose_name='Teacher'),
+        ),
+        migrations.AddField(
+            model_name='supervision',
+            name='area',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supervisions',
+                                    to='chronos.SupervisionArea', verbose_name='Supervision area'),
+        ),
+        migrations.AddField(
+            model_name='supervision',
+            name='break_item',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supervisions',
+                                    to='chronos.Break', verbose_name='Break'),
+        ),
+        migrations.AddField(
+            model_name='supervision',
+            name='teacher',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supervisions',
+                                    to='core.Person', verbose_name='Teacher'),
+        ),
+        migrations.AddIndex(
+            model_name='break',
+            index=models.Index(fields=['weekday', 'time_start', 'time_end'], name='chronos_bre_weekday_165338_idx'),
+        ),
+    ]
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 94fd844c2c578ec29257ad2cfe0280af9f1ea6b9..78d9b425b26d7c182f9d4abc3b65c63a5949ee4d 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -18,6 +18,7 @@ from django.utils.decorators import classproperty
 from django.utils.translation import ugettext_lazy as _
 
 from calendarweek.django import CalendarWeek, i18n_day_names_lazy, i18n_day_abbrs_lazy
+from colorfield.fields import ColorField
 from django_global_request.middleware import get_request
 
 from aleksis.core.mixins import ExtensibleModel
@@ -466,7 +467,10 @@ class LessonSubstitution(models.Model):
     )
     room = models.ForeignKey("Room", models.CASCADE, null=True, blank=True, verbose_name=_("Room"))
 
-    cancelled = models.BooleanField(default=False)
+    cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled?"))
+    cancelled_for_teachers = models.BooleanField(default=False, verbose_name=_("Cancelled for teachers?"))
+
+    comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True)
 
     def clean(self) -> None:
         if self.subject and self.cancelled:
@@ -587,3 +591,125 @@ class TimetableWidget(DashboardWidget):
     class Meta:
         proxy = True
         verbose_name = _("Timetable widget")
+
+
+class AbsenceReason(ExtensibleModel):
+    title = models.CharField(verbose_name=_("Title"), max_length=50)
+    description = models.TextField(verbose_name=_("Description"), blank=True, null=True)
+
+    class Meta:
+        verbose_name = _("Absence reason")
+        verbose_name_plural = _("Absence reasons")
+
+class Absence(ExtensibleModel):
+    reason = models.ForeignKey("AbsenceReason", on_delete=models.CASCADE, related_name="absences")
+    person = models.ManyToManyField("core.Person", related_name="absences")
+
+    date_start = models.DateField(verbose_name=_("Effective start date of absence"), null=True)
+    date_end = models.DateField(verbose_name=_("Effective end date of absence"), null=True)
+    period_from = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Effective start period of absence"), null=True, related_name="+")
+    period_to = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Effective end period of absence"), null=True, related_name="+")
+    comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True)
+
+    class Meta:
+        ordering = ["date_start"]
+        indexes = [models.Index(fields=["date_start", "date_end"])]
+        verbose_name = _("Absence")
+        verbose_name_plural = _("Absences")
+
+
+class Exam(ExtensibleModel):
+    lesson = models.ForeignKey("Lesson", on_delete=models.CASCADE, related_name="exams")
+
+    date = models.DateField(verbose_name=_("Date of exam"), null=True)
+    period_from = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Effective start period of exam"), null=True, related_name="+")
+    period_to = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Effective end period of exam"), null=True, related_name="+")
+
+    title = models.CharField(verbose_name=_("Title"), max_length=50)
+    comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True)
+
+    class Meta:
+        ordering = ["date"]
+        indexes = [models.Index(fields=["date"])]
+        verbose_name = _("Exam")
+        verbose_name_plural = _("Exams")
+
+
+class Holiday(ExtensibleModel):
+    title = models.CharField(verbose_name=_("Title of the holidays"), max_length=50)
+    date_start = models.DateField(verbose_name=_("Effective start date of holidays"), null=True)
+    date_end = models.DateField(verbose_name=_("Effective end date of holidays"), null=True)
+    comments = models.TextField(verbose_name=_("Comments"), null=True, blank=True)
+
+    class Meta:
+        ordering = ["date_start"]
+        indexes = [models.Index(fields=["date_start", "date_end"])]
+        verbose_name = _("Holiday")
+        verbose_name_plural = _("Holidays")
+
+
+class SupervisionArea(ExtensibleModel):
+    short_name = models.CharField(verbose_name=_("Short name"), max_length=10)
+    name = models.CharField(verbose_name=_("Long name"), max_length=50)
+    colour_fg = ColorField(default="#000000")
+    colour_bg = ColorField()
+
+    class Meta:
+        ordering = ["name"]
+        verbose_name = _("Supervision area")
+        verbose_name_plural = _("Supervision areas")
+
+
+class Break(ExtensibleModel):
+    short_name = models.CharField(verbose_name=_("Short name"), max_length=10)
+    name = models.CharField(verbose_name=_("Long name"), max_length=50)
+    weekday = models.PositiveSmallIntegerField(verbose_name=_("Week day"), choices=TimePeriod.WEEKDAY_CHOICES)
+    time_start = models.TimeField(verbose_name=_("Start time"))
+    time_end = models.TimeField(verbose_name=_("End time"))
+
+    class Meta:
+        ordering = ["weekday", "time_start"]
+        indexes = [models.Index(fields=["weekday", "time_start", "time_end"])]
+        verbose_name = _("Break")
+        verbose_name_plural = _("Breaks")
+
+
+class Supervision(ExtensibleModel):
+    area = models.ForeignKey(SupervisionArea, models.CASCADE, verbose_name=_("Supervision area"), related_name="supervisions")
+    break_item = models.ForeignKey(Break, models.CASCADE, verbose_name=_("Break"), related_name="supervisions")
+    teacher = models.ForeignKey("core.Person", models.CASCADE, related_name="supervisions", verbose_name=_("Teacher"))
+
+    class Meta:
+        ordering = ["area", "break_item"]
+        verbose_name= _("Supervision")
+        verbose_name_plural = _("Supervisions")
+
+
+class SupervisionSubstitution(ExtensibleModel):
+    date = models.DateField(verbose_name=_("Date"))
+    supervision = models.ForeignKey(Supervision, models.CASCADE, verbose_name=_("Supervision"), related_name="substitutions")
+    teacher = models.ForeignKey("core.Person", models.CASCADE, related_name="substituted_supervisions", verbose_name=_("Teacher"))
+
+    class Meta:
+        ordering = ["date", "supervision"]
+        verbose_name = _("Supervision substitution")
+        verbose_name_plural = _("Supervision substitutions")
+
+
+class Event(ExtensibleModel):
+    title = models.CharField(verbose_name=_("Title"), max_length=50)
+    date_start = models.DateField(verbose_name=_("Effective start date of event"), null=True)
+    date_end = models.DateField(verbose_name=_("Effective end date of event"), null=True)
+    absence_reason = models.ForeignKey("AbsenceReason", on_delete=models.CASCADE, related_name="absence_reason", verbose_name=_("Absence reason"))
+    period_from = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Effective start period of event"), related_name="+")
+    period_to = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Effective end period of event"), related_name="+")
+
+    groups = models.ManyToManyField("core.Group", related_name="events", verbose_name=_("Groups"))
+    rooms = models.ManyToManyField("Room", related_name="events", verbose_name=_("Rooms"))
+    teachers = models.ManyToManyField("core.Person", related_name="events", verbose_name=_("Teachers"))
+
+    class Meta:
+        ordering = ["date_start"]
+        indexes = [models.Index(fields=["period_from", "period_to", "date_start", "date_end"])]
+        verbose_name = _("Event")
+        verbose_name_plural = _("Events")