diff --git a/.gitignore b/.gitignore
index ec8b50c64d9d2dccbb75b4424fbe4a02d4ce817e..9d218124e6ea053643309cf65d03fbf71c7a9096 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,29 +1,29 @@
 # Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
 *$py.class
+*.py[cod]
+__pycache__/
 
 # Distribution / packaging
+*.egg
+*.egg-info/
 .Python
+.eggs/
+.installed.cfg
 build/
 develop-eggs/
 dist/
 downloads/
 eggs/
-.eggs/
 lib/
 lib64/
 parts/
 sdist/
 var/
 wheels/
-*.egg-info/
-.installed.cfg
-*.egg
 
 # Installer logs
-pip-log.txt
 pip-delete-this-directory.txt
+pip-log.txt
 
 # Translations
 *.mo
@@ -39,14 +39,17 @@ local_settings.py
 # Environments
 .env
 .venv
+ENV/
 env/
 venv/
-ENV/
 
 # Editors
 *~
 DEADJOE
 \#*#
+
+# IntelliJ
+.idea
 .idea/
 
 # Database
@@ -55,13 +58,17 @@ db.sqlite3
 # Sphinx
 docs/_build/
 
+# TeX
+*.aux
+
 # Generated files
-biscuit/static/
 biscuit/node_modules/
-biscuit/media/
+biscuit/static/
 
 .coverage
+.mypy_cache/
 .tox/
-maintenance_mode_state.txt
 htmlcov/
-.mypy_cache/
+maintenance_mode_state.txt
+media/
+package-lock.json
diff --git a/README.rst b/README.rst
index f6ef25051668258915fc5a05894a192bdaa35691..06c7091431c8465690afdb29ef8e9af1af1305d2 100644
--- a/README.rst
+++ b/README.rst
@@ -33,14 +33,20 @@ Licence
 
 ::
 
-  Copyright © 2019 Dominik George <dominik.george@teckids.org>
+  Copyright © 2019, 2020 Dominik George <dominik.george@teckids.org>
+  Copyright © 2019 Martin Gummi <martin.gummi@teckids.org>
+  Copyright © 2019 Julian Leucker <leuckeju@katharineum.de>
   Copyright © 2019 mirabilos <thorsten.glaser@teckids.org>
-  Copyright © 2019 Tom Teichler <tom.teichler@teckids.org>
+  Copyright © 2018, 2019 Frank Poetzsch-Heffter <p-h@katharineum.de>
+  Copyright © 2019, 2020 Tom Teichler <tom.teichler@teckids.org>
+  Copyright © 2018, 2019, 2020 Jonathan Weth <wethjo@katharineum.de>
+  Copyright © 2019, 2020 Hangzhi Yu <yuha@katharineum.de>
 
-  Licenced under the EUPL
+  Licenced under the EUPL, version 1.2 or later
 
 Please see the LICENCE file accompanying this distribution for the
 full licence text or on the `Europen Union Public Licence`_ website
+https://joinup.ec.europa.eu/collection/eupl/guidelines-users-and-developers
 (including all other official language versions).
 
 .. _BiscuIT-ng: https://edugit.org/BiscuIT/BiscuIT-ng
diff --git a/biscuit/core/dashboard/README.md b/biscuit/core/dashboard/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b9a8f00b2082812dfb0eb882849d75574277e19e
--- /dev/null
+++ b/biscuit/core/dashboard/README.md
@@ -0,0 +1,74 @@
+# Dashboard
+Das Dashboard dient dazu, den Benutzer zu begrüßen (> Startseite)
+und seine letzten Aktivitäten anzuzeigen.
+
+Edit: Außerdem zeigt das Dashboard aktuelle Nachrichten für den Benutzer an.
+
+## Aktivitäten
+Als Aktivität gilt alles, was der Nutzer selbst macht, d.h., bewusst.
+
+### Eine Aktivität registrieren
+1. Importieren
+
+        from .apps import <Meine App>Config
+        from dashboard.models import Activity
+
+2. Registrieren
+
+        act = Activity(title="<Titel der Aktion>", description="<Beschreibung der Aktion>", app=<Meine App>Config.verbose_name, user=<Benutzer Objekt>)
+        act.save()
+
+## Benachrichtigungen
+Als Benachrichtigung gilt eine Aktion, die den Nutzer betrifft.
+
+### Eine Benachrichtigung verschicken
+1. Importieren
+
+        from .apps import <Meine App>Config
+        from dashboard.models import Notification
+
+2. Verschicken
+
+          register_notification(title="<Titel der Nachricht>",
+                                      description="<Weitere Informationen>",
+                                      app=<Meine App>Config.verbose_name, user=<Benutzer Objekt>,
+                                      link=request.build_absolute_uri(<Link für weitere Informationen>))
+
+    **Hinweis:** Der angegebene Link muss eine absolute URL sein.
+    Dies wird durch übergabe eines dynamischen Linkes (z. B. /aub/1) an die Methode `request.build_absolute_uri()` erreicht.
+
+    Um einen dynamischen Link durch den Namen einer Django-URL zu "errechnen", dient die Methode `reverse()`.
+
+    Literatur:
+    - [1] https://docs.djangoproject.com/en/2.1/ref/request-response/#django.http.HttpRequest.build_absolute_uri
+    - [2] https://docs.djangoproject.com/en/2.1/ref/urlresolvers/#reverse
+
+## Caches
+### Sitecache
+Ein Seitencache basiert auf dem Django-Decorator `@cache_page`  und cacht die HTML-Ausgabe des entsprechenden Views.
+
+### Variablencache
+Ein Variablencache nutzt die Low-Level-Cache-API von Django und speichert den Inhalt einer Variable.
+
+### Verwaltung
+Jedes gecachte Objekt (ob Sitecache oder Variablencache) benötigt ein Cache-Objekt in der DB. Bei Cacheinhalten für die nur eine Variable gespeichert werden muss oder ein View, wird die Datei `caches.py` verwendet, wo der Cache als Konstante gespeichert ist:
+```
+<EXAMPLE_CACHE>, _ = Cache.objects.get_or_create(id="<example_cache>",
+                                                 defaults={
+                                                     "site_cache": <True/False>,
+                                                     "name": "<Readable name>",
+                                                     "expiration_time": <10>}) # in seconds
+
+```
+#### Variablencache
+Für Variablencaches kann mit der Funktion `get()` eines Cache-Objektes der aktuelle Inhalt des Caches abgefragt werden.
+Bei abgelaufenen Caches wird `False` zurückgeben, dann ist der Wert neu zu berechnen und mit `update(<new_value>)` zu aktualisieren, wobei die Aktualisierungszeit automatisch zurückgesetzt wird.
+
+#### Sitecache
+Für einen Sitecache kann folgender Decorator zum entsprechenden View hinzugefügt werden:
+```
+@cache_page(<EXAMPLE_CACHE>.expiration_time)
+```
+
+### Literatur
+- https://docs.djangoproject.com/en/2.2/topics/cache/
diff --git a/biscuit/core/dashboard/__init__.py b/biscuit/core/dashboard/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/biscuit/core/dashboard/admin.py b/biscuit/core/dashboard/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..a9286d91effa36ce9fdb9e4cc020c2a3506da854
--- /dev/null
+++ b/biscuit/core/dashboard/admin.py
@@ -0,0 +1,11 @@
+from django.contrib import admin
+from .models import Activity, Notification, Cache
+
+
+class CacheAdmin(admin.ModelAdmin):
+    readonly_fields = ["id", "site_cache", "last_time_updated"]
+
+
+admin.site.register(Activity)
+admin.site.register(Notification)
+admin.site.register(Cache, CacheAdmin)
diff --git a/biscuit/core/dashboard/apps.py b/biscuit/core/dashboard/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc6084702acf8de9b788588f5813d689a8bd82bf
--- /dev/null
+++ b/biscuit/core/dashboard/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class DashboardConfig(AppConfig):
+    name = 'dashboard'
+    verbose_name = "Dashboard"
diff --git a/biscuit/core/dashboard/caches.py b/biscuit/core/dashboard/caches.py
new file mode 100644
index 0000000000000000000000000000000000000000..663df3c5e5d474fb57c65775c9e0dc4ebe1025c3
--- /dev/null
+++ b/biscuit/core/dashboard/caches.py
@@ -0,0 +1,48 @@
+from dashboard.models import Cache
+
+PARSED_LESSONS_CACHE, _ = Cache.objects.get_or_create(id="parsed_lessons",
+                                                      defaults={"name": "Geparste Stunden (Regelplan)",
+                                                                "expiration_time": 60 * 60 * 24})
+
+DRIVE_CACHE, _ = Cache.objects.get_or_create(id="drive",
+                                             defaults={"name": "Zwischenspeicher für teachers, rooms, classses, etc.",
+                                                       "expiration_time": 60})
+
+EXPIRATION_TIME_CACHE_FOR_PLAN_CACHES, _ = Cache.objects.get_or_create(id="expiration_time_cache_for_plan_caches",
+                                                                       defaults={"name": "Ablaufzeit für Plan-Caches",
+                                                                                 "expiration_time": 60})
+
+BACKGROUND_CACHE_REFRESH, _ = Cache.objects.get_or_create(id="background_cache_refresh",
+                                                          defaults={
+                                                              "name": "Hintergrundaktualisierung der Variablencaches",
+                                                              "expiration_time": 0})
+
+PLAN_VIEW_CACHE, _ = Cache.objects.get_or_create(id="plan_view_cache",
+                                                 defaults={
+                                                     "site_cache": True,
+                                                     "name": "Wochenplan (Regelplan/SMART PLAN)",
+                                                     "expiration_time": 60})
+
+MY_PLAN_VIEW_CACHE, _ = Cache.objects.get_or_create(id="my_plan_view_cache",
+                                                    defaults={
+                                                        "site_cache": True,
+                                                        "name": "Mein Plan",
+                                                        "expiration_time": 60})
+
+SUBS_VIEW_CACHE, _ = Cache.objects.get_or_create(id="subs_view_cache",
+                                                 defaults={
+                                                     "site_cache": True,
+                                                     "name": "Vertretungen (Tabellenansicht)",
+                                                     "expiration_time": 60})
+
+LATEST_ARTICLE_CACHE, _ = Cache.objects.get_or_create(id="latest_article_cache",
+                                                      defaults={
+                                                          "name": "Letzter Artikel von der Homepage",
+                                                          "expiration_time": 60
+                                                      })
+
+CURRENT_EVENTS_CACHE, _ = Cache.objects.get_or_create(id="current_events_cache",
+                                                      defaults={
+                                                          "name": "Aktuelle Termine",
+                                                          "expiration_time": 60
+                                                      })
diff --git a/biscuit/core/dashboard/management/commands/refresh_caches.py b/biscuit/core/dashboard/management/commands/refresh_caches.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ed4421d4d6f3c3cc29a16ad8876c63eedd7058d
--- /dev/null
+++ b/biscuit/core/dashboard/management/commands/refresh_caches.py
@@ -0,0 +1,83 @@
+import datetime
+
+from django.core.management import BaseCommand
+from django.utils import timezone
+
+from dashboard.caches import BACKGROUND_CACHE_REFRESH
+from dashboard.models import Cache
+from util.network import get_newest_article_from_news, get_current_events_with_cal
+from timetable.views import get_next_weekday_with_time, get_calendar_week
+from untisconnect.drive import build_drive, TYPE_TEACHER, TYPE_CLASS, TYPE_ROOM
+from untisconnect.parse import parse
+from untisconnect.plan import get_plan
+
+
+class Command(BaseCommand):
+    help = 'Refresh all var caches'
+
+    def start(self, s):
+        self.stdout.write(s)
+
+    def finish(self):
+        self.stdout.write(self.style.SUCCESS('  Erledigt.'))
+
+    def handle(self, *args, **options):
+        self.start("Alte Caches löschen ...")
+        for cache in Cache.objects.filter(needed_until__isnull=False):
+            if not cache.is_needed():
+                print("Ist nicht mehr benötigt:", cache, ", benötigt bis", cache.needed_until)
+                cache.delete()
+        self.finish()
+
+        self.start("Aktualisiere Drive ... ")
+        drive = build_drive(force_update=True)
+        print(drive)
+        self.finish()
+
+        self.start("Aktualisiered Lessons ...")
+        parse(force_update=True)
+        self.finish()
+
+        self.start("Aktualisiere Pläne ...")
+
+        days = []
+        days.append(get_next_weekday_with_time(timezone.now(), timezone.now().time()))
+        days.append(get_next_weekday_with_time(days[0] + datetime.timedelta(days=1), datetime.time(0)))
+        print(days)
+
+        types = [
+            (TYPE_TEACHER, "teachers"),
+            (TYPE_CLASS, "classes"),
+            (TYPE_ROOM, "rooms")
+        ]
+        for type_id, type_key in types:
+            self.start(type_key)
+
+            for id, obj in drive[type_key].items():
+                self.start("  " + obj.name if obj.name is not None else "")
+                self.start("    Regelplan")
+                get_plan(type_id, id, force_update=True)
+                for day in days:
+
+                    calendar_week = day.isocalendar()[1]
+                    if day != days[0] and days[0].isocalendar()[1] == calendar_week and days[0].year == day.year:
+                        continue
+                    monday_of_week = get_calendar_week(calendar_week, day.year)["first_day"]
+
+                    self.start("    " + str(monday_of_week))
+                    get_plan(type_id, id, smart=True, monday_of_week=monday_of_week, force_update=True)
+
+        self.finish()
+
+        self.start("Aktualisiere Artikel ...")
+        get_newest_article_from_news(force_update=True)
+        self.finish()
+
+        self.start("Aktualisiere Termine ...")
+        get_current_events_with_cal(force_update=True)
+        self.finish()
+
+        self.start("Aktualisierungszeitpunkt in der Datenbank speichern ...")
+        BACKGROUND_CACHE_REFRESH.last_time_updated = timezone.now()
+        BACKGROUND_CACHE_REFRESH.save()
+        self.finish()
diff --git a/biscuit/core/dashboard/migrations/0001_initial.py b/biscuit/core/dashboard/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..b98c580d6c71ce0871d5260d9ae591322e824b0e
--- /dev/null
+++ b/biscuit/core/dashboard/migrations/0001_initial.py
@@ -0,0 +1,42 @@
+# Generated by Django 2.2.1 on 2019-05-29 15:06
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Notification',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(max_length=200)),
+                ('description', models.TextField(max_length=500)),
+                ('link', models.URLField(blank=True)),
+                ('app', models.CharField(max_length=100)),
+                ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications',
+                                           to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Activity',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(max_length=200)),
+                ('description', models.TextField(max_length=500)),
+                ('app', models.CharField(max_length=100)),
+                ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]
diff --git a/biscuit/core/dashboard/migrations/0002_cache.py b/biscuit/core/dashboard/migrations/0002_cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d146b35c7961c326f2b7de0bcf33ea6bdb552a5
--- /dev/null
+++ b/biscuit/core/dashboard/migrations/0002_cache.py
@@ -0,0 +1,25 @@
+# Generated by Django 2.2.1 on 2019-08-25 10:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('dashboard', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Cache',
+            fields=[
+                ('id',
+                 models.CharField(max_length=150, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
+                ('name', models.CharField(max_length=150, verbose_name='Name')),
+                ('expiration_time', models.IntegerField(default=20, verbose_name='Ablaufzeit')),
+            ],
+            options={
+                'verbose_name': 'Cacheeintrag',
+                'verbose_name_plural': 'Cacheeinträge',
+            },
+        ),
+    ]
diff --git a/biscuit/core/dashboard/migrations/0002_notification_read.py b/biscuit/core/dashboard/migrations/0002_notification_read.py
new file mode 100644
index 0000000000000000000000000000000000000000..19cc479866c684399fe5eb3c833a61e0c9e7aa98
--- /dev/null
+++ b/biscuit/core/dashboard/migrations/0002_notification_read.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.1 on 2019-09-01 08:40
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('dashboard', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='notification',
+            name='read',
+            field=models.BooleanField(default=False),
+        ),
+    ]
diff --git a/biscuit/core/dashboard/migrations/0003_cache_last_time_updated.py b/biscuit/core/dashboard/migrations/0003_cache_last_time_updated.py
new file mode 100644
index 0000000000000000000000000000000000000000..2cda5789033367e5a6cac0ab4c87543b82e4a849
--- /dev/null
+++ b/biscuit/core/dashboard/migrations/0003_cache_last_time_updated.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.1 on 2019-08-25 11:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('dashboard', '0002_cache'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='cache',
+            name='last_time_updated',
+            field=models.DateTimeField(blank=True, null=True,
+                                       verbose_name='Letzter Aktualisierungszeitpunkt des Caches'),
+        ),
+    ]
diff --git a/biscuit/core/dashboard/migrations/0004_cache_site_cache.py b/biscuit/core/dashboard/migrations/0004_cache_site_cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5ed2edb969cd34a0b45c5195ae4014c5f2a38c8
--- /dev/null
+++ b/biscuit/core/dashboard/migrations/0004_cache_site_cache.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.1 on 2019-08-25 11:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('dashboard', '0003_cache_last_time_updated'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='cache',
+            name='site_cache',
+            field=models.BooleanField(default=False, verbose_name='Seitencache?'),
+        ),
+    ]
diff --git a/biscuit/core/dashboard/migrations/0005_cache_needed_until.py b/biscuit/core/dashboard/migrations/0005_cache_needed_until.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1669563cba91ee60e961d442af05897e68f12e9
--- /dev/null
+++ b/biscuit/core/dashboard/migrations/0005_cache_needed_until.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.6 on 2019-11-08 20:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('dashboard', '0004_cache_site_cache'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='cache',
+            name='needed_until',
+            field=models.DateField(default=None, null=True, verbose_name='Benötigt bis'),
+        ),
+    ]
diff --git a/biscuit/core/dashboard/migrations/0006_merge_20191118_1939.py b/biscuit/core/dashboard/migrations/0006_merge_20191118_1939.py
new file mode 100644
index 0000000000000000000000000000000000000000..057086014b0e26b629955e76c6cdea6a40428772
--- /dev/null
+++ b/biscuit/core/dashboard/migrations/0006_merge_20191118_1939.py
@@ -0,0 +1,13 @@
+# Generated by Django 2.2.6 on 2019-11-18 18:39
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('dashboard', '0005_cache_needed_until'),
+        ('dashboard', '0002_notification_read'),
+    ]
+
+    operations = [
+    ]
diff --git a/biscuit/core/dashboard/migrations/__init__.py b/biscuit/core/dashboard/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/biscuit/core/dashboard/models.py b/biscuit/core/dashboard/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a26ce363f72ef0867614a389fc1c7b1062ee16f
--- /dev/null
+++ b/biscuit/core/dashboard/models.py
@@ -0,0 +1,127 @@
+import datetime
+
+from django.core.cache import cache
+from django.db import models
+from django.contrib.auth.models import User
+from django.utils import timezone
+
+from mailer import send_mail_with_template
+
+
+class Activity(models.Model):
+    user = models.ForeignKey(to=User, on_delete=models.CASCADE)
+
+    title = models.CharField(max_length=150)
+    description = models.TextField(max_length=500)
+
+    app = models.CharField(max_length=100)
+
+    created_at = models.DateTimeField(default=timezone.now)
+
+    def __str__(self):
+        return self.title
+
+
+class Notification(models.Model):
+    user = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name="notifications")
+    title = models.CharField(max_length=150)
+    description = models.TextField(max_length=500)
+    link = models.URLField(blank=True)
+
+    app = models.CharField(max_length=100)
+
+    read = models.BooleanField(default=False)
+    created_at = models.DateTimeField(default=timezone.now)
+
+    def __str__(self):
+        return self.title
+
+
+def register_notification(user, title, description, app="SchoolApps", link=""):
+    n = Notification(user=user, title=title, description=description, app=app, link=link)
+
+    n.save()
+    context = {
+        'notification': n
+    }
+    send_mail_with_template(title, [user.email], "mail/notification.txt", "mail/notification.html", context)
+
+
+class Cache(models.Model):
+    id = models.CharField(max_length=150, unique=True, primary_key=True, verbose_name="ID")
+    name = models.CharField(max_length=150, verbose_name="Name")
+    expiration_time = models.IntegerField(default=20, verbose_name="Ablaufzeit")
+    last_time_updated = models.DateTimeField(blank=True, null=True,
+                                             verbose_name="Letzter Aktualisierungszeitpunkt des Caches")
+    site_cache = models.BooleanField(default=False, verbose_name="Seitencache?")
+    needed_until = models.DateField(default=None, null=True, blank=True, verbose_name="Benötigt bis")
+
+    class Meta:
+        verbose_name = "Cacheeintrag"
+        verbose_name_plural = "Cacheeinträge"
+
+    def __str__(self):
+        return self.name or self.id
+
+    def update(self, new_value):
+        if not self.site_cache:
+            self.last_time_updated = timezone.now()
+            cache.set(self.id, new_value, self.expiration_time)
+            self.save()
+
+    def get(self):
+        if not self.site_cache:
+            return cache.get(self.id, False)
+        else:
+            return None
+
+    def is_expired(self) -> bool:
+        """
+        Checks whether a cache is expired
+        :return: Is cache expired?
+        """
+        # If cache never was updated it have to
+        if self.last_time_updated is None:
+            return True
+
+        # Else check if now is bigger than last time updated + expiration time
+        delta = datetime.timedelta(seconds=self.expiration_time)
+        return timezone.now() > self.last_time_updated + delta
+
+    def is_needed(self) -> bool:
+        """
+        Checks whether a plan can be deleted
+        :return: Is cache needed?
+        """
+        if self.needed_until is None:
+            return True
+        elif timezone.now().date() > self.needed_until:
+            return False
+        else:
+            return True
+
+    def delete(self, *args, **kwargs):
+        """Overrides model function delete to delete cache entry, too"""
+        cache.delete(self.id)
+        super(Cache, self).delete(*args, **kwargs)
+
+    def decorator(self, func):
+        decorator_cache = self
+
+        def wrapper(*args, **kwargs):
+            if "force_update" in kwargs:
+                force_update = kwargs["force_update"]
+                del kwargs["force_update"]
+            else:
+                force_update = False
+            cached = decorator_cache.get()
+            if cached is not False and not force_update:
+                print("CACHED VALUE FOR ", func)
+                return cached
+
+            print("NON CACHED VALUE FOR ", func, "FORCE", force_update)
+            res = func(*args, **kwargs)
+            decorator_cache.update(res)
+            return res
+
+        return wrapper
diff --git a/biscuit/core/dashboard/plan_caches.py b/biscuit/core/dashboard/plan_caches.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b22a3293e8467fe0842e75ec37abefbe59761ed
--- /dev/null
+++ b/biscuit/core/dashboard/plan_caches.py
@@ -0,0 +1,51 @@
+import datetime
+from django.utils import timezone
+from dashboard.caches import EXPIRATION_TIME_CACHE_FOR_PLAN_CACHES
+from untisconnect.drive import drive, TYPE_TEACHER, TYPE_CLASS, Cache
+from untisconnect.api_helper import date_to_untis_date
+
+
+def get_cache_for_plan(type: int, id: int, smart: bool = False, monday_of_week=None) -> Cache:
+    """
+    Creates a Cache for a plan with given params
+    :param type: TYPE_TEACHER, TYPE_CLASS or TYPE_ROOM
+    :param id: database id of plan
+    :param smart: Is smart?
+    :param monday_of_week: Monday of needed week (if smart)
+    :return: Cache object
+    """
+
+    # Create unique id for plan cache
+    cache_id = "plan_{}_{}{}".format(type, id, "_smart" if smart else "")
+
+    # Decide which type of plan
+    if type == TYPE_TEACHER:
+        idx = "teachers"
+    elif type == TYPE_CLASS:
+        idx = "classes"
+    else:
+        idx = "rooms"
+
+    # Set name for cache entry
+    name = "Stundenplan für {}".format(drive[idx][id])
+
+    needed_until = timezone.now().date() + datetime.timedelta(days=1)
+    if smart:
+        # Add date to cache id and name if smart plan
+        cache_id += "_" + date_to_untis_date(monday_of_week)
+        name += ", " + date_to_untis_date(monday_of_week)
+
+        # Set time after which cache will be deleted
+        needed_until = monday_of_week + datetime.timedelta(days=4)
+
+    # Create new cache entry
+    cache = Cache.objects.get_or_create(id=cache_id)[0]
+
+    # Set expiration time and name to cache entry
+    if cache.expiration_time != EXPIRATION_TIME_CACHE_FOR_PLAN_CACHES.expiration_time or cache.name != name:
+        cache.expiration_time = EXPIRATION_TIME_CACHE_FOR_PLAN_CACHES.expiration_time
+        cache.name = name
+        cache.needed_until = needed_until
+        cache.save()
+
+    return cache
diff --git a/biscuit/core/dashboard/settings.py b/biscuit/core/dashboard/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..4dc09f18c3e8d8f2979aa5e8eb6dda8479f85735
--- /dev/null
+++ b/biscuit/core/dashboard/settings.py
@@ -0,0 +1,31 @@
+import dbsettings
+
+
+class LatestArticleSettings(dbsettings.Group):
+    latest_article_is_activated = dbsettings.BooleanValue("Funktion aktivieren?", default=True)
+    wp_domain = dbsettings.StringValue("WordPress-Domain", help_text="Ohne abschließenden Slash",
+                                       default="https://katharineum-zu-luebeck.de")
+    replace_vs_composer_stuff = dbsettings.BooleanValue("VisualComposer-Tags durch regulären Ausdruck entfernen?",
+                                                        default=True)
+
+
+class CurrentEventsSettings(dbsettings.Group):
+    current_events_is_activated = dbsettings.BooleanValue("Funktion aktivieren?", default=True)
+    calendar_url = dbsettings.StringValue("URL des Kalenders", help_text="Pfad zu einer ICS-Datei",
+                                          default="https://nimbus.katharineum.de/remote.php/dav/public-calendars"
+                                                  "/owit7yysLB2CYNTq?export")
+    events_count = dbsettings.IntegerValue("Anzahl der Termine, die angezeigt werden sollen", default=5)
+
+
+class MyStatusSettings(dbsettings.Group):
+    my_status_is_activated = dbsettings.BooleanValue("Funktion aktivieren?", default=True)
+
+
+class CurrentExamsSettings(dbsettings.Group):
+    current_exams_is_activated = dbsettings.BooleanValue("Funktion aktivieren?", default=True)
+
+
+latest_article_settings = LatestArticleSettings("Funktion: Letzter Artikel")
+current_events_settings = CurrentEventsSettings("Funktion: Aktuelle Termine")
+my_status_settings = MyStatusSettings("Funktion: Mein Status")
+current_exams_settings = MyStatusSettings("Funktion: Aktuelle Klausuren")
diff --git a/biscuit/core/dashboard/templates/dashboard/index.html b/biscuit/core/dashboard/templates/dashboard/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..8450678118187c74ce34ce317a27047a844b8f0e
--- /dev/null
+++ b/biscuit/core/dashboard/templates/dashboard/index.html
@@ -0,0 +1,18 @@
+{% load staticfiles %}
+{% include 'partials/header.html' %}
+
+<main>
+    <script>
+        var API_URL = "{% url "api_information" %}";
+        var MY_PLAN_URL = "{% url "timetable_my_plan" %}";
+    </script>
+    <script src="{% static "js/moment-with-locales.min.js" %}"></script>
+    {% include "components/react.html" %}
+    <script src="{% static "js/dashboard.js" %}"></script>
+
+    <div id="dashboard_container">
+
+    </div>
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/biscuit/core/dashboard/templates/dashboard/tools.html b/biscuit/core/dashboard/templates/dashboard/tools.html
new file mode 100644
index 0000000000000000000000000000000000000000..cce11ff3de4d0eccb06203b18d431f6a6b544a91
--- /dev/null
+++ b/biscuit/core/dashboard/templates/dashboard/tools.html
@@ -0,0 +1,76 @@
+{% include 'partials/header.html' %}
+{% load msg_box %}
+<main>
+    <h4>Tools</h4>
+    <div class="card">
+        <div class="card-content">
+            <span class="card-title">
+                Cache-Management
+            </span>
+            {% msg_box "Nach Änderung der Ablaufzeit muss der entsprechende Cache (bei Variablencaches) bzw. der gesamte Cache (bei Sitecaches) geleert werden, damit die Änderung wirksam wird." "warning" "warning" %}
+            <table class="highlight">
+                <thead>
+                <tr>
+                    <th>ID</th>
+                    <th>Name</th>
+                    <th>Typ</th>
+                    <th>Ablaufzeit</th>
+                    <th>Letzter Aktualisierungszeitpunkt des Caches</th>
+                    <th>Aktionen</th>
+                </tr>
+                </thead>
+                <tbody>
+                {% for cache in caches %}
+                    <tr>
+                        <td>
+                            <pre>{{ cache.id }}</pre>
+                        </td>
+                        <td>{{ cache.name }}</td>
+                        <td>
+                            <span class="badge new">{{ cache.site_cache|yesno:"Seitencache,Variablencache" }}</span>
+                        </td>
+
+                        <td>{{ cache.expiration_time }} s</td>
+                        <td>
+                            {% if cache.site_cache %}
+                                k. A.
+                            {% else %}
+                                {{ cache.last_time_updated }}
+                                {% if cache.is_expired %}
+                                    <span class="badge new red">Abgelaufen</span>
+                                {% else %}
+                                    <span class="badge new green">Gültig</span>
+                                {% endif %}
+                            {% endif %}
+                        </td>
+                        <td>
+                            {% if not cache.site_cache %}
+                                <a class="btn-flat waves-effect waves-red red-text"
+                                   href="{% url "tools_clear_single_cache" cache.id %}">
+                                    <i class="material-icons left">delete</i> Cache leeren
+                                </a>
+                            {% endif %}
+                            <a class="btn-flat waves-effect waves-orange orange-text"
+                               href="{% url "admin:dashboard_cache_change" cache.id %}" target="_blank">
+                                <i class="material-icons left">edit</i>
+                                Ablaufzeit bearbeiten
+                            </a>
+
+                        </td>
+                    </tr>
+                {% endfor %}
+                </tbody>
+            </table>
+            {% if msg == "success_cleared_whole_cache" %}
+                {% msg_box "Der gesamte Cache wurde erfolgreich geleert." "success" "check_circle" %}
+            {% elif msg == "success_cleared_single_cache" %}
+                {% msg_box "Ein Cache wurde erfolgreich geleert." "success" "check_circle" %}
+            {% endif %}
+        </div>
+        <div class="card-action">
+            <a href="{% url "tools_clear_cache" %}">Cache leeren</a>
+        </div>
+    </div>
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/biscuit/core/dashboard/urls.py b/biscuit/core/dashboard/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8b08c524694d3efb13566caf7a4b539195066d2
--- /dev/null
+++ b/biscuit/core/dashboard/urls.py
@@ -0,0 +1,34 @@
+from django.db import ProgrammingError
+from django.urls import path
+
+from untisconnect.models import Terms, Schoolyear
+
+try:
+    import dashboard.views.dashboard as views
+
+    urlpatterns = [
+        path('', views.index, name='dashboard'),
+        path('api', views.api_information, name="api_information"),
+        path('api/notifications/read/<int:id>', views.api_read_notification, name="api_read_notification"),
+        path('api/my-plan', views.api_my_plan_html, name="api_my_plan_html"),
+    ]
+
+except (Terms.DoesNotExist, Schoolyear.DoesNotExist, ProgrammingError):
+    from timetable import fallback_view
+
+    urlpatterns = [
+        path('', fallback_view.fallback, name='dashboard'),
+        path('api', fallback_view.fallback, name="api_information"),
+        path('api/notifications/read/<int:id>', fallback_view.fallback, name="api_read_notification"),
+        path('api/my-plan', fallback_view.fallback, name="api_my_plan_html"),
+    ]
+
+import dashboard.views.tools as tools_views
+
+urlpatterns += [
+    path('offline', tools_views.offline, name='offline'),
+    path("tools", tools_views.tools, name="tools"),
+    path("tools/clear-cache", tools_views.tools_clear_cache, name="tools_clear_cache"),
+    path("tools/clear-cache/<str:id>", tools_views.tools_clear_cache, name="tools_clear_single_cache"),
+    path('about/', tools_views.about, name='about')
+]
diff --git a/biscuit/core/dashboard/views/dashboard.py b/biscuit/core/dashboard/views/dashboard.py
new file mode 100644
index 0000000000000000000000000000000000000000..a45100ed064711904d77895318c5614e4c530266
--- /dev/null
+++ b/biscuit/core/dashboard/views/dashboard.py
@@ -0,0 +1,142 @@
+from email.utils import formatdate
+
+from django.contrib.auth.decorators import login_required
+from django.http import JsonResponse
+from django.shortcuts import get_object_or_404
+from django.shortcuts import render
+from django.template.loader import render_to_string
+from django.utils import timezone
+from martor.templatetags.martortags import safe_markdown
+
+from dashboard.models import Activity, Notification
+from dashboard.settings import latest_article_settings, current_events_settings
+from timetable.hints import get_all_hints_by_class_and_time_period, get_all_hints_for_teachers_by_time_period
+from timetable.views import get_next_weekday_with_time
+from untisconnect.api import TYPE_TEACHER, TYPE_CLASS
+from untisconnect.datetimeutils import get_name_for_next_week_day_from_today
+from untisconnect.utils import get_type_and_object_of_user, get_plan_for_day
+from util.network import get_newest_article_from_news, get_current_events_with_cal
+
+
+@login_required
+def index(request):
+    """ Dashboard: Show daily relevant information """
+
+    return render(request, 'dashboard/index.html')
+
+
+@login_required
+def api_information(request):
+    """ API request: Give information for dashboard in JSON """
+    # Load activities
+    activities = Activity.objects.filter(user=request.user).order_by('-created_at')[:5]
+
+    # Load notifications
+    notifications = request.user.notifications.all().filter(user=request.user).order_by('-created_at')[:5]
+    unread_notifications = request.user.notifications.all().filter(user=request.user, read=False).order_by(
+        '-created_at')
+
+    # Get latest article from homepage
+    if latest_article_settings.latest_article_is_activated:
+        newest_article = get_newest_article_from_news(domain=latest_article_settings.wp_domain)
+    else:
+        newest_article = None
+
+    # Get date information
+    date_formatted = get_name_for_next_week_day_from_today()
+    next_weekday = get_next_weekday_with_time(timezone.now(), timezone.now().time())
+
+    # Get user type (student, teacher, etc.)
+    _type, el = get_type_and_object_of_user(request.user)
+
+    # Get hints
+    if _type == TYPE_TEACHER:
+        # Get hints for teachers
+        hints = list(get_all_hints_for_teachers_by_time_period(next_weekday, next_weekday))
+    elif _type == TYPE_CLASS:
+        # Get hints for students
+        hints = list(get_all_hints_by_class_and_time_period(el, next_weekday, next_weekday))
+    else:
+        hints = []
+
+    # Serialize hints
+    ser = []
+    for hint in hints:
+        serialized = {
+            "from_date": formatdate(float(hint.from_date.strftime('%s'))),
+            "to_date": formatdate(float(hint.to_date.strftime('%s'))),
+            "html": safe_markdown(hint.text)
+        }
+        ser.append(serialized)
+    hints = ser
+
+    context = {
+        'activities': list(activities.values()),
+        'notifications': list(notifications.values()),
+        "unread_notifications": list(unread_notifications.values()),
+        # 'user_type': UserInformation.user_type(request.user),
+        # 'user_type_formatted': UserInformation.user_type_formatted(request.user),
+        # 'classes': UserInformation.user_classes(request.user),
+        # 'courses': UserInformation.user_courses(request.user),
+        # 'subjects': UserInformation.user_subjects(request.user),
+        # 'has_wifi': UserInformation.user_has_wifi(request.user),
+        "newest_article": newest_article,
+        "current_events": get_current_events_with_cal() if current_events_settings.current_events_is_activated else None,
+        "date_formatted": date_formatted,
+        "user": {
+            "username": request.user.username,
+            "full_name": request.user.first_name
+        }
+    }
+
+    # If plan is available for user give extra information
+    if _type is not None and request.user.has_perm("timetable.show_plan"):
+        context["plan"] = {
+            "type": _type,
+            "name": el.shortcode if _type == TYPE_TEACHER else el.name,
+            "hints": hints
+        }
+        context["has_plan"] = True
+    else:
+        context["has_plan"] = False
+
+    return JsonResponse(context)
+
+
+@login_required
+def api_read_notification(request, id):
+    """ API request: Mark notification as read """
+
+    notification = get_object_or_404(Notification, id=id, user=request.user)
+    notification.read = True
+    notification.save()
+
+    return JsonResponse({"success": True})
+
+
+@login_required
+def api_my_plan_html(request):
+    """ API request: Get rendered lessons with substitutions for dashboard """
+
+    # Get user type (student, teacher, etc.)
+    _type, el = get_type_and_object_of_user(request.user)
+
+    # Plan is only for teachers and students available
+    if (_type != TYPE_TEACHER and _type != TYPE_CLASS) or not request.user.has_perm("timetable.show_plan"):
+        return JsonResponse({"success": False})
+
+    # Get calendar week and monday of week
+    next_weekday = get_next_weekday_with_time()
+
+    # Get plan
+    plan, holiday = get_plan_for_day(_type, el.id, next_weekday)
+
+    # Serialize plan
+    lessons = []
+    for lesson_container, time in plan:
+        html = render_to_string("timetable/lesson.html", {"col": lesson_container, "type": _type}, request=request)
+        lessons.append({"time": time, "html": html})
+
+    # Return JSON
+    return JsonResponse(
+        {"success": True, "lessons": lessons, "holiday": holiday[0].__dict__ if len(holiday) > 0 else None})
diff --git a/biscuit/core/dashboard/views/tools.py b/biscuit/core/dashboard/views/tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..42d116cffcf82837c872195fdaf488d276447630
--- /dev/null
+++ b/biscuit/core/dashboard/views/tools.py
@@ -0,0 +1,45 @@
+from django.contrib.auth.decorators import login_required, user_passes_test
+from django.core.cache import cache
+from django.shortcuts import render, redirect
+from django.urls import reverse
+
+from dashboard.models import Cache
+from meta import OPEN_SOURCE_COMPONENTS
+
+
+def offline(request):
+    return render(request, 'common/offline.html')
+
+
+def about(request):
+    return render(request, "common/about.html", context={"components": OPEN_SOURCE_COMPONENTS})
+
+
+@login_required
+@user_passes_test(lambda u: u.is_superuser)
+def tools(request):
+    msg = None
+    if request.session.get("msg", False):
+        msg = request.session["msg"]
+        request.session["msg"] = None
+
+    caches = Cache.objects.all()
+    context = {
+        "msg": msg,
+        "caches": caches
+    }
+    return render(request, "dashboard/tools.html", context)
+
+
+@login_required
+def tools_clear_cache(request, id=None):
+    if id is not None:
+        cache.delete(id)
+        request.session["msg"] = "success_cleared_single_cache"
+        print("[IMPORTANT] Single cache cleared!")
+    else:
+        cache.clear()
+        request.session["msg"] = "success_cleared_whole_cache"
+        print("[IMPORTANT] Whole cache cleared!")
+
+    return redirect(reverse("tools"))
diff --git a/biscuit/core/debug/__init__.py b/biscuit/core/debug/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/biscuit/core/debug/admin.py b/biscuit/core/debug/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb9534da72a30417ee61e3f5cfff774bac9275bd
--- /dev/null
+++ b/biscuit/core/debug/admin.py
@@ -0,0 +1,16 @@
+from django.contrib import admin
+
+from .models import DebugLogGroup, DebugLog
+
+
+class DebugLogAdmin(admin.ModelAdmin):
+    readonly_fields = ["id", "group", "return_code", "filename", "updated_at"]
+
+
+class DebugLogGroupAdmin(admin.ModelAdmin):
+    readonly_fields = ["id"]
+
+
+# Register your models here.
+admin.site.register(DebugLogGroup, DebugLogGroupAdmin)
+admin.site.register(DebugLog, DebugLogAdmin)
diff --git a/biscuit/core/debug/apps.py b/biscuit/core/debug/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c1d0c0a15e96063ee78f411be2bd78e91aada77
--- /dev/null
+++ b/biscuit/core/debug/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class DebugConfig(AppConfig):
+    name = 'debug'
diff --git a/biscuit/core/debug/migrations/0001_initial.py b/biscuit/core/debug/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6775658862d3397caee389ea94a04ca1dc134b0
--- /dev/null
+++ b/biscuit/core/debug/migrations/0001_initial.py
@@ -0,0 +1,47 @@
+# Generated by Django 2.2.1 on 2019-05-23 13:59
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='DebugLogGroup',
+            fields=[
+                ('id', models.CharField(max_length=100, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=200, verbose_name='Name')),
+                ('desc_as_pre',
+                 models.CharField(blank=True, max_length=250, verbose_name='Beschreibung, dargestellt als HTML-PRE')),
+            ],
+            options={
+                'verbose_name': 'Debug-Log-Gruppe',
+                'verbose_name_plural': 'Debug-Log-Gruppen',
+            },
+        ),
+        migrations.CreateModel(
+            name='DebugLog',
+            fields=[
+                ('id', models.CharField(max_length=100, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=200, verbose_name='Name')),
+                ('return_code', models.IntegerField(blank=True, null=True, verbose_name='UNIX-Rückgabecode')),
+                ('filename', models.FilePathField(match='.*.log', path='/home/wethjo/dev/school-apps/schoolapps/latex',
+                                                  verbose_name='Dateiname zur Logdatei')),
+                ('updated_at',
+                 models.DateTimeField(default=django.utils.timezone.now, verbose_name='Aktualisierungszeitpunkt')),
+                ('group',
+                 models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL,
+                                   related_name='logs', to='debug.DebugLogGroup', verbose_name='Gruppe')),
+            ],
+            options={
+                'verbose_name': 'Debug-Log',
+                'verbose_name_plural': 'Debug-Logs',
+            },
+        ),
+    ]
diff --git a/biscuit/core/debug/migrations/0002_auto_20190523_1627.py b/biscuit/core/debug/migrations/0002_auto_20190523_1627.py
new file mode 100644
index 0000000000000000000000000000000000000000..9db1d9ed1b1d80e6efdc4b16093025c0baaf44ab
--- /dev/null
+++ b/biscuit/core/debug/migrations/0002_auto_20190523_1627.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.2.1 on 2019-05-23 14:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('debug', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='debuglog',
+            name='log_text',
+            field=models.TextField(blank=True, verbose_name='Log-Text (falls nicht Datei)'),
+        ),
+        migrations.AlterField(
+            model_name='debuglog',
+            name='filename',
+            field=models.FilePathField(blank=True, match='.*.log', path='/home/wethjo/dev/school-apps/schoolapps/latex',
+                                       verbose_name='Dateiname zur Logdatei (falls nicht Log-Text)'),
+        ),
+    ]
diff --git a/biscuit/core/debug/migrations/0003_auto_20190818_0910.py b/biscuit/core/debug/migrations/0003_auto_20190818_0910.py
new file mode 100644
index 0000000000000000000000000000000000000000..a21678f276bb0834100127ef13f870aba02802da
--- /dev/null
+++ b/biscuit/core/debug/migrations/0003_auto_20190818_0910.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.1 on 2019-08-18 07:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('debug', '0002_auto_20190523_1627'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='debuglog',
+            name='filename',
+            field=models.FilePathField(blank=True, match='.*.log', path='/home/p-h/git/school-apps/schoolapps/latex', verbose_name='Dateiname zur Logdatei (falls nicht Log-Text)'),
+        ),
+    ]
diff --git a/biscuit/core/debug/migrations/0004_auto_20190916_1450.py b/biscuit/core/debug/migrations/0004_auto_20190916_1450.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0905c799ba5552647595bfa2e8a4cc7d7b44c9c
--- /dev/null
+++ b/biscuit/core/debug/migrations/0004_auto_20190916_1450.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.4 on 2019-09-16 12:50
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('debug', '0003_auto_20190818_0910'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='debuglog',
+            name='filename',
+            field=models.FilePathField(blank=True, match='.*.log', path='/data/Silas/Daten/school-apps/schoolapps/latex', verbose_name='Dateiname zur Logdatei (falls nicht Log-Text)'),
+        ),
+    ]
diff --git a/biscuit/core/debug/migrations/__init__.py b/biscuit/core/debug/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/biscuit/core/debug/models.py b/biscuit/core/debug/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..831f537a54a336f1f25d7e338dbf3acef75631ba
--- /dev/null
+++ b/biscuit/core/debug/models.py
@@ -0,0 +1,138 @@
+import os
+import traceback
+
+from django.db import models
+from django.utils import timezone
+
+from schoolapps.settings import BASE_DIR
+
+
+class DebugLogGroup(models.Model):
+    # Meta
+    id = models.CharField(primary_key=True, blank=False, max_length=100, verbose_name="ID")
+    name = models.CharField(blank=False, max_length=200, verbose_name="Name")
+    desc_as_pre = models.CharField(blank=True, max_length=250, verbose_name="Beschreibung, dargestellt als HTML-PRE")
+
+    class Meta:
+        verbose_name = "Debug-Log-Gruppe"
+        verbose_name_plural = "Debug-Log-Gruppen"
+
+    def __str__(self):
+        return self.name or self.id
+
+    def is_successful(self):
+        """
+        :return: Were all operations in this group successful?
+        """
+        successful = True
+        for log in self.logs.all():
+            if not log.is_successful():
+                successful = False
+        return successful
+
+
+DEBUG_LOG_DIR = os.path.join(BASE_DIR, "latex")
+
+
+class DebugLog(models.Model):
+    # Meta
+    id = models.CharField(primary_key=True, blank=False, max_length=100, verbose_name="ID")
+    name = models.CharField(blank=False, max_length=200, verbose_name="Name")
+    group = models.ForeignKey(DebugLogGroup, on_delete=models.SET_NULL, default=None, null=True, blank=True,
+                              related_name="logs", verbose_name="Gruppe")  # If null, it wouldn't be displayed
+
+    # Data
+    return_code = models.IntegerField(blank=True, null=True, verbose_name="UNIX-Rückgabecode")
+    filename = models.FilePathField(path=DEBUG_LOG_DIR, match=".*.log",
+                                    verbose_name="Dateiname zur Logdatei (falls nicht Log-Text)", blank=True)
+    log_text = models.TextField(verbose_name="Log-Text (falls nicht Datei)", blank=True)
+    updated_at = models.DateTimeField(blank=False, default=timezone.now, verbose_name="Aktualisierungszeitpunkt")
+
+    class Meta:
+        verbose_name = "Debug-Log"
+        verbose_name_plural = "Debug-Logs"
+
+    def __str__(self):
+        return self.name or self.id
+
+    def get_file_content(self):
+        """
+        :return: The log text (file or DB)
+        """
+        if self.filename:
+            print(self.filename)
+            f = open(os.path.join(DEBUG_LOG_DIR, self.filename), "r")
+            content = f.read()
+            f.close()
+            return content
+        elif self.log_text:
+            return self.log_text
+        else:
+            return ""
+
+    def is_successful(self):
+        """
+        :return: Was the last operation successful?
+        """
+        return self.return_code == 0
+
+
+def get_log_group_by_id(id):
+    """
+    Get a log group from DB by given id
+    :param id: ID of log group
+    :return:
+    """
+    p, _ = DebugLogGroup.objects.get_or_create(id=id)
+    return p
+
+
+def register_log_with_filename(id, group_id, filename, return_code):
+    """
+    Register a operation in debugging tool with a log file
+
+    :param id: id of operation
+    :param group_id: id of group
+    :param filename: file path (based on latex dir)
+    :param return_code: UNIX return code
+    """
+    p, _ = DebugLog.objects.get_or_create(id=id)
+    group = get_log_group_by_id(group_id)
+    p.group = group
+    p.return_code = return_code
+    p.filename = filename
+    p.updated_at = timezone.now()
+    p.save()
+
+
+def register_return_0(id, group_id):
+    """
+    Register a operation in debugging tool with an return code of 0 (success) and no log text/log file
+
+    :param id: id of operation
+    :param group_id: id of group
+    """
+    p, _ = DebugLog.objects.get_or_create(id=id)
+    group = get_log_group_by_id(group_id)
+    p.group = group
+    p.return_code = 0
+    p.log_text = ""
+    p.updated_at = timezone.now()
+    p.save()
+
+
+def register_traceback(id, group_id):
+    """
+    Register a operation in debugging tool with an return code of 1 (error) and a log text
+
+    :param id: id of operation
+    :param group_id: id of group
+    """
+    msg = traceback.format_exc()
+    p, _ = DebugLog.objects.get_or_create(id=id)
+    group = get_log_group_by_id(group_id)
+    p.group = group
+    p.return_code = 1
+    p.log_text = msg
+    p.updated_at = timezone.now()
+    p.save()
diff --git a/biscuit/core/debug/templates/debug/debug.html b/biscuit/core/debug/templates/debug/debug.html
new file mode 100644
index 0000000000000000000000000000000000000000..0f6cbc52608ae4b413088d09fa6f1ccd2b9543cf
--- /dev/null
+++ b/biscuit/core/debug/templates/debug/debug.html
@@ -0,0 +1,80 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+{% load martortags %}
+{% load staticfiles %}
+<link rel="stylesheet" href="{% static "css/highlight.min.css" %}">
+<script src="{% static "js/highlight.min.js" %}"></script>
+<script>hljs.initHighlightingOnLoad();</script>
+
+<style>
+    .debug-li {
+        line-height: 2;
+    }
+
+    .debug-icon {
+        font-size: 2.5rem;
+    }
+</style>
+
+<main>
+    <a class="btn-flat waves-effect waves-teal right btn-flat-medium" href="{% url "debug_logs" %}"><i
+            class="material-icons refresh center">refresh</i></a>
+
+    <h4>Debuggingtool</h4>
+
+
+    <h5>Schnellüberblick</h5>
+    <div class="row">
+        {% for group in groups %}
+            <div class="col s12 m4">
+                <div class="card">
+                    <div class="card-content">
+                        <i class="material-icons right {{ group.is_successful|yesno:"green,red" }}-text debug-icon">{{ group.is_successful|yesno:"check_circle,error" }}</i>
+                        <span class="card-title">{{ group.name }}</span>
+                        <ul>
+                            {% for log in group.logs.all %}
+                                <li class="debug-li">
+                                    <i class="material-icons right {{ log.is_successful|yesno:"green,red" }}-text">{{ log.is_successful|yesno:"check_circle,error" }}</i>
+                                    {{ log.name }}
+                                </li>
+                            {% endfor %}
+                        </ul>
+                    </div>
+                    <div class="card-action">
+                        <a href="#{{ group.id }}">
+                            Log anzeigen
+                        </a>
+                    </div>
+                </div>
+
+            </div>
+        {% endfor %}
+    </div>
+
+
+    <h5>Logs</h5>
+    {% for group in groups %}
+        <div class="card" id="{{ group.id }}">
+            <div class="card-content">
+                <i class="material-icons right {{ group.is_successful|yesno:"green,red" }}-text medium">{{ group.is_successful|yesno:"check_circle,error" }}</i>
+                <span class="card-title">{{ group.name }}</span>
+                <pre>{{ group.desc_as_pre|default:" " }}</pre>
+                {% for log in group.logs.all %}
+                    <div>
+                        <i class="material-icons right {{ log.is_successful|yesno:"green,red" }}-text small">{{ log.is_successful|yesno:"check_circle,error" }}</i>
+
+                        <h5>{{ log.name }} </h5>
+                        <p><i class="material-icons left">access_time</i> {{ log.updated_at }}</p>
+                        <pre><code class="plaintext scroll-fix">RETURN CODE: {{ log.return_code }}
+
+{{ log.get_file_content }}
+                    </code></pre>
+                    </div>
+                {% endfor %}
+            </div>
+        </div>
+    {% endfor %}
+
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/biscuit/core/debug/tests.py b/biscuit/core/debug/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/biscuit/core/debug/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/biscuit/core/debug/urls.py b/biscuit/core/debug/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee68b8411b7aaece1a9194a0fd7c48e6fa17172b
--- /dev/null
+++ b/biscuit/core/debug/urls.py
@@ -0,0 +1,8 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+    path("", views.debugging_tool),
+    path("logs/", views.debugging_tool, name="debug_logs")
+]
diff --git a/biscuit/core/debug/views.py b/biscuit/core/debug/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..8531e3e3efcd8cc82b3e7a615df4ec4e2f22e970
--- /dev/null
+++ b/biscuit/core/debug/views.py
@@ -0,0 +1,11 @@
+from django.contrib.auth.decorators import login_required, permission_required
+from django.shortcuts import render
+
+from debug.models import DebugLogGroup
+
+
+@login_required
+@permission_required("debug.can_view_debug_log")
+def debugging_tool(request):
+    groups = DebugLogGroup.objects.all()
+    return render(request, "debug/debug.html", {"groups": groups})
diff --git a/biscuit/core/mailer.py b/biscuit/core/mailer.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cd4c0c98cb0927e6aa72c0561a5327c10a67cad
--- /dev/null
+++ b/biscuit/core/mailer.py
@@ -0,0 +1,20 @@
+from django.core.mail import send_mail
+from django.template.loader import render_to_string
+
+SENDER_EMAIL = 'SchoolApps <infoplan@katharineum.de>'
+
+
+def send_mail_with_template(title, receivers, plain_template, html_template, context={}, sender_email=SENDER_EMAIL):
+    msg_plain = render_to_string(plain_template, context)
+    msg_html = render_to_string(html_template, context)
+
+    try:
+        send_mail(
+            title,
+            msg_plain,
+            sender_email,
+            receivers,
+            html_message=msg_html,
+        )
+    except Exception as e:
+        print("[EMAIL PROBLEM] ", e)
diff --git a/biscuit/core/meta.py b/biscuit/core/meta.py
new file mode 100644
index 0000000000000000000000000000000000000000..9af2f86c59f640d67970a38470a11f3f616d5fbe
--- /dev/null
+++ b/biscuit/core/meta.py
@@ -0,0 +1,64 @@
+import os
+from django.conf import settings
+
+# Build path for copyright
+copyright_path = os.path.abspath(os.path.join(settings.BASE_DIR, '..', 'COPYRIGHT.md'))
+
+# Read copyright from file
+with open(copyright_path, "r") as f:
+    COPYRIGHT = f.read()
+
+COPYRIGHT_SHORT = "© 2018–2019 Mitglieder der Computer-AG, Katharineum zu Lübeck"
+
+VERSION = '1.1.4 "Aebli"'
+
+LICENSE_APACHE_2 = "Apache 2.0 License"
+LICENSE_BSD = "2-Clause BSD License"
+LICENSE_BSD_3 = "3-Clause BSD License"
+LICENSE_MIT = "MIT License"
+LICENSE_GPL_V2 = "GNU General Public License v2.0"
+LICENSE_GPL_V3 = "GNU General Public License v3.0"
+
+OPEN_SOURCE_COMPONENTS = [
+    # ("Docker (u.a. Engine, CLI, docker-compose)", "https://github.com/docker", LICENSE_APACHE_2,
+    #  "https://github.com/docker/docker/blob/master/LICENSE"),
+    ("Django", "https://www.djangoproject.com/", "Django BSD License",
+     "https://github.com/django/django/blob/master/LICENSE"),
+    ("Python 3", "https://www.python.org/", "PSF LICENSE AGREEMENT FOR PYTHON",
+     "https://docs.python.org/3/license.html"),
+    ("jQuery", "https://jquery.com/", LICENSE_MIT, "https://github.com/jquery/jquery/blob/master/LICENSE.txt"),
+    ("pip", "https://pypi.org/project/pip/", LICENSE_MIT, "https://github.com/pypa/pip/blob/master/LICENSE.txt"),
+    ("Requests", "https://requests.kennethreitz.org/", LICENSE_APACHE_2,
+     "https://github.com/psf/requests/blob/master/LICENSE"),
+    ("django-widget-tweaks", "https://github.com/jazzband/django-widget-tweaks", LICENSE_MIT,
+     "https://github.com/jazzband/django-widget-tweaks/blob/master/LICENSE"),
+    ("Materialize CSS", "https://materializecss.com/", LICENSE_MIT,
+     "https://github.com/Dogfalo/materialize/blob/master/LICENSE"),
+    ("Material Design Icons", "http://google.github.io/material-design-icons/", LICENSE_APACHE_2,
+     "https://github.com/google/material-design-icons/blob/master/LICENSE"),
+    ("highlight.js", "https://highlightjs.org/", LICENSE_BSD_3,
+     "https://github.com/highlightjs/highlight.js/blob/master/LICENSE"),
+    ("React", "https://reactjs.org/", LICENSE_MIT, "https://github.com/facebook/react/blob/master/LICENSE"),
+    ("mysqlclient", "https://github.com/PyMySQL/mysqlclient-python", LICENSE_GPL_V2,
+     "https://github.com/PyMySQL/mysqlclient-python/blob/master/LICENSE"),
+    ("django-auth-ldap", "https://github.com/django-auth-ldap/django-auth-ldap", LICENSE_BSD,
+     "https://github.com/django-auth-ldap/django-auth-ldap/blob/master/LICENSE"),
+    ("django-dbsettings", "https://github.com/zlorf/django-dbsettings", LICENSE_BSD_3,
+     "https://github.com/zlorf/django-dbsettings/blob/master/LICENSE"),
+    ("Django PDB", "https://github.com/HassenPy/django-pdb", "Public Domain", ""),
+    ("Django Material", "https://github.com/viewflow/django-material", LICENSE_BSD_3,
+     "https://github.com/viewflow/django-material/blob/master/LICENSE.txt"),
+    ("Django Filter", "https://github.com/carltongibson/django-filter", LICENSE_BSD_3,
+     "https://github.com/carltongibson/django-filter/blob/master/LICENSE"),
+    ("django-react-templatetags", "https://github.com/Frojd/django-react-templatetags", LICENSE_MIT,
+     "https://github.com/Frojd/django-react-templatetags/blob/develop/LICENSE"),
+    ("martor", "https://github.com/agusmakmun/django-markdown-editor", LICENSE_GPL_V3,
+     "https://github.com/agusmakmun/django-markdown-editor/blob/master/LICENSE"),
+    ("Babel", "https://babeljs.io/", LICENSE_MIT, "https://github.com/babel/babel/blob/master/LICENSE")
+]
+OPEN_SOURCE_COMPONENTS.sort(key=lambda elem: elem[0].lower())
+
+
+# Provide vars to all templates via processor
+def meta_processor(request):
+    return {'COPYRIGHT': COPYRIGHT, "COPYRIGHT_SHORT": COPYRIGHT_SHORT, "VERSION": VERSION}
diff --git a/biscuit/core/schoolapps/__init__.py b/biscuit/core/schoolapps/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/biscuit/core/schoolapps/example_secure_settings.py b/biscuit/core/schoolapps/example_secure_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ba684fa1ecf494f6fe558e673dbbb7e2be21545
--- /dev/null
+++ b/biscuit/core/schoolapps/example_secure_settings.py
@@ -0,0 +1,33 @@
+# EMAIL
+EMAIL_HOST = 'postoffice.katharineum.de'
+EMAIL_PORT = 587
+EMAIL_HOST_USER = 'infoplan@katharineum.de'
+EMAIL_HOST_PASSWORD = 'grummelPASS1531'
+EMAIL_USE_TLS = True
+
+# SECRET KEY
+SECRET_KEY = '_89lg!56$d^sf$22cz1ja_f)x9z(nc*y-x*@j4!!vzmlgi*53u'
+
+# DATABASES
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.mysql',
+        'NAME': 'schoolapps',
+        'USER': 'www-data',
+        'PASSWORD': 'grummelPASS1531',
+        'HOST': '',
+        'PORT': ''
+    },
+    'untis': {
+        'ENGINE': 'django.db.backends.mysql',
+        'NAME': 'Untis',
+        'USER': 'www-data',
+        'PASSWORD': 'grummelPASS1531',
+        'HOST': '',
+        'PORT': ''
+    }
+}
+
+# LDAP
+AUTH_LDAP_BIND_DN = "cn=django-agent,dc=example,dc=com"
+AUTH_LDAP_BIND_PASSWORD = "phlebotinum"
diff --git a/biscuit/core/schoolapps/settings.py b/biscuit/core/schoolapps/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..36fd54aa760b3c7a73a72754b972a28b659b2241
--- /dev/null
+++ b/biscuit/core/schoolapps/settings.py
@@ -0,0 +1,246 @@
+"""
+Django settings for schoolapps project.
+"""
+
+import os
+import ldap
+from django_auth_ldap.config import LDAPSearch, PosixGroupType, GroupOfNamesType, LDAPGroupType
+import logging
+from .secure_settings import *
+
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+DEBUG = True
+
+# PDB debugger option
+POST_MORTEM = True
+
+ALLOWED_HOSTS = [
+    'info.katharineum.de',
+    '178.63.239.184',
+    '159.69.181.50',
+    'localhost',
+    '127.0.0.1',
+    '13049d63.ngrok.io'
+]
+
+INTERNAL_IPS = [
+    '127.0.0.1',
+]
+
+# Application definition
+
+INSTALLED_APPS = [
+    'dashboard.apps.DashboardConfig',
+    "debug.apps.DebugConfig",
+    'aub.apps.AubConfig',
+    'fibu.apps.FibuConfig',
+    'untisconnect.apps.UntisconnectConfig',
+    'timetable.apps.TimetableConfig',
+    'menu.apps.MenuConfig',
+    'support.apps.SupportConfig',
+    'faq.apps.FaqConfig',
+    'dbsettings',
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'material',
+    'django_react_templatetags',
+    'martor',
+    'widget_tweaks',
+    'pwa',
+    'templatetags.apps.TemplatetagsConfig',
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'schoolapps.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [
+            os.path.join(BASE_DIR, 'templates')
+        ],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+                'django_react_templatetags.context_processors.react_context_processor',
+                'meta.meta_processor',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'schoolapps.wsgi.application'
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+# Internationalization
+
+LANGUAGE_CODE = 'de-de'
+TIME_ZONE = 'Europe/Berlin'
+USE_I18N = True
+USE_L10N = True
+USE_TZ = True
+
+# Static files (CSS, JavaScript, Images)
+
+STATIC_URL = '/static/'
+STATICFILES_DIRS = [
+    os.path.join(BASE_DIR, 'static')
+]
+STATIC_ROOT = os.path.join(BASE_DIR, 'staticcollect')
+
+# Redirect to home URL after login (Default redirects to /accounts/profile/)
+LOGIN_REDIRECT_URL = '/'
+
+# EMAIL
+EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+
+# TIMETABLE
+TIMETABLE_WIDTH = 5
+TIMETABLE_HEIGHT = 9
+LESSONS = [('8:00', '1.'), ('8:45', '2.'), ('9:45', '3.'), ('10:35', '4.'), ('11:35', '5.'),
+           ('12:25', '6.'), ('13:15', '7.'), ('14:05', '8.'), ('14:50', '9.')]
+SHORT_WEEK_DAYS = ["Mo", "Di", "Mi", "Do", "Fr"]
+LONG_WEEK_DAYS = [("Montag", 0), ("Dienstag", 1), ("Mittwoch", 2), ("Donnerstag", 3), ("Freitag", 4)]
+
+# LDAP
+
+# Baseline configuration.
+AUTH_LDAP_SERVER_URI = "ldap://127.0.0.1"
+AUTH_LDAP_USER_SEARCH = LDAPSearch("dc=skole,dc=skolelinux,dc=no",
+                                   ldap.SCOPE_SUBTREE, "(&(objectClass=posixAccount)(uid=%(user)s))")
+AUTH_LDAP_GROUP_SEARCH = LDAPSearch("dc=skole,dc=skolelinux,dc=no", ldap.SCOPE_SUBTREE,
+                                    "(&(objectClass=posixGroup))")
+AUTH_LDAP_GROUP_TYPE = PosixGroupType()
+AUTH_LDAP_USER_ATTR_MAP = {
+    "first_name": "givenName",
+    "last_name": "sn",
+    "email": "mail"
+}
+AUTH_LDAP_USER_FLAGS_BY_GROUP = {
+    "is_staff": "cn=schoolapps-admins,ou=group,dc=skole,dc=skolelinux,dc=no",
+    "is_superuser": "cn=schoolapps-admins,ou=group,dc=skole,dc=skolelinux,dc=no",
+}
+
+AUTH_LDAP_ALWAYS_UPDATE_USER = True
+AUTH_LDAP_MIRROR_GROUPS = True
+AUTH_LDAP_CACHE_GROUPS = True
+AUTH_LDAP_GROUP_CACHE_TIMEOUT = 300
+
+# Keep ModelBackend around for per-user permissions and maybe a local superuser.
+AUTHENTICATION_BACKENDS = (
+    'django_auth_ldap.backend.LDAPBackend',
+    'django.contrib.auth.backends.ModelBackend',
+)
+
+if DEBUG:
+    logger = logging.getLogger('django_auth_ldap')
+    logger.addHandler(logging.StreamHandler())
+    logger.setLevel(logging.DEBUG)
+
+# Media
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+
+# Use cache for db settings (only on production)
+DBSETTINGS_USE_CACHE = not DEBUG
+
+# Cache configs (only on production)
+TEST_MEMCACHE = False
+if not DEBUG or TEST_MEMCACHE:
+    CACHES = {
+        'default': {
+            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+            'LOCATION': '127.0.0.1:11211',
+        }
+    }
+else:
+    CACHES = {
+        'default': {
+            'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
+        }
+    }
+
+# PWA
+PWA_APP_NAME = 'SchoolApps'
+PWA_APP_DESCRIPTION = "Eine Sammlung an nützlichen Apps für den Schulalltag am Katharineum zu Lübeck"
+PWA_APP_THEME_COLOR = '#da1f3d'
+PWA_APP_BACKGROUND_COLOR = '#ffffff'
+PWA_APP_DISPLAY = 'standalone'
+PWA_APP_SCOPE = '/'
+PWA_APP_ORIENTATION = 'any'
+PWA_APP_START_URL = '/'
+PWA_APP_ICONS = [
+    {
+        "src": "/static/icons/android_192.png",
+        "sizes": "192x192",
+        "type": "image/png"
+    },
+    {
+        "src": "/static/icons/android_512.png",
+        "sizes": "512x512",
+        "type": "image/png"
+    }
+]
+PWA_APP_SPLASH_SCREEN = [
+    {
+        'src': '/static/icons/android_512.png',
+        'media': '(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)'
+    }
+]
+PWA_APP_DIR = 'ltr'
+PWA_SERVICE_WORKER_PATH = os.path.join(BASE_DIR, 'static/common', 'serviceworker.js')
+PWA_APP_LANG = 'de-DE'
+
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'handlers': {
+        'console': {
+            'level': 'INFO',
+            'class': 'logging.StreamHandler',
+        },
+        'file': {
+            'level': 'DEBUG',
+            'class': 'logging.FileHandler',
+            'filename': 'log.django',
+        },
+    },
+    'loggers': {
+        'django': {
+            'handlers': ['console', 'file'],
+            'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),
+        },
+    },
+}
diff --git a/biscuit/core/schoolapps/urls.py b/biscuit/core/schoolapps/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba42b84d22a00a0aefe57eb87e08ac9daf6fba37
--- /dev/null
+++ b/biscuit/core/schoolapps/urls.py
@@ -0,0 +1,91 @@
+"""schoolapps URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/2.0/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+
+from django.conf import settings
+from django.conf.urls import include
+from django.conf.urls.static import static
+from django.contrib import admin
+from django.shortcuts import render
+from django.urls import path
+
+
+def custom_page_not_found(request, exception):
+    print(exception)
+    return render(request, 'common/404.html', context={"martor": False})
+
+
+handler404 = custom_page_not_found
+
+urlpatterns = [
+    #############
+    # Dashboard #
+    #############
+    path('', include('dashboard.urls')),
+
+    ########
+    # Auth #
+    ########
+    path('accounts/', include('django.contrib.auth.urls')),
+
+    #######
+    # AUB #
+    #######
+    path('aub/', include('aub.urls')),
+
+    ########
+    # FIBU #
+    ########
+    path('fibu/', include('fibu.urls')),
+
+    #############
+    # TIMETABLE #
+    #############
+    path('timetable/', include('timetable.urls')),
+
+    ########
+    # MENU #
+    ########
+    path('menu/', include('menu.urls')),
+
+    #########
+    # Admin #
+    #########
+    path("debug/", include("debug.urls")),
+    path('settings/', include('dbsettings.urls')),
+    path('admin/', admin.site.urls),
+
+    ###########
+    # SUPPORT #
+    ###########
+    path('support/', include('support.urls')),
+
+    #######
+    # FAQ #
+    #######
+    path('faq/', include('faq.urls')),
+
+    path('', include('pwa.urls')),
+
+    path('martor/', include('martor.urls')),
+
+    #######
+    # 404 #
+    #######
+    path('404/', custom_page_not_found, name='404'),
+]
+
+urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/biscuit/core/schoolapps/wsgi.py b/biscuit/core/schoolapps/wsgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..15033c6106e4b6ff7de5b3809479bb0c62c3b1ad
--- /dev/null
+++ b/biscuit/core/schoolapps/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for schoolapps project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "schoolapps.settings")
+
+application = get_wsgi_application()
diff --git a/biscuit/core/settings.py b/biscuit/core/settings.py
index 8b794de16034827f6e05f60c2cb827050b2bd2a0..ba183e461853eba7e767992ace1efc1c67d94b1d 100644
--- a/biscuit/core/settings.py
+++ b/biscuit/core/settings.py
@@ -230,7 +230,7 @@ STATIC_ROOT = _settings.get("static.root", os.path.join(BASE_DIR, "static"))
 MEDIA_ROOT = _settings.get("media.root", os.path.join(BASE_DIR, "media"))
 NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "node_modules"))
 
-YARN_INSTALLED_APPS = ["jquery", "popper.js", "datatables", "select2", "materialize-css", "material-design-icons-iconfont"]
+YARN_INSTALLED_APPS = ["datatables", "highlight.js", "jquery", "manup", "materialize-css", "moment", "popper.js", "prop-types", "react", "react-dom", "material-design-icons-iconfont", "select2"]
 
 JS_URL = _settings.get("js_assets.url", STATIC_URL)
 JS_ROOT = _settings.get("js_assets.root", NODE_MODULES_ROOT + "/node_modules")
diff --git a/biscuit/core/static/common/favicon.ico b/biscuit/core/static/common/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..1405a12165083ca70bcdfffeacc527f5986a3785
Binary files /dev/null and b/biscuit/core/static/common/favicon.ico differ
diff --git a/biscuit/core/static/common/helper.js b/biscuit/core/static/common/helper.js
new file mode 100644
index 0000000000000000000000000000000000000000..2b6837838df14525bf203a3e5b2d4ed2bfd76cb2
--- /dev/null
+++ b/biscuit/core/static/common/helper.js
@@ -0,0 +1,112 @@
+function formatDate(date) {
+    return date.getDate() + "." + (date.getMonth() + 1) + "." + date.getFullYear();
+}
+
+
+function addZeros(i) {
+    if (i < 10) {
+        return "0" + i;
+    } else {
+        return "" + i;
+    }
+}
+
+function formatDateForDjango(date) {
+    return "" + date.getFullYear() + "/" + addZeros(date.getMonth() + 1) + "/" + addZeros(date.getDate()) + "/";
+
+}
+
+function getNow() {
+    return new Date();
+}
+
+function getNowFormatted() {
+    return formatDate(getNow());
+}
+
+
+function selectActiveLink() {
+    var currlocation = $('meta[name="active-loaction"]');
+    var url_name = currlocation.attr("content");
+    //console.log(url_name);
+
+    var selector = ".url-" + url_name;
+    console.log(selector);
+    $(selector).addClass("active");
+    $(selector).parent().parent().parent().addClass("active");
+}
+
+$(document).ready(function () {
+    selectActiveLink();
+
+    $("dmc-datetime input").addClass("datepicker");
+    $("[data-form-control='date']").addClass("datepicker");
+    $("[data-form-control='time']").addClass("timepicker");
+
+    // Initialize sidenav [MAT]
+    $(".sidenav").sidenav();
+
+    // Initialize datepicker [MAT]
+    $('.datepicker').datepicker({
+        format: 'dd.mm.yyyy',
+        // Translate to German
+        i18n: {
+            months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+            monthsShort: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
+            weekdays: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+            weekdaysShort: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
+            weekdaysAbbrev: ['S', 'M', 'D', 'M', 'D', 'F', 'S'],
+
+            // Buttons
+            today: 'Heute',
+            cancel: 'Abbrechen',
+            done: 'OK',
+        },
+
+        // Set monday as first day of week
+        firstDay: 1,
+        autoClose: true
+    });
+
+    // Initialize timepicker [MAT]
+    $('.timepicker').timepicker({
+        twelveHour: false,
+        autoClose: true,
+        i18n: {
+            cancel: 'Abbrechen',
+            clear: 'Löschen',
+            done: 'OK'
+        },
+    });
+
+    // Initialize tooltip [MAT]
+    $('.tooltipped').tooltip();
+
+    // Initialize select [MAT]
+    $('select').formSelect();
+
+    // Initalize print button
+    $("#print").click(function () {
+        window.print();
+    });
+
+    // Initialize Collapsible [MAT]
+    $('.collapsible').collapsible();
+
+    // Initialize FABs [MAT]
+    $('.fixed-action-btn').floatingActionButton();
+
+    // Initialize Modals [MAT]
+    $('.modal').modal();
+
+    // Initialize delete button
+    $(".delete-button").click(function (e) {
+        if (!confirm("Wirklich löschen?")) {
+            e.preventDefault();
+        }
+    });
+
+    if (typeof onFinish !== 'undefined') {
+        onFinish();
+    }
+});
diff --git a/biscuit/core/static/common/logo.png b/biscuit/core/static/common/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..005e0b6c64a3475a0ccaa875caaf7673c14b8fd2
Binary files /dev/null and b/biscuit/core/static/common/logo.png differ
diff --git a/biscuit/core/static/common/serviceworker.js b/biscuit/core/static/common/serviceworker.js
new file mode 100644
index 0000000000000000000000000000000000000000..76a52613b4a8a08e45d06b1fdcc54a74e47f5913
--- /dev/null
+++ b/biscuit/core/static/common/serviceworker.js
@@ -0,0 +1,115 @@
+//This is the SchoolApps service worker
+
+const CACHE = "schoolapps-cache";
+
+const precacheFiles = [
+    '',
+    '/faq/',
+];
+
+const offlineFallbackPage = '/offline';
+
+const avoidCachingPaths = [
+    '/admin',
+    '/settings',
+    '/support',
+    '/tools',
+    '/faq/ask',
+    '/aub/apply_for',
+    '/aub/check1',
+    '/aub/check2',
+    '/aktuell.pdf',
+    '/accounts/login',
+    '/timetable/aktuell.pdf',
+    '/api',
+];
+
+function pathComparer(requestUrl, pathRegEx) {
+  return requestUrl.match(new RegExp(pathRegEx));
+}
+
+function comparePaths(requestUrl, pathsArray) {
+  if (requestUrl) {
+    for (let index = 0; index < pathsArray.length; index++) {
+      const pathRegEx = pathsArray[index];
+      if (pathComparer(requestUrl, pathRegEx)) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+self.addEventListener("install", function (event) {
+  console.log("[SchoolApps PWA] Install Event processing.");
+
+  console.log("[SchoolApps PWA] Skipping waiting on install.");
+  self.skipWaiting();
+
+  event.waitUntil(
+    caches.open(CACHE).then(function (cache) {
+      console.log("[SchoolApps PWA] Caching pages during install.");
+
+      return cache.addAll(precacheFiles).then(function () {
+        return cache.add(offlineFallbackPage);
+      });
+    })
+  );
+});
+
+// Allow sw to control of current page
+self.addEventListener("activate", function (event) {
+  console.log("[SchoolApps PWA] Claiming clients for current page.");
+  event.waitUntil(self.clients.claim());
+});
+
+// If any fetch fails, it will look for the request in the cache and serve it from there first
+self.addEventListener("fetch", function (event) {
+  if (event.request.method !== "GET") return;
+  networkFirstFetch(event);
+});
+
+function networkFirstFetch(event) {
+  event.respondWith(
+    fetch(event.request)
+      .then(function (response) {
+        // If request was successful, add or update it in the cache
+        console.log("[SchoolApps PWA] Network request successful.");
+        event.waitUntil(updateCache(event.request, response.clone()));
+        return response;
+      })
+      .catch(function (error) {
+        console.log("[SchoolApps PWA] Network request failed. Serving content from cache: " + error);
+        return fromCache(event);
+      })
+  );
+}
+
+function fromCache(event) {
+  // Check to see if you have it in the cache
+  // Return response
+  // If not in the cache, then return offline fallback page
+  return caches.open(CACHE).then(function (cache) {
+    return cache.match(event.request)
+    .then(function (matching) {
+      if (!matching || matching.status === 404) {
+        console.log("[SchoolApps PWA] Cache request failed. Serving offline fallback page.");
+        // Use the precached offline page as fallback
+        return caches.match(offlineFallbackPage)
+      }
+
+      return matching;
+    });
+  });
+}
+
+function updateCache(request, response) {
+  if (!comparePaths(request.url, avoidCachingPaths)) {
+    return caches.open(CACHE).then(function (cache) {
+      return cache.put(request, response);
+    });
+  }
+
+  return Promise.resolve();
+}
diff --git a/biscuit/core/static/common/style.css b/biscuit/core/static/common/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..780e9fec90ff9d4abcaf22a9482c247d9ed97b49
--- /dev/null
+++ b/biscuit/core/static/common/style.css
@@ -0,0 +1,673 @@
+/**********/
+/* COMMON */
+/**********/
+
+body {
+    display: flex;
+    min-height: 100vh;
+    flex-direction: column;
+}
+
+.primary-color {
+    background-color: #da1f3d !important;
+}
+
+.primary-color-text, .primary-color-text a {
+    color: #da1f3d !important;
+}
+
+/**********/
+/* HEADER */
+/**********/
+
+.brand-logo {
+    margin-left: 10px;
+}
+
+/********/
+/* MAIN */
+/********/
+
+main {
+    padding: 10px 20px;
+    flex: 1 0 auto;
+}
+
+/***********/
+/* SIDENAV */
+/***********/
+
+ul.sidenav.sidenav-fixed li.logo {
+    margin-top: 32px;
+    margin-bottom: 50px;
+}
+
+ul.sidenav.sidenav-fixed .brand-logo {
+    margin: 0;
+}
+
+.logo img {
+    width: 250px;
+}
+
+header a.sidenav-trigger {
+    position: absolute;
+    left: 7.5%;
+    top: 0;
+
+    height: 64px;
+    font-size: 38px;
+
+    float: none;
+
+    text-align: center;
+    color: white;
+
+    z-index: 2;
+}
+
+
+@media only screen and (max-width: 993px) {
+    header div.nav-wrapper {
+        z-index: -5;
+    }
+}
+
+header, main, footer {
+    margin-left: 300px;
+}
+
+
+.footer-icon {
+    font-size: 22px !important;
+    vertical-align: middle;
+}
+
+
+@media only screen and (min-width: 1384px) {
+    .footer-row-large {
+        display: flex;
+        align-items: center;
+    }
+
+    .footer-row-small {
+        display: none;
+    }
+}
+
+@media only screen and (max-width: 1383px) {
+    .footer-row-large {
+        display: none;
+    }
+
+    .footer-row-small {
+        display: block;
+    }
+}
+
+ul.footer-ul {
+    display: inline-block;
+    text-align: right;
+    float: right;
+}
+
+.make-it-higher {
+    vertical-align: middle;
+    line-height: 36px;
+}
+
+@media only screen and (max-width: 992px) {
+    header, main, footer {
+        margin-left: 0;
+    }
+}
+
+/* Collections */
+
+ul.collection .collection-item .title {
+    font-weight: bold;
+}
+
+.section {
+    padding: 0;
+}
+
+form .row {
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+/* Badges */
+
+span.badge.new::after {
+    content: "";
+}
+
+span.badge.new {
+    font-size: 1rem;
+    line-height: 26px;
+    height: 26px;
+}
+
+span.badge.new.no-float {
+    float: none;
+    padding: 3px 6px;
+}
+
+span.badge .material-icons {
+    font-size: 0.9rem;
+}
+
+/*+++++++++++*/
+/* Timetable */
+/*+++++++++++*/
+.smart-plan-badge {
+    margin: 5px 20px 5px 0;
+}
+
+li.active > a > .sidenav-badge {
+    background-color: whitesmoke !important;
+    color: #DA3D56 !important;
+}
+
+.timetable-plan .row, .timetable-plan .col {
+    display: flex;
+    padding: 0 .25rem;
+}
+
+.timetable-plan .row {
+    margin-bottom: .25rem;
+}
+
+.lesson-card, .timetable-title-card {
+    margin: 0;
+    display: flex;
+    flex-grow: 1;
+    min-height: 65px;
+}
+
+.lesson-card .card-content {
+    padding: 0;
+    text-align: center;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+}
+
+.lesson-card .card-content div {
+    padding: 3px;
+    flex: auto;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.timetable-title-card .card-content {
+    padding: 10px;
+    text-align: center;
+    width: 100%;
+}
+
+.timetable-mobile-title-card {
+    margin-top: 50px;
+    margin-bottom: .60rem;
+}
+
+.timetable-mobile-title-card:first-child {
+    margin-top: -10px;
+    margin-bottom: .60rem;
+}
+
+.timetable-mobile-title-card .card-content {
+    padding: 10px;
+    text-align: center;
+    width: 100%;
+}
+
+.timetable-mobile-title-card .card-content .card-title {
+    font-weight: bold;
+}
+
+table.substitutions td, table.substitutions th {
+    padding: 10px 5px;
+}
+
+.lesson-with-sub {
+    border: 3px solid red;
+    border-radius: 3px;
+}
+
+.lesson-with-sub .badge {
+    margin: 0;
+}
+
+.lesson-with-event {
+    border: 3px solid #9c27b0;
+    border-radius: 3px;
+}
+
+.lesson-card a, .substitutions a {
+    color: inherit;
+}
+
+/*.timetable-time {*/
+/*margin-right: 20px;*/
+/*}*/
+
+/*+++++++++*/
+/* Buttons */
+/*+++++++++*/
+
+.btn-flat-large {
+    line-height: 60px;
+    height: 60px;
+}
+
+.btn-flat-large i {
+    font-size: 4rem;
+}
+
+.btn-flat-medium {
+    line-height: 40px;
+    height: 40px;
+}
+
+.btn-flat-medium i {
+    font-size: 2rem;
+}
+
+.btn-timetable-quicklaunch {
+    margin: 1%;
+    width: 30%;
+    background-color: rgba(0, 0, 0, 0.05) !important;
+    color: black;
+}
+
+.btn-timetable-quicklaunch:hover {
+    background-color: #da1f3d !important;
+    color: whitesmoke;
+}
+
+.no-margin {
+    margin: 0 !important;
+}
+
+.valign-middle {
+    vertical-align: middle;
+}
+
+.valign-top {
+    vertical-align: top;
+}
+
+.valign-bot {
+    vertical-align: bottom;
+}
+
+.height-inherit {
+    height: 100%;
+}
+
+/* Table*/
+
+table.striped > tbody > tr:nth-child(odd), table tr.striped {
+    background-color: rgba(208, 208, 208, 0.5);
+}
+
+
+/*+++++++*/
+/* Print */
+/*+++++++*/
+#print-header {
+    display: none;
+}
+
+.print-icon {
+    margin-top: 1.52rem;
+}
+
+@media print {
+    body {
+        font-size: 15px;
+    }
+
+    header, main, footer {
+        margin-left: 0;
+    }
+
+    ul.sidenav {
+        display: none !important;
+        transform: translateX(-105%) !important;
+    }
+
+    nav {
+        display: none;
+    }
+
+    .sidenav-trigger {
+        display: none;
+    }
+
+    #print-header {
+        display: block;
+        border-bottom: 1px solid;
+        margin-bottom: 0;
+    }
+
+    #print-header .col.right-align {
+        padding: 15px;
+    }
+
+    main, header {
+        padding: 0;
+    }
+
+    footer, footer .footer-copyright, footer .container {
+        background-color: white !important;
+        color: black !important;
+    }
+
+    footer a {
+        display: none;
+    }
+
+    .footer-copyright, .footer-copyright .container {
+        padding: 0 !important;
+        margin: 0 !important;
+    }
+
+    .no-print {
+        display: none;
+    }
+}
+
+.alert ul, .alert p {
+    margin: 0;
+}
+
+.alert > p, .alert > div {
+    margin: 10px;
+    padding: 10px;
+    border-left: 5px solid;
+}
+
+.alert.success > p, .alert.success > div {
+    background-color: #c5e1a5;
+    border-color: #4caf50;
+}
+
+.alert.error > p, .alert.error > div {
+    background-color: #ef9a9a;
+    border-color: #b71c1c;
+}
+
+.alert.primary > p, .alert.primary > div, .alert.info > p, .alert.info > div {
+    background-color: #ececec;
+    border-color: #da1f3d;
+}
+
+.alert.warning p, .alert.warning div {
+    background-color: #ffb74d;
+    border-color: #e65100;
+}
+
+main .alert p:first-child, main .alert div:first-child {
+    margin-left: -10px;
+    margin-right: -10px;
+}
+
+.btn, .btn-large, .btn-small {
+    background-color: #0f9d58;
+}
+
+.btn:hover, .btn-large:hover, .btn-small {
+    background-color: #DA1F3D;
+}
+
+/*++++++++++++++++
+FEEDBACK
+++++++++++++++++*/
+
+.rating {
+    display: flex;
+    flex-direction: row-reverse;
+    flex-wrap: wrap;
+    width: 100%;
+    min-width: 150px;
+    max-width: 600px;
+    justify-content: flex-end;
+}
+
+@media only screen and (max-width: 992px) {
+    .rating {
+        margin-top: 12px;
+        min-height: 40px;
+        justify-content: space-around;
+    }
+
+    ´
+}
+
+.rating label {
+    display: flex;
+    flex: 1;
+    position: relative;
+    cursor: pointer;
+}
+
+.rating label:after {
+    font-family: 'Material Icons';
+    -webkit-font-feature-settings: 'liga';
+    position: absolute;
+    color: #777;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    text-align: center;
+    font-size: 40px;
+    content: 'star_border';
+}
+
+.rating input {
+    display: none !important;
+}
+
+.rating > input:checked + label:after,
+.rating > input:checked ~ label:after {
+    content: "star";
+    color: #FFD700;
+}
+
+.rating > input:hover + label:after,
+.rating > input:hover ~ label:after {
+    content: "star";
+    color: #FFD700;
+}
+
+.support-input-mobile label {
+    line-height: 1rem;
+}
+
+.span-info {
+    width: 24px;
+    vertical-align: middle;
+}
+
+i.collapsible-trigger {
+    margin-right: -10px;
+
+}
+
+.collapsible .collapsible-trigger::before {
+    content: 'arrow_downward';
+}
+
+.collapsible .collapsible-trigger.v2::before {
+    content: 'more_vert';
+}
+
+.collapsible .active .collapsible-trigger::before {
+    content: 'arrow_upward';
+}
+
+
+.scroll-fix {
+    max-height: 300px;
+    overflow: scroll;
+}
+
+.waves-effect.waves-katharineum .waves-ripple {
+    background-color: rgba(218, 31, 61, 0.65);
+}
+
+.no-margin {
+    margin: 0 !important;
+}
+
+.no-padding {
+    padding: 0 !important;
+}
+
+.no-pad-left {
+    padding-left: 0 !important;
+}
+
+.no-pad-right {
+    padding-right: 0 !important;
+}
+
+.sidenav a:not(.collapsible-header) {
+    padding: 0 16px;
+}
+
+ul.sidenav li.logo > a:hover {
+    background: none !important;
+}
+
+.waves-effect.waves-primary .waves-ripple {
+    /* The alpha value allows the text and background color
+    of the button to still show through. */
+    background-color: #da1f3d;
+}
+
+.sidenav .collapsible-body > ul:not(.collapsible) > li.active a > i, .sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active a > i {
+    color: #fff;
+}
+
+.sidenav .collapsible-body > ul:not(.collapsible) > li.active, .sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active {
+    background-color: #DA3D56;
+}
+
+.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating) {
+    color: #da1f3d;
+}
+
+.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover {
+    color: #ea4661;
+}
+
+/*section:not(:last-of-type) {*/
+/*    border-bottom: solid #bdbdbd 2px;*/
+/*}*/
+
+/*++++++++
++HOLIDAYS+
+++++++++++ */
+
+.holiday-badge {
+    float: left !important;
+    position: relative;
+    margin-left: 0% !important;
+    left: 50%;
+    transform: translate(-50%);
+    width: auto;
+    height: auto !important;
+    min-height: 26px;
+}
+
+/* Dashboard */
+
+.card-action-badge {
+    float: left !important;
+    margin-left: 0 !important;
+    margin-top: -3px;
+    margin-right: 10px;
+}
+
+.event-card {
+    padding: 10px;
+}
+
+.event-card .title {
+    font-size: 18px;
+    font-weight: 500;
+}
+
+
+.flex-row {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    justify-content: space-between;
+}
+
+.hundred-percent {
+    width: 100%;
+}
+
+.badge-image {
+    position: absolute;
+    left: 0;
+    top: 10px;
+    z-index: 1;
+    background-color: #da1f3d;
+    color: white;
+    padding: 2px 10px;
+    border-radius: 0 3px 3px 0;
+    text-transform: uppercase;
+    font-weight: 300;
+}
+
+.center-via-flex {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+}
+
+.center2-via-flex {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+.spinner-primary {
+    border-color: #da1f3d;
+}
+
+.dashboard-cards {
+    -webkit-column-break-inside: avoid;
+    column-count: 3;
+}
+
+@media (min-width: 800px) and (max-width: 1460px) {
+    .dashboard-cards {
+        column-count: 2;
+    }
+}
+
+@media (max-width: 800px) {
+    .dashboard-cards {
+        column-count: 1;
+    }
+}
+
+
+.dashboard-cards .card {
+    display: inline-block;
+    overflow: visible;
+    width: 100%;
+}
diff --git a/biscuit/core/static/icons/android_192.png b/biscuit/core/static/icons/android_192.png
new file mode 100644
index 0000000000000000000000000000000000000000..3de63ed87e9a3c6389b8cbf686922d5d2b1d38ec
Binary files /dev/null and b/biscuit/core/static/icons/android_192.png differ
diff --git a/biscuit/core/static/icons/android_512.png b/biscuit/core/static/icons/android_512.png
new file mode 100644
index 0000000000000000000000000000000000000000..3de63ed87e9a3c6389b8cbf686922d5d2b1d38ec
Binary files /dev/null and b/biscuit/core/static/icons/android_512.png differ
diff --git a/biscuit/core/static/icons/apple_114.png b/biscuit/core/static/icons/apple_114.png
new file mode 100644
index 0000000000000000000000000000000000000000..859930c3204e69df15c42d5437786ace0c921310
Binary files /dev/null and b/biscuit/core/static/icons/apple_114.png differ
diff --git a/biscuit/core/static/icons/apple_152.png b/biscuit/core/static/icons/apple_152.png
new file mode 100644
index 0000000000000000000000000000000000000000..d554e0ddb5e53d6f67b7e411c82388e1222cae0d
Binary files /dev/null and b/biscuit/core/static/icons/apple_152.png differ
diff --git a/biscuit/core/static/icons/apple_180.png b/biscuit/core/static/icons/apple_180.png
new file mode 100644
index 0000000000000000000000000000000000000000..69dacfa6d08225ea888812f578cd1b8e9df21789
Binary files /dev/null and b/biscuit/core/static/icons/apple_180.png differ
diff --git a/biscuit/core/static/icons/apple_76.png b/biscuit/core/static/icons/apple_76.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f89e66cac53188c090f030022cf5047a5ca3ea6
Binary files /dev/null and b/biscuit/core/static/icons/apple_76.png differ
diff --git a/biscuit/core/static/icons/favicon_16.png b/biscuit/core/static/icons/favicon_16.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6a2906384295d750a2b2f66a7a967bacd5a1159
Binary files /dev/null and b/biscuit/core/static/icons/favicon_16.png differ
diff --git a/biscuit/core/static/icons/favicon_32.png b/biscuit/core/static/icons/favicon_32.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b3dbb819c40cfa9942139197d8b9d6ed320e562
Binary files /dev/null and b/biscuit/core/static/icons/favicon_32.png differ
diff --git a/biscuit/core/static/icons/favicon_48.png b/biscuit/core/static/icons/favicon_48.png
new file mode 100644
index 0000000000000000000000000000000000000000..06886725806fa482c887c3443686be0cfde828fe
Binary files /dev/null and b/biscuit/core/static/icons/favicon_48.png differ
diff --git a/biscuit/core/static/js/dashboard.js b/biscuit/core/static/js/dashboard.js
new file mode 100644
index 0000000000000000000000000000000000000000..1604cae664f8a80628172763611379c01a65bd85
--- /dev/null
+++ b/biscuit/core/static/js/dashboard.js
@@ -0,0 +1,601 @@
+/*** THIS FILE IS GENERATED from react/src/dashboard.js ***/
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var REFRESH_TIME = 15;
+
+// function WithCheckCircleIcon(props) {
+//     return <div className={"col s12"}>
+//         <i className={"material-icons left green-text"}>check_circle</i>
+//         {props.children}
+//     </div>
+// }
+
+var Dashboard = function (_React$Component) {
+    _inherits(Dashboard, _React$Component);
+
+    function Dashboard() {
+        _classCallCheck(this, Dashboard);
+
+        var _this = _possibleConstructorReturn(this, (Dashboard.__proto__ || Object.getPrototypeOf(Dashboard)).call(this));
+
+        _this.updateRefreshTime = function () {
+            if (_this.state.refreshIn >= 1) {
+                if (_this.state.timeout) {
+                    window.clearTimeout(_this.state.timeout);
+                }
+                var timeout = window.setTimeout(_this.updateRefreshTime, 1000);
+                _this.setState({ refreshIn: _this.state.refreshIn - 1, timeout: timeout });
+            } else {
+                _this.updateData();
+            }
+        };
+
+        _this.updateData = function () {
+            if (_this.state.networkProblems) {
+                _this.setState({isLoading: true, networkProblems: false});
+            }
+
+            var that = _this;
+            $.getJSON(API_URL, function (data) {
+                console.log(data);
+                if (data) {
+                    that.setState(Object.assign({}, data, {refreshIn: REFRESH_TIME + 1, isLoading: false}));
+                    that.updateRefreshTime();
+                }
+            }).fail(function () {
+                console.log("error");
+                that.setState({refreshIn: REFRESH_TIME + 1, networkProblems: true});
+            });
+            $.getJSON(API_URL + "/my-plan", function (data) {
+                console.log(data);
+                if (data && data.lessons) {
+                    that.setState({lessons: data.lessons, holiday: data.holiday});
+                }
+            }).fail(function () {
+                console.log("error");
+                that.setState({networkProblems: true});
+            });
+        };
+
+        _this.state = {
+            refreshIn: REFRESH_TIME,
+            isLoading: true,
+            networkProblems: false
+        };
+        return _this;
+    }
+
+    _createClass(Dashboard, [{
+        key: "componentDidMount",
+        value: function componentDidMount() {
+            console.log(API_URL);
+            this.updateData();
+        }
+    }, {
+        key: "closeNotification",
+        value: function closeNotification(notification) {
+            console.log(notification);
+            $("#not-" + notification.id).addClass("scale-out");
+            window.setTimeout(function () {
+                $("#not-" + notification.id).hide();
+            }, 200);
+            $.getJSON(API_URL + "/notifications/read/" + notification.id);
+            this.updateData();
+            this.setState({ time: new Date() });
+        }
+    }, {
+        key: "render",
+        value: function render() {
+            if (this.state.networkProblems) {
+                // Show loading screen until first data are loaded
+                return React.createElement(
+                    "div",
+                    {className: "row center-via-flex container", style: {"height": "20em"}},
+                    React.createElement(
+                        "i",
+                        {className: "material-icons large"},
+                        "signal_wifi_off"
+                    ),
+                    React.createElement(
+                        "p",
+                        {className: "flow-text text-center"},
+                        "Es ist ein Fehler bei der Netzwerkverbindung aufgetreten."
+                    ),
+                    React.createElement(
+                        "button",
+                        {className: "btn-flat grey-text", onClick: this.updateData},
+                        "Erneuter Versuch in ",
+                        this.state.refreshIn,
+                        " s"
+                    )
+                );
+            }
+
+            if (this.state.isLoading) {
+                // Show loading screen until first data are loaded
+                return React.createElement(
+                    "div",
+                    {className: "row center-via-flex container", style: {"height": "15em"}},
+                    React.createElement(
+                        "div",
+                        {className: "center2-via-flex"},
+                        React.createElement(
+                            "div",
+                            { className: "preloader-wrapper big active" },
+                            React.createElement(
+                                "div",
+                                { className: "spinner-layer spinner-primary" },
+                                React.createElement(
+                                    "div",
+                                    { className: "circle-clipper left" },
+                                    React.createElement("div", { className: "circle" })
+                                ),
+                                React.createElement(
+                                    "div",
+                                    { className: "gap-patch" },
+                                    React.createElement("div", { className: "circle" })
+                                ),
+                                React.createElement(
+                                    "div",
+                                    { className: "circle-clipper right" },
+                                    React.createElement("div", { className: "circle" })
+                                )
+                            )
+                        ),
+                        React.createElement(
+                            "p",
+                            { className: "text-center flow-text" },
+                            "Deine aktuellen Informationen werden geladen \u2026"
+                        )
+                    )
+                );
+            }
+
+            var that = this;
+            return React.createElement(
+                "div",
+                null,
+                React.createElement(
+                    "button",
+                    { className: "btn-flat right grey-text", onClick: this.updateData },
+                    React.createElement(
+                        "i",
+                        { className: "material-icons left" },
+                        "refresh"
+                    ),
+                    "in ",
+                    this.state.refreshIn,
+                    " s"
+                ),
+                React.createElement(
+                    "p",
+                    { className: "flow-text" },
+                    "Moin Moin, ",
+                    this.state.user.full_name !== "" ? this.state.user.full_name : this.state.user.username,
+                    ". Hier findest du alle aktuellen Informationen:"
+                ),
+                React.createElement(
+                    "div",
+                    { className: "alert success" },
+                    React.createElement(
+                        "p",
+                        null,
+                        React.createElement(
+                            "i",
+                            { className: "material-icons left" },
+                            "report_problem"
+                        ),
+                        "Das neue Dashboard von SchoolApps befindet sich momentan in der ",
+                        React.createElement(
+                            "strong",
+                            null,
+                            "Testphase"
+                        ),
+                        ". Falls Fehler auftreten oder du einen Verbesserungsvorschlag f\xFCr uns hast, schreibe uns bitte unter ",
+                        React.createElement(
+                            "a",
+                            {
+                                href: "mailto:support@katharineum.de" },
+                            "support@katharineum.de"
+                        ),
+                        "."
+                    )
+                ),
+                this.state.unread_notifications && this.state.unread_notifications.length > 0 ? this.state.unread_notifications.map(function (notification) {
+                    return React.createElement(
+                        "div",
+                        { className: "alert primary scale-transition", id: "not-" + notification.id,
+                            key: notification.id },
+                        React.createElement(
+                            "div",
+                            null,
+                            React.createElement(
+                                "i",
+                                { className: "material-icons left" },
+                                "info"
+                            ),
+                            React.createElement(
+                                "div",
+                                { className: "right" },
+                                React.createElement(
+                                    "button",
+                                    { className: "btn-flat", onClick: function onClick() {
+                                            return that.closeNotification(notification);
+                                        } },
+                                    React.createElement(
+                                        "i",
+                                        { className: "material-icons center" },
+                                        "close"
+                                    )
+                                )
+                            ),
+                            React.createElement(
+                                "strong",
+                                null,
+                                notification.title
+                            ),
+                            React.createElement(
+                                "p",
+                                null,
+                                notification.description
+                            )
+                        )
+                    );
+                }) : "",
+                this.state.plan && this.state.plan.hints.length > 0 ? React.createElement(
+                    "div",
+                    null,
+                    this.state.plan.hints.map(function (hint, idx) {
+                        return React.createElement(
+                            "div",
+                            { className: "alert primary", key: idx },
+                            React.createElement(
+                                "div",
+                                null,
+                                React.createElement(
+                                    "em",
+                                    { className: "right hide-on-small-and-down" },
+                                    "Hinweis f\xFCr ",
+                                    that.state.date_formatted
+                                ),
+                                React.createElement(
+                                    "i",
+                                    { className: "material-icons left" },
+                                    "announcement"
+                                ),
+                                React.createElement("p", { dangerouslySetInnerHTML: { __html: hint.html } }),
+                                React.createElement(
+                                    "em",
+                                    { className: "hide-on-med-and-up" },
+                                    "Hinweis f\xFCr ",
+                                    that.state.date_formatted
+                                )
+                            )
+                        );
+                    })
+                ) : "",
+                React.createElement(
+                    "div",
+                    { className: "row" },
+                    this.state.has_plan ? React.createElement(
+                        "div",
+                        {className: "col s12 m12 l6 xl4"},
+                        React.createElement(
+                            "div",
+                            {className: "card"},
+                            React.createElement(
+                                "div",
+                                {className: "card-content"},
+                                React.createElement(
+                                    "span",
+                                    {className: "card-title"},
+                                    "Plan ",
+                                    this.state.plan.type === 2 ? "der" : "für",
+                                    " ",
+                                    React.createElement(
+                                        "em",
+                                        null,
+                                        this.state.plan.name
+                                    ),
+                                    " f\xFCr ",
+                                    this.state.date_formatted
+                                ),
+                                this.state.holiday ? React.createElement(
+                                    "div",
+                                    { className: "card" },
+                                    React.createElement(
+                                        "div",
+                                        { className: "card-content" },
+                                        React.createElement(
+                                            "span",
+                                            {
+                                                className: "badge new blue center-align holiday-badge" },
+                                            this.state.holiday.name
+                                        ),
+                                        React.createElement("br", null)
+                                    )
+                                ) : this.state.lessons && this.state.lessons.length > 0 ? React.createElement(
+                                    "div",
+                                    { className: "timetable-plan" },
+                                    this.state.lessons.map(function (lesson) {
+                                        // Show one lesson row
+                                        return React.createElement(
+                                            "div",
+                                            { className: "row" },
+                                            React.createElement(
+                                                "div",
+                                                { className: "col s4" },
+                                                React.createElement(
+                                                    "div",
+                                                    { className: "card timetable-title-card" },
+                                                    React.createElement(
+                                                        "div",
+                                                        { className: "card-content" },
+                                                        React.createElement(
+                                                            "span",
+                                                            { className: "card-title left" },
+                                                            lesson.time.number_format
+                                                        ),
+                                                        React.createElement(
+                                                            "div",
+                                                            {
+                                                                className: "right timetable-time grey-text text-darken-2" },
+                                                            React.createElement(
+                                                                "span",
+                                                                null,
+                                                                lesson.time.start_format
+                                                            ),
+                                                            React.createElement("br", null),
+                                                            React.createElement(
+                                                                "span",
+                                                                null,
+                                                                lesson.time.end_format
+                                                            )
+                                                        )
+                                                    )
+                                                )
+                                            ),
+                                            React.createElement("div", { className: "col s8",
+                                                dangerouslySetInnerHTML: { __html: lesson.html } })
+                                        );
+                                    })
+                                ) : ""
+                            ),
+                            React.createElement(
+                                "div",
+                                { className: "card-action" },
+                                React.createElement(
+                                    "a",
+                                    { href: MY_PLAN_URL },
+                                    React.createElement(
+                                        "span",
+                                        { className: "badge new primary-color card-action-badge" },
+                                        "SMART PLAN"
+                                    ),
+                                    "anzeigen"
+                                )
+                            )
+                        )
+                    ) : "",
+                    this.state.current_events && this.state.current_events.length > 0 ? React.createElement(
+                        "div",
+                        {className: "col s12 m12 l6 xl4"},
+                        React.createElement(
+                            "div",
+                            {className: "card "},
+                            React.createElement(
+                                "div",
+                                {className: "card-content"},
+                                React.createElement(
+                                    "span",
+                                    {className: "card-title"},
+                                    "Aktuelle Termine"
+                                ),
+                                this.state.current_events.map(function (event) {
+                                    return React.createElement(
+                                        "div",
+                                        { className: "card-panel event-card" },
+                                        React.createElement(
+                                            "span",
+                                            { className: "title" },
+                                            event.name
+                                        ),
+                                        React.createElement("br", null),
+                                        event.formatted
+                                    );
+                                })
+                            ),
+                            React.createElement(
+                                "div",
+                                { className: "card-action" },
+                                React.createElement(
+                                    "a",
+                                    { href: "https://katharineum-zu-luebeck.de/aktuelles/termine/", target: "_blank" },
+                                    "Weitere Termine"
+                                )
+                            )
+                        )
+                    ) : "",
+                    this.state.newest_article ? React.createElement(
+                        "div",
+                        {className: "col s12 m12 l6 xl4"},
+                        React.createElement(
+                            "div",
+                            {className: "card"},
+                            React.createElement(
+                                "div",
+                                {className: "card-image"},
+                                React.createElement(
+                                    "span",
+                                    {className: "badge-image z-depth-2"},
+                                    "Aktuelles von der Homepage"
+                                ),
+                                React.createElement("img", {
+                                    src: this.state.newest_article.image_url,
+                                    alt: this.state.newest_article.title
+                                })
+                            ),
+                            React.createElement(
+                                "div",
+                                {className: "card-content"},
+                                React.createElement("span", {
+                                    className: "card-title",
+                                    dangerouslySetInnerHTML: {__html: this.state.newest_article.title}
+                                }),
+                                React.createElement("p", {dangerouslySetInnerHTML: {__html: this.state.newest_article.short_text}})
+                            ),
+                            React.createElement(
+                                "div",
+                                {className: "card-action"},
+                                React.createElement(
+                                    "a",
+                                    {href: this.state.newest_article.link, target: "_blank"},
+                                    "Mehr lesen"
+                                )
+                            )
+                        ),
+                        React.createElement(
+                            "a",
+                            {
+                                className: "btn hundred-percent primary-color",
+                                href: "https://katharineum-zu-luebeck.de/",
+                                target: "_blank"
+                            },
+                            "Weitere Artikel",
+                            React.createElement(
+                                "i",
+                                {className: "material-icons right"},
+                                "arrow_forward"
+                            )
+                        )
+                    ) : ""
+                ),
+                React.createElement(
+                    "div",
+                    { className: "row" },
+                    React.createElement(
+                        "div",
+                        { className: "col s12 m6" },
+                        React.createElement(
+                            "h5",
+                            null,
+                            "Letzte Aktivit\xE4ten"
+                        ),
+                        this.state.activities && this.state.activities.length > 0 ? React.createElement(
+                            "ul",
+                            { className: "collection" },
+                            this.state.activities.map(function (activity) {
+                                return React.createElement(
+                                    "li",
+                                    { className: "collection-item", key: activity.id },
+                                    React.createElement(
+                                        "span",
+                                        { className: "badge new primary-color" },
+                                        activity.app
+                                    ),
+                                    React.createElement(
+                                        "span",
+                                        { className: "title" },
+                                        activity.title
+                                    ),
+                                    React.createElement(
+                                        "p",
+                                        null,
+                                        React.createElement(
+                                            "i",
+                                            { className: "material-icons left" },
+                                            "access_time"
+                                        ),
+                                        " ",
+                                        activity.created_at
+                                    ),
+                                    React.createElement(
+                                        "p",
+                                        null,
+                                        activity.description
+                                    )
+                                );
+                            })
+                        ) : React.createElement(
+                            "p",
+                            null,
+                            "Noch keine Aktivit\xE4ten vorhanden."
+                        )
+                    ),
+                    React.createElement(
+                        "div",
+                        { className: "col s12 m6" },
+                        React.createElement(
+                            "h5",
+                            null,
+                            "Letzte Benachrichtigungen"
+                        ),
+                        this.state.notifications && this.state.notifications.length > 0 ? React.createElement(
+                            "ul",
+                            { className: "collection" },
+                            this.state.notifications.map(function (notification) {
+                                return React.createElement(
+                                    "li",
+                                    { className: "collection-item", key: notification.id },
+                                    React.createElement(
+                                        "span",
+                                        { className: "badge new primary-color" },
+                                        notification.app
+                                    ),
+                                    React.createElement(
+                                        "span",
+                                        { className: "title" },
+                                        notification.title
+                                    ),
+                                    React.createElement(
+                                        "p",
+                                        null,
+                                        React.createElement(
+                                            "i",
+                                            { className: "material-icons left" },
+                                            "access_time"
+                                        ),
+                                        " ",
+                                        notification.created_at
+                                    ),
+                                    React.createElement(
+                                        "p",
+                                        null,
+                                        notification.description
+                                    ),
+                                    notification.link ? React.createElement(
+                                        "p",
+                                        null,
+                                        React.createElement(
+                                            "a",
+                                            { href: notification.link },
+                                            "Mehr Informationen \u2192"
+                                        )
+                                    ) : ""
+                                );
+                            })
+                        ) : React.createElement(
+                            "p",
+                            null,
+                            "Noch keine Benachrichtigungen vorhanden."
+                        )
+                    )
+                )
+            );
+        }
+    }]);
+
+    return Dashboard;
+}(React.Component);
+
+$(document).ready(function () {
+    var domContainer = document.querySelector('#dashboard_container');
+    ReactDOM.render(React.createElement(Dashboard, null), domContainer);
+});
diff --git a/biscuit/core/static/js/rebus.js b/biscuit/core/static/js/rebus.js
new file mode 100644
index 0000000000000000000000000000000000000000..752008a3dc5b7e2e43261ea7bf0ada5b51e65722
--- /dev/null
+++ b/biscuit/core/static/js/rebus.js
@@ -0,0 +1,642 @@
+/*** THIS FILE IS GENERATED from react/src/rebus.js ***/
+
+var _createClass = function () {
+    function defineProperties(target, props) {
+        for (var i = 0; i < props.length; i++) {
+            var descriptor = props[i];
+            descriptor.enumerable = descriptor.enumerable || false;
+            descriptor.configurable = true;
+            if ("value" in descriptor) descriptor.writable = true;
+            Object.defineProperty(target, descriptor.key, descriptor);
+        }
+    }
+
+    return function (Constructor, protoProps, staticProps) {
+        if (protoProps) defineProperties(Constructor.prototype, protoProps);
+        if (staticProps) defineProperties(Constructor, staticProps);
+        return Constructor;
+    };
+}();
+
+function _classCallCheck(instance, Constructor) {
+    if (!(instance instanceof Constructor)) {
+        throw new TypeError("Cannot call a class as a function");
+    }
+}
+
+function _possibleConstructorReturn(self, call) {
+    if (!self) {
+        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+    }
+    return call && (typeof call === "object" || typeof call === "function") ? call : self;
+}
+
+function _inherits(subClass, superClass) {
+    if (typeof superClass !== "function" && superClass !== null) {
+        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
+    }
+    subClass.prototype = Object.create(superClass && superClass.prototype, {
+        constructor: {
+            value: subClass,
+            enumerable: false,
+            writable: true,
+            configurable: true
+        }
+    });
+    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
+}
+
+var OPTIONS_ONLINE_COMMON = ["Portal ist nicht erreichbar", "Fehlermeldung(en) tauchen auf", "Anmeldung funktioniert nicht", "Zugangsdaten vergessen"];
+
+var BASIC_OPTIONS = [{
+    id: "infrastructureIssues",
+    name: "Infrastrukturprobleme",
+    options: [{
+        id: "presentationDeviceIssue",
+        name: "Problem mit Beamer/Fernseher",
+        helpText: "Bitte wähle aus, wo der Beamer bzw. Fernseher steht!"
+    }, {
+        id: "printerIssue",
+        name: "Problem mit einem Drucker",
+        helpText: "Bitte nenne uns in der Beschreibung das Modell des Druckers, damit wir genau wissen, welchen Drucker du meinst!"
+    }, {
+        id: "subMonitorIssue",
+        name: "Vertretungsplanmonitor funktioniert nicht",
+        helpText: "Nenne uns bitte in der Beschreibung ggf. weitere Informationen!"
+    }, {
+        id: "aulaIssue",
+        name: "Problem in der Aula (→Technik-AG)",
+        helpText: "Deine Anfrage wird direkt an die Technik-AG weitergeleitet."
+    }, {
+        id: "wlanIssue",
+        name: "Probleme mit dem Schul-WLAN (kath-schueler/lehrer)",
+        helpText: "Nenne uns bitte unbedingt auch den Ort in der Schule, an dem das Problem auftrat."
+    }]
+}, {
+    id: "onlineIssues",
+    name: "Webservices",
+    options: [{
+        id: "forum",
+        name: "Forum (ILIAS)",
+        options: OPTIONS_ONLINE_COMMON.concat(["Ich kann meinen Kurs bzw. Klasse nicht sehen/finden.", "Ich kann keine Dateien hochladen.", "Es taucht eine weiße Seite auf.", "Ich habe falsche Informationen gefunden."])
+    }, {
+        id: "mail",
+        name: "Webmail/Mailserver",
+        options: OPTIONS_ONLINE_COMMON.concat(["Mein E-Mail-Programm funktioniert mit meiner …@katharineum.de-Adresse nicht.", "Ich bekomme keine E-Mails bzw. kann keine senden."])
+    }, {
+        id: "schoolapps",
+        name: "SchoolApps",
+        options: OPTIONS_ONLINE_COMMON.concat(["Der Stundenplan/Vertretungsplan ist falsch.", "Ich bin der falschen Klasse zugeordnet.", "Ich habe einen Fehler gefunden."])
+    }, {
+        id: "subOrMenu",
+        name: "Vertretungsplan/Speiseplan",
+        options: OPTIONS_ONLINE_COMMON.concat(["Kein Vertretungsplan zu sehen", "Falscher Vertretungsplan zu sehen", "Kein Speiseplan zu sehen", "Falscher Speiseplan zu sehen"])
+    }, {
+        id: "website",
+        name: "Website (katharineum-zu-luebeck.de)",
+        options: ["Website nicht erreichbar", "Falsche Inhalte vorhanden", "Typografiefehler"]
+
+    }, {
+        id: "otherOnlineIssue",
+        name: "Andere Anwendung"
+    }]
+}, {
+    id: "deviceIssues",
+    name: "Probleme am Computer/Notebook",
+    options: [{
+        id: "loginIssue",
+        name: "Anmeldeproblem/Passwort vergessen"
+    }, {
+        id: "internetIssue",
+        name: "Internetproblem"
+    }, {
+        id: "noReaction",
+        name: "Programm-/Computerabsturz (keine Reaktion)"
+    }, {
+        id: "powerOffNoBoot",
+        name: "Computer/Notebook ist ausgegangen/startet nicht"
+    }, {
+        id: "speedIssue",
+        name: "Computer/Notebook zu langsam"
+    }, {
+        id: "noUSB",
+        name: "USB-Stick wird nicht erkannt"
+    }, {
+        id: "noOpenTray",
+        name: "CD/DVD-Laufwerk öffnet sich nicht"
+    }, {
+        id: "noCDDVD",
+        name: "CD/DVD wird nicht erkannt/abgespielt"
+    }, {
+        id: "keyboardMouse",
+        name: "Tastatur/Maus funktioniert nicht"
+    }, {
+        id: "missingHardware",
+        name: "Tastatur/Maus/Lautsprecher/etc. fehlt"
+    }, {
+        id: "missingKeys",
+        name: "Fehlende Tasten auf der Tastatur"
+    }, {
+        id: "hardwareMisc",
+        name: "Andere Hardware defekt / Äußere Schäden"
+    }]
+}, {
+    id: "otherIssues",
+    name: "Andere Probleme",
+    options: [{
+        id: "extra",
+        name: "Sonstiges"
+    }]
+}];
+
+var OTHER_LOCATIONS = ["Notebookwagen 1. Stock/R 2.06", "Notebookwagen 2. Stock/R 2.10", "Notebookwagen 3. Stock/Physik", "Internetcafe", "Infopoint/Sekretariatsvorraum", "Lehrerzimmer (Vorraum)", "Lehrerzimmer (Hauptraum)"];
+
+function getCategoryOfOption(option) {
+    var _iteratorNormalCompletion = true;
+    var _didIteratorError = false;
+    var _iteratorError = undefined;
+
+    try {
+        for (var _iterator = BASIC_OPTIONS[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+            var category = _step.value;
+
+            // console.log(category);
+            var _iteratorNormalCompletion2 = true;
+            var _didIteratorError2 = false;
+            var _iteratorError2 = undefined;
+
+            try {
+                for (var _iterator2 = category.options[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+                    var opt = _step2.value;
+
+                    // console.log(opt);
+                    if (opt.id === option) {
+                        return category.id;
+                    }
+                }
+            } catch (err) {
+                _didIteratorError2 = true;
+                _iteratorError2 = err;
+            } finally {
+                try {
+                    if (!_iteratorNormalCompletion2 && _iterator2.return) {
+                        _iterator2.return();
+                    }
+                } finally {
+                    if (_didIteratorError2) {
+                        throw _iteratorError2;
+                    }
+                }
+            }
+        }
+    } catch (err) {
+        _didIteratorError = true;
+        _iteratorError = err;
+    } finally {
+        try {
+            if (!_iteratorNormalCompletion && _iterator.return) {
+                _iterator.return();
+            }
+        } finally {
+            if (_didIteratorError) {
+                throw _iteratorError;
+            }
+        }
+    }
+}
+
+function getOption(option) {
+    var _iteratorNormalCompletion3 = true;
+    var _didIteratorError3 = false;
+    var _iteratorError3 = undefined;
+
+    try {
+        for (var _iterator3 = BASIC_OPTIONS[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+            var category = _step3.value;
+            var _iteratorNormalCompletion4 = true;
+            var _didIteratorError4 = false;
+            var _iteratorError4 = undefined;
+
+            try {
+                for (var _iterator4 = category.options[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
+                    var opt = _step4.value;
+
+                    if (opt.id === option) {
+                        return opt;
+                    }
+                }
+            } catch (err) {
+                _didIteratorError4 = true;
+                _iteratorError4 = err;
+            } finally {
+                try {
+                    if (!_iteratorNormalCompletion4 && _iterator4.return) {
+                        _iterator4.return();
+                    }
+                } finally {
+                    if (_didIteratorError4) {
+                        throw _iteratorError4;
+                    }
+                }
+            }
+        }
+    } catch (err) {
+        _didIteratorError3 = true;
+        _iteratorError3 = err;
+    } finally {
+        try {
+            if (!_iteratorNormalCompletion3 && _iterator3.return) {
+                _iterator3.return();
+            }
+        } finally {
+            if (_didIteratorError3) {
+                throw _iteratorError3;
+            }
+        }
+    }
+}
+
+var Select = function (_React$Component) {
+    _inherits(Select, _React$Component);
+
+    function Select() {
+        _classCallCheck(this, Select);
+
+        return _possibleConstructorReturn(this, (Select.__proto__ || Object.getPrototypeOf(Select)).apply(this, arguments));
+    }
+
+    _createClass(Select, [{
+        key: "render",
+        value: function render() {
+            return React.createElement(
+                "select",
+                {onChange: this.props.onChange, defaultValue: "no", required: this.props.show},
+                React.createElement(
+                    "option",
+                    {value: "no", disabled: true},
+                    "Nichts ausgew\xE4hlt"
+                ),
+                this.props.values.map(function (val, i) {
+                    return React.createElement(
+                        "option",
+                        {value: val, key: i},
+                        val
+                    );
+                }),
+                React.createElement(
+                    "option",
+                    {value: "extra"},
+                    this.props.defaultValue
+                )
+            );
+        }
+    }]);
+
+    return Select;
+}(React.Component);
+
+Select.propTypes = {
+    onChange: PropTypes.func.isRequired,
+    values: PropTypes.array.isRequired,
+    defaultValue: PropTypes.string,
+    show: PropTypes.bool.isRequired
+
+};
+
+Select.defaultProps = {
+    defaultValue: "Sonstiges"
+};
+
+var Input = function (_React$Component2) {
+    _inherits(Input, _React$Component2);
+
+    function Input() {
+        _classCallCheck(this, Input);
+
+        return _possibleConstructorReturn(this, (Input.__proto__ || Object.getPrototypeOf(Input)).apply(this, arguments));
+    }
+
+    _createClass(Input, [{
+        key: "render",
+        value: function render() {
+            return React.createElement(
+                "div",
+                {
+                    className: (this.props.show ? "" : "hide ") + "input-field col s12 m12 l4"
+                },
+                React.createElement(
+                    "i",
+                    {className: "material-icons prefix"},
+                    this.props.icon
+                ),
+                this.props.children,
+                React.createElement(
+                    "label",
+                    null,
+                    this.props.label
+                )
+            );
+        }
+    }]);
+
+    return Input;
+}(React.Component);
+
+Input.propTypes = {
+    icon: PropTypes.string,
+    show: PropTypes.bool,
+    label: PropTypes.string.isRequired,
+    children: PropTypes.object.isRequired
+};
+
+Input.defaultProps = {
+    icon: "list",
+    show: false
+};
+
+var REBUSDynSelect = function (_React$Component3) {
+    _inherits(REBUSDynSelect, _React$Component3);
+
+    function REBUSDynSelect() {
+        _classCallCheck(this, REBUSDynSelect);
+
+        var _this3 = _possibleConstructorReturn(this, (REBUSDynSelect.__proto__ || Object.getPrototypeOf(REBUSDynSelect)).call(this));
+
+        _this3._onCategoryChanges = function (e) {
+            var opt = e.target.value;
+            var category = getCategoryOfOption(opt);
+            var option = getOption(opt);
+
+            // Get matching helper text
+            var helpText = option.helpText || _this3.state.helpText;
+            if (category === "deviceIssues") {
+                helpText = "Wähle bitte das Gerät mit dem Problem aus! Bitte vergiss nicht, uns das Problem unten genauer zu beschreiben!";
+            } else if (category === "onlineIssues") {
+                helpText = "Bitte konkretisiere das Problem durch eine Auswahl und gib bitte unten genauere Informationen an.";
+            } else if (category === "otherIssues") {
+                helpText = "Da es sich scheinbar um ein seltenes oder noch nicht erfasstes Problem handelt, gib uns bitte besonders viele Informationen.";
+            }
+
+            // Update state
+            _this3.setState({
+                selectedCategory: category,
+                selectedOption: option,
+                step: 1,
+                helpText: helpText
+            });
+        };
+
+        _this3._onSetB = function (e) {
+            var val = e.target.value;
+            _this3.setState({
+                valueB: val,
+                step: 2
+            });
+        };
+
+        _this3._onSetC = function (e) {
+            var val = e.target.value;
+            _this3.setState({
+                valueC: val,
+                step: 2
+            });
+        };
+
+        _this3.state = {
+            selectedCategory: "noCategory",
+            selectedOption: null,
+            helpText: "Wähle bitte eine Kategorie aus!",
+            valueB: "",
+            valueC: "",
+            step: 0
+        };
+        return _this3;
+    }
+
+    _createClass(REBUSDynSelect, [{
+        key: "componentDidMount",
+        value: function componentDidMount() {
+            // Init materialize selects
+            var elems = document.querySelectorAll('select');
+            M.FormSelect.init(elems, {});
+        }
+    }, {
+        key: "render",
+        value: function render() {
+            var LOCATIONS = this.props.rooms.concat(OTHER_LOCATIONS);
+            var LOCATIONS_WITH_POSSIBLE_PRESENTATION_DEVICE = this.props.rooms;
+            LOCATIONS.sort();
+
+            // console.log(this.state);
+            var that = this;
+            var sC = this.state.selectedCategory;
+            var sO = this.state.selectedOption ? this.state.selectedOption.id : null;
+            var step = this.state.step;
+            // console.log(BASIC_OPTIONS[2].options);
+            return React.createElement(
+                "div",
+                {className: "App"},
+                React.createElement(
+                    "div",
+                    {className: "row"},
+                    React.createElement(
+                        "div",
+                        {
+                            className: "input-field col s12 m12 l4"
+                        },
+                        React.createElement(
+                            "i",
+                            {className: "material-icons prefix"},
+                            "list"
+                        ),
+                        React.createElement(
+                            "select",
+                            {
+                                onChange: this._onCategoryChanges, defaultValue: "noCategory", className: "validate",
+                                required: true
+                            },
+                            "-",
+                            React.createElement(
+                                "option",
+                                {value: "noCategory", disabled: true},
+                                "Keine Kategorie ausgew\xE4hlt"
+                            ),
+                            BASIC_OPTIONS.map(function (category) {
+                                return React.createElement(
+                                    "optgroup",
+                                    {label: category.name, key: category.id},
+                                    category.options.map(function (option) {
+                                        return React.createElement(
+                                            "option",
+                                            {value: option.id, key: option.id},
+                                            option.name
+                                        );
+                                    })
+                                );
+                            })
+                        ),
+                        React.createElement(
+                            "label",
+                            null,
+                            "Kategorie"
+                        )
+                    ),
+                    React.createElement(
+                        Input,
+                        {label: "Ort des Computer/Notebook", icon: "location_on", show: sC === "deviceIssues"},
+                        React.createElement(Select, {
+                            onChange: this._onSetB, values: LOCATIONS, defaultValue: "Anderer Ort",
+                            show: sC === "deviceIssues"
+                        })
+                    ),
+                    React.createElement(
+                        Input,
+                        {
+                            label: "Ort des Beamer/Fernseher", icon: "location_on",
+                            show: sO === "presentationDeviceIssue"
+                        },
+                        React.createElement(Select, {
+                            onChange: this._onSetB, values: LOCATIONS_WITH_POSSIBLE_PRESENTATION_DEVICE,
+                            defaultValue: "Anderer Raum", show: sO === "presentationDeviceIssue"
+                        })
+                    ),
+                    React.createElement(
+                        Input,
+                        {label: "Art des Problems", icon: "bug_report", show: sO === "printerIssue"},
+                        React.createElement(Select, {
+                            onChange: this._onSetB,
+                            values: ["Papierstau", "Toner leer", "Papier leer", "Drucker bekommt keine Daten"],
+                            defaultValue: "Anderes Problem", show: sO === "subMonitorIssue"
+                        })
+                    ),
+                    React.createElement(
+                        Input,
+                        {label: "Art des Problems", icon: "bug_report", show: sO === "subMonitorIssue"},
+                        React.createElement(Select, {
+                            onChange: this._onSetB,
+                            values: ["Schwarzer Bildschirm", "Tage wechseln nicht (Eingefroren)"],
+                            defaultValue: "Anderes Problem", show: sO === "subMonitorIssue"
+                        })
+                    ),
+                    React.createElement(
+                        Input,
+                        {label: "Art des Problems", icon: "bug_report", show: sO === "wlanIssue"},
+                        React.createElement(Select, {
+                            onChange: this._onSetB,
+                            values: ["Kein Empfang", "Zugangsdaten funktionieren nicht", "Geschwindigkeit zu langsam"],
+                            defaultValue: "Anderes Problem", show: sO === "wlanIssue"
+                        })
+                    ),
+                    BASIC_OPTIONS[1].options.map(function (opt) {
+                        if (opt.options) {
+                            return React.createElement(
+                                Input,
+                                {
+                                    label: "Art des Problems", icon: "bug_report",
+                                    show: sC === "onlineIssues" && sO === opt.id, key: opt.id
+                                },
+                                React.createElement(Select, {
+                                    onChange: that._onSetB,
+                                    values: opt.options,
+                                    defaultValue: "Anderes Problem", show: sC === "onlineIssues" && sO === opt.id,
+                                    key: opt.id
+                                })
+                            );
+                        } else {
+                            return React.createElement("p", null);
+                        }
+                    }),
+                    React.createElement(
+                        Input,
+                        {
+                            label: "Handelt es sich um einen Beamer oder einen Fernseher?", icon: "tv",
+                            show: sO === "presentationDeviceIssue" && step === 2
+                        },
+                        React.createElement(Select, {
+                            onChange: this._onSetC, values: ["Beamer", "Fernseher/Bildschirm"],
+                            defaultValue: "Sonstiges", show: sO === "presentationDeviceIssue" && step === 2
+                        })
+                    ),
+                    React.createElement(
+                        Input,
+                        {
+                            label: "Ort des Druckers", icon: "location_on",
+                            show: sO === "printerIssue" && step === 2
+                        },
+                        React.createElement(Select, {
+                            onChange: this._onSetC, values: LOCATIONS,
+                            defaultValue: "Anderer Raum", show: sO === "presentationDeviceIssue"
+                        })
+                    ),
+                    React.createElement(
+                        Input,
+                        {
+                            label: "Um welches WLAN-Netzwerk handelt es sich?", icon: "wifi",
+                            show: sO === "wlanIssue" && step === 2
+                        },
+                        React.createElement(Select, {
+                            onChange: this._onSetC,
+                            values: ["kath-schueler", "kath-lehrer", "kath-edu", "kath-gaeste"],
+                            defaultValue: "-", show: sO === "wlanIssue" && step === 2
+                        })
+                    ),
+                    React.createElement(
+                        "div",
+                        {
+                            className: (sC === "deviceIssues" && step === 2 ? "" : "hide ") + "input-field col s12 m12 l4"
+                        },
+                        React.createElement(
+                            "i",
+                            {className: "material-icons prefix"},
+                            "device_unknown"
+                        ),
+                        React.createElement("input", {
+                            type: "text", id: "valc", onChange: this._onSetC,
+                            required: sC === "deviceIssues" && step === 2, className: "validate"
+                        }),
+                        React.createElement(
+                            "label",
+                            {htmlFor: "valc"},
+                            "Um welches Ger\xE4t handelt es sich?"
+                        )
+                    ),
+                    React.createElement(
+                        "div",
+                        {className: "col s12"},
+                        React.createElement(
+                            "p",
+                            null,
+                            React.createElement(
+                                "i",
+                                {className: "material-icons left"},
+                                "info"
+                            ),
+                            this.state.helpText
+                        )
+                    )
+                ),
+                React.createElement(
+                    "div",
+                    null,
+                    React.createElement("input", {
+                        type: "hidden", name: "a",
+                        value: this.state.selectedOption ? this.state.selectedOption.name : ""
+                    }),
+                    React.createElement("input", {type: "hidden", name: "b", value: this.state.valueB}),
+                    React.createElement("input", {type: "hidden", name: "c", value: this.state.valueC})
+                )
+            );
+        }
+    }]);
+
+    return REBUSDynSelect;
+}(React.Component);
+
+REBUSDynSelect.propTypes = {
+    rooms: PropTypes.array.isRequired
+};
+
+$(document).ready(function () {
+    var domContainer = document.querySelector('#dynselect');
+    ReactDOM.render(React.createElement(REBUSDynSelect, props), domContainer);
+});
diff --git a/biscuit/core/templates/common/404.html b/biscuit/core/templates/common/404.html
new file mode 100644
index 0000000000000000000000000000000000000000..a20f264e20b2cef11dabf9d3c3c10e4547cef9e5
--- /dev/null
+++ b/biscuit/core/templates/common/404.html
@@ -0,0 +1,135 @@
+{#<!DOCTYPE html>#}
+{#<html lang="de">#}
+{#<head>#}
+{#    <meta charset="utf-8">#}
+{#    <meta http-equiv="X-UA-Compatible" content="IE=edge">#}
+{#    <meta name="viewport" content="width=device-width,initial-scale=1">#}
+{#    <meta name="description" content="Selbst programmierte Anwendungen für den Schullaltag am Katharineum zu Lübeck">#}
+{#    <title>SchoolApps – Katharineum zu Lübeck</title>#}
+{##}
+{#    <!-- Android  -->#}
+{#    <meta name="theme-color" content="#da1f3d">#}
+{#    <meta name="mobile-web-app-capable" content="yes">#}
+{##}
+{#    <!-- iOS -->#}
+{#    <meta name="apple-mobile-web-app-title" content="SchoolApps">#}
+{#    <meta name="apple-mobile-web-app-capable" content="yes">#}
+{#    <meta name="apple-mobile-web-app-status-bar-style" content="default">#}
+{##}
+{#    <!-- Windows  -->#}
+{#    <meta name="msapplication-navbutton-color" content="#da1f3d">#}
+{#    <meta name="msapplication-TileColor" content="#da1f3d">#}
+{#    <meta name="msapplication-TileImage" content="ms-icon-144x144.png">#}
+{#    <meta name="msapplication-config" content="browserconfig.xml">#}
+{##}
+{#    <!-- Pinned Sites  -->#}
+{#    <meta name="application-name" content="SchoolApps">#}
+{#    <meta name="msapplication-tooltip" content="SchoolApps">#}
+{#    <meta name="msapplication-starturl" content="/">#}
+{##}
+{#    <!-- Tap highlighting  -->#}
+{#    <meta name="msapplication-tap-highlight" content="no">#}
+{##}
+{#    <!-- UC Mobile Browser  -->#}
+{#    <meta name="full-screen" content="yes">#}
+{#    <meta name="browsermode" content="application">#}
+{##}
+{##}
+{#    <!-- Main Link Tags  -->#}
+{#    <link href="/static/icons/favicon_16.png" rel="icon" type="image/png" sizes="16x16">#}
+{#    <link href="/static/icons/favicon_32.png" rel="icon" type="image/png" sizes="32x32">#}
+{#    <link href="/static/icons/favicon_48.pngg" rel="icon" type="image/png" sizes="48x48">#}
+{##}
+{#    <!-- iOS  -->#}
+{#    <!-- non-retina iPad iOS 7 -->#}
+{#    <link rel="apple-touch-icon" href="/static/icons/apple_76.png" sizes="76x76">#}
+{#    <!-- retina iPhone vor iOS 7 -->#}
+{#    <link rel="apple-touch-icon" href="/static/icons/apple_114.png" sizes="114x114">#}
+{#    <!-- retina iPad iOS 7 -->#}
+{#    <link rel="apple-touch-icon" href="/static/icons/apple_152.png" sizes="152x152">#}
+{#    <!-- retina iPad iOS 7 für iPhone 6 Plus -->#}
+{#    <link rel="apple-touch-icon" href="/static/icons/apple_180.png" sizes="180x180">#}
+{##}
+{##}
+{#    <!-- Pinned Tab  -->#}
+{##}
+{##}
+{#    <!-- Android  -->#}
+{#    <link href="/static/icons/android_192.png" rel="icon" sizes="192x192">#}
+{##}
+{#    <!-- Others -->#}
+{##}
+{##}
+{##}
+{#    <!-- Favicon -->#}
+{#    <link rel="shortcut icon" type="image/x-icon" href="/static/common/favicon.ico">#}
+{#    <link rel="manifest" href="/static/common/manifest.json">#}
+{##}
+{#    <!--------->#}
+{#    <!-- CSS -->#}
+{#    <!--------->#}
+{#    <link href="/static/css/materialdesignicons-webfont/material-icons.css" rel="stylesheet">#}
+{#    <link rel="stylesheet" type="text/css" media="screen"#}
+{#          href="/static/css/materialize.min.css">#}
+{#    <link rel="stylesheet" type="text/css" href="/static/common/style.css">#}
+{#    <script src="/static/js/jquery/jquery-3.3.1.slim.min.js"></script>#}
+{##}
+{##}
+{#    <!-- location (for "active" in sidenav -->#}
+{#    <meta name="active-loaction" content="404">#}
+{##}
+{##}
+{#</head>#}
+{#<body>#}
+{##}
+{#<header>#}
+{#    <!-- Menu button (sidenav) -->#}
+{#    <div class="container">#}
+{#        <a href="#" data-target="slide-out" class="top-nav sidenav-trigger hide-on-large-only">#}
+{#            <i class="material-icons">menu</i>#}
+{#        </a>#}
+{#    </div>#}
+{##}
+{#    <!-- Nav bar (logged in as, logout) -->#}
+{#    <nav class="primary-color">#}
+{#        <a class="brand-logo" href="/">SchoolApps</a>#}
+{#    </nav>#}
+{##}
+{#    <div id="print-header" class="row">#}
+{#        <div class="col s6 logo">#}
+{#            <img src="/static/common/logo.png">#}
+{#        </div>#}
+{#        <div class="col s6 right-align">#}
+{#            <a href="/"><strong>SchoolApps</strong></a><br>#}
+{#            Katharineum zu Lübeck#}
+{#        </div>#}
+{#    </div>#}
+{##}
+{#    <!-- Main nav (sidenav) -->#}
+{#    <ul id="slide-out" class="sidenav sidenav-fixed">#}
+{#        <li class="logo">#}
+{#            <a id="logo-container" href="/" class="brand-logo">#}
+{#                <img src="/static/common/logo.png" alt="Logo des Katharineums">#}
+{#            </a>#}
+{#        </li>#}
+{#    </ul>#}
+{#</header>#}
+{% include "partials/header.html" %}
+
+<main>
+    <h3>Leider existiert diese Seite nicht. (Fehler 404)</h3>
+
+    <p class="flow-text">
+      Beim Aufrufen dieser Seite ist ein Fehler aufgetreten. Wahrscheinlich existiert die gewünschte Seite unter der Adresse "<code id="url"></code>" nicht.
+
+        Solltest du der Meinung sein, dass diese Seite eigentlich existieren müsste, wende dich bitte an die
+        <a href="mailto:support@katharineum.de">Computer-AG</a>.
+    </p>
+
+</main>
+
+<script>
+    document.getElementById("url").innerHTML = window.location.pathname;
+</script>
+
+{% include 'partials/footer.html' %}
diff --git a/biscuit/core/templates/common/about.html b/biscuit/core/templates/common/about.html
new file mode 100644
index 0000000000000000000000000000000000000000..1cb16028bc60620525ad311638ff5c8f9d78804d
--- /dev/null
+++ b/biscuit/core/templates/common/about.html
@@ -0,0 +1,51 @@
+{% load martortags %}
+
+{% include "partials/header.html" %}
+<main>
+
+    <div class="card">
+        <div class="card-content">
+            <span class="card-title">Entwickler und Copyright</span>
+            <p>{{ COPYRIGHT|linebreaksbr|safe_markdown }}</p>
+            <br>
+            <p>
+                GitHub:
+                <a href="https://github.com/Katharineum/school-apps">https://github.com/Katharineum/school-apps</a>
+            </p>
+            <p>
+                Kontakt:
+                <a href="mailto:support@katharineum.de">support@katharineum.de</a>
+            </p>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-content">
+            <span class="card-title">Lizenz</span>
+EUPL v1.2 or later
+        </div>
+    </div>
+    <div class="card">
+        <div class="card-content">
+            <span class="card-title">Verwendete Open-Source-Komponenten</span>
+            Folgende Open-Source-Komponenten wurden in SchoolApps benutzt:
+
+            <ul class="collection">
+                {% for component in components %}
+                    <li class="collection-item">
+                        <h6>{{ component.0 }}</h6>
+                        <p style="margin-bottom: 10px;">
+                            <a href="{{ component.1 }}">{{ component.1 }}</a>
+                            · Lizensiert unter der
+                            <a href="{{ component.3 }}">{{ component.2 }}</a>
+                        </p>
+                    </li>
+                {% endfor %}
+            </ul>
+
+            Ein Anspruch auf Vollständigkeit wird nicht erhoben.
+        </div>
+    </div>
+</main>
+
+{% include "partials/footer.html" %}
diff --git a/biscuit/core/templates/common/offline.html b/biscuit/core/templates/common/offline.html
new file mode 100644
index 0000000000000000000000000000000000000000..913cb0285293de156c9518da89c5e1f56f3e2040
--- /dev/null
+++ b/biscuit/core/templates/common/offline.html
@@ -0,0 +1,16 @@
+{% include 'partials/header.html' %}
+
+<main>
+    <h3><i class="material-icons left medium" style="font-size: 2.92rem;">signal_wifi_off</i> Es besteht keine
+        Verbindung zum Internet. </h3>
+
+    <p class="flow-text">
+        Beim Aufrufen dieser Seite ist ein Fehler aufgetreten. Vermutlich hast du keine Verbindung zum Internet.
+        Prüfe, ob dein WLAN oder deine mobilen Daten engeschaltet sind, und probiere es noch einmal.
+        Wenn du der Meinung bist, dass du über eine Verbindung verfügst, wende dich bitte an die
+        <a href="mailto:support@katharineum.de">Computer-AG</a>
+        .
+    </p>
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/biscuit/core/templates/components/msgbox.html b/biscuit/core/templates/components/msgbox.html
new file mode 100644
index 0000000000000000000000000000000000000000..1eb0d0b8ca211e6c1614c4e38d5db6b537cb9a30
--- /dev/null
+++ b/biscuit/core/templates/components/msgbox.html
@@ -0,0 +1,10 @@
+{% if msg %}
+    <div class="alert {{ status }}">
+        <div>
+            {% if icon != "" %}
+                <i class="material-icons left">{{ icon }}</i>
+            {% endif %}
+            {{ msg }}
+        </div>
+    </div>
+{% endif %}
diff --git a/biscuit/core/templates/components/react.html b/biscuit/core/templates/components/react.html
new file mode 100644
index 0000000000000000000000000000000000000000..452959486f3e4a317b650e1219575c08a4065df2
--- /dev/null
+++ b/biscuit/core/templates/components/react.html
@@ -0,0 +1,11 @@
+{% load staticfiles %}
+
+{% if debug %}
+    <script src="{% static "js/react/prop-types.development.js" %}"></script>
+    <script src="{% static "js/react/react.development.js" %}"></script>
+    <script src="{% static "js/react/react-dom.development.js" %}"></script>
+{% else %}
+    <script src="{% static "js/react/prop-types.production.min.js" %}"></script>
+    <script src="{% static "js/react/react.production.min.js" %}"></script>
+    <script src="{% static "js/react/react-dom.production.min.js" %}"></script>
+{% endif %}
diff --git a/biscuit/core/templates/mail/email.html b/biscuit/core/templates/mail/email.html
new file mode 100644
index 0000000000000000000000000000000000000000..58f6f6b639a423649f425ea12997aa28e6ec7007
--- /dev/null
+++ b/biscuit/core/templates/mail/email.html
@@ -0,0 +1,13 @@
+{% include "mail/header.html" %}
+
+<main>
+    Hallo {{ user.get_full_name }},
+    vielen Dank, dass Sie <b>SchoolApps</b> benutzen.
+    <hr>
+    Weitere Informationen finden Sie hier:
+    <ul>
+        <li><a href="https://forum.katharineum.de">FORUM</a></li>
+    </ul>
+    <hr>
+    Ihre Computer-AG
+</main>
diff --git a/biscuit/core/templates/mail/email.txt b/biscuit/core/templates/mail/email.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c781ae1d5fe821a8c1903eea66f284083d4c1ec4
--- /dev/null
+++ b/biscuit/core/templates/mail/email.txt
@@ -0,0 +1,9 @@
+Hallo {{ user.name }},
+vielen Dank, dass du SchoolApps benutzt.
+
+----
+Weitere Informationen findest du hier:
+- FORUM (forum.katharineum.de)
+----
+
+Deine Computer-AG
diff --git a/biscuit/core/templates/mail/header.html b/biscuit/core/templates/mail/header.html
new file mode 100644
index 0000000000000000000000000000000000000000..ad452e53be3e7ada776b02ce2b64ab605e206b9f
--- /dev/null
+++ b/biscuit/core/templates/mail/header.html
@@ -0,0 +1,8 @@
+<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
+
+<style>
+    main {
+        padding: 10px;
+    }
+</style>
diff --git a/biscuit/core/templates/mail/notification.html b/biscuit/core/templates/mail/notification.html
new file mode 100644
index 0000000000000000000000000000000000000000..a5619f9aa49bdc693e106a8b5cca888a5d8b29a4
--- /dev/null
+++ b/biscuit/core/templates/mail/notification.html
@@ -0,0 +1,15 @@
+{% include "mail/header.html" %}
+
+<main>
+    <p>Hallo {{ notification.user.get_full_name }}, <br>
+        wir haben eine neue Nachricht für dich:</p>
+    <blockquote>
+        <p>{{ notification.description }}</p>
+        {% if notification.link %}
+            <a href="{{ notification.link }}">Weitere Informationen →</a>
+        {% endif %}
+    </blockquote>
+    <p>Von {{ notification.app }} am {{ notification.created_at }}</p>
+
+    <i>Dein SchoolApps-Team</i>
+</main>
diff --git a/biscuit/core/templates/mail/notification.txt b/biscuit/core/templates/mail/notification.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ca927a4fd764d6d1e02d225a9dc43a201adf695d
--- /dev/null
+++ b/biscuit/core/templates/mail/notification.txt
@@ -0,0 +1,13 @@
+Hallo {{ notification.user.name }},
+wir haben eine neue Nachricht für dich:
+
+
+{{ notification.description }}
+
+{% if notification.link %}
+    Weitere Informationen: {{ notification.link }}
+{% endif %}
+
+Von {{ notification.app }} am {{ notification.created_at }}
+
+Dein SchoolApps-Team
diff --git a/biscuit/core/templates/martor/editor.html b/biscuit/core/templates/martor/editor.html
new file mode 100644
index 0000000000000000000000000000000000000000..dccea1c07d93851a03392b1fef97228098912b9e
--- /dev/null
+++ b/biscuit/core/templates/martor/editor.html
@@ -0,0 +1,71 @@
+{% load i18n %}
+<div class="main-martor main-martor-{{ field_name }} card" data-field-name="{{ field_name }}">
+    <div class="section-martor card-content">
+        <div class="row">
+            <ul class="tabs col s12 m3 primary-color-text">
+                <li class="tab">
+                    <a class="item" data-tab="editor-tab-{{ field_name }}" href="#editor-{{ field_name }}">
+                        <i class="material-icons">edit</i>
+                        {#                        {% trans "Editor" %}#}
+                    </a>
+                </li>
+                <li class="tab">
+                    <a class="item" data-tab="preview-tab-{{ field_name }}" href="#preview-{{ field_name }}">
+                        <i class="material-icons">slideshow</i>
+                        {#                        {% trans "Preview" %}#}
+                    </a>
+                </li>
+
+            </ul>
+            <div class="col s12 m9">
+                {% include "martor/toolbar.html" %}
+            </div>
+        </div>
+        <div class="col s12 tab segment" data-tab="editor-tab-{{ field_name }}"
+             id="editor-{{ field_name }}">
+            <div class="ui active dimmer upload-progress" style="display:none">
+                <div class="ui text loader">{% trans "Uploading... please wait..." %}</div>
+            </div>
+
+            <div id="martor-{{ field_name }}" class="martor-field martor-field-{{ field_name }}"></div>
+            {{ martor }}
+            <i class="angle double down grey icon expand-editor"></i>
+        </div>
+        <div class="martor-preview col s12 tab segment" data-tab="preview-tab-{{ field_name }}"
+             id="preview-{{ field_name }}">
+            <p>{% trans "Nothing to preview" %}</p>
+        </div>
+    </div><!-- end  /.section-martor -->
+
+    {% include 'martor/guide.html' %}
+    {% include 'martor/emoji.html' %}
+    <script type="text/javascript">
+        $(document).ready(function () {
+            $('.tabs').tabs();
+            $('.dropdown-trigger').dropdown();
+            $('.modal').modal();
+        });
+    </script>
+    <style type="text/css">
+        .main-martor .card-content {
+            margin: 0;
+            padding: 0;
+        }
+
+        .martor-toolbar .btn-flat {
+            padding: 0 6px;
+        }
+
+        .maximize::before {
+            content: "fullscreen";
+        }
+
+        .minimize::before {
+            content: "fullscreen_exit";
+        }
+
+        .main-martor .tab a i {
+            line-height: inherit;
+        }
+    </style>
+</div>
diff --git a/biscuit/core/templates/martor/emoji.html b/biscuit/core/templates/martor/emoji.html
new file mode 100644
index 0000000000000000000000000000000000000000..61bc0a1843efa512bc15147b786d21e94758b2a5
--- /dev/null
+++ b/biscuit/core/templates/martor/emoji.html
@@ -0,0 +1,14 @@
+{% load i18n %}
+<div class="ui large modal scrolling transition modal-emoji">
+    <i class="close icon"></i>
+    <div class="header"><i class="help circle icon"></i> {% trans "Select Emoji to Insert" %}</div>
+    <div class="content emoji-content-base">
+        <div class="ui segment emoji-loader-init">
+            <div class="ui active inverted dimmer">
+                <div class="ui text loader">{% trans "Preparing emojis..." %}</div>
+            </div>
+        </div>
+        <div class="ui grid emoji-content-body">
+        </div>
+    </div>
+</div>
diff --git a/biscuit/core/templates/martor/guide.html b/biscuit/core/templates/martor/guide.html
new file mode 100644
index 0000000000000000000000000000000000000000..60d35b70403144b39a63df6b4d7ee084cc844268
--- /dev/null
+++ b/biscuit/core/templates/martor/guide.html
@@ -0,0 +1,176 @@
+{% load i18n static %}
+<div class="ui medium modal scrolling transition modal-help-guide modal" id="modal-{{ field_name }}">
+    {#  <i class="close icon"></i>#}
+
+    <div class="modal-content">
+        <a href="#" class="modal-close btn-flat right"><i class="material-icons">close</i></a>
+
+        <h4><i class="help circle icon"></i> {% trans "Markdown Guide" %}</h4>
+        <p>{% blocktrans with doc_url='http://commonmark.org/help/' %}This site is powered by Markdown. For full
+            documentation,
+            <a href="{{ doc_url }}" target="_blank">click here</a>{% endblocktrans %}</p>
+        <table class="ui celled table markdown-reference">
+            <thead>
+            <tr>
+                <th>{% trans "Code" %}</th>
+                <th>{% trans "Or" %}</th>
+                <th>Linux/Windows</th>
+                <th>Mac OS</th>
+                <th>{% trans "... to Get" %}</th>
+            </tr>
+            </thead>
+            <tbody>
+            {#            <tr>#}
+            {#                <td>:emoji_name:</td>#}
+            {#                <td>&mdash;</td>#}
+            {#                <td>&mdash;</td>#}
+            {#                <td>&mdash;</td>#}
+            {#                <td><img class="marked-emoji" src="{% static 'plugins/images/heart.png' %}"></td>#}
+            {#            </tr>#}
+            {#            <tr>#}
+            {#                <td>@[username]</td>#}
+            {#                <td>&mdash;</td>#}
+            {#                <td>Ctrl+M</td>#}
+            {#                <td>Command+M</td>#}
+            {#                <td><a href="#">@username</a></td>#}
+            {#            </tr>#}
+            {#            <tr>#}
+            {#                <td colspan="5"></td>#}
+            {#            </tr>#}
+            <tr>
+                <td>*Italic*</td>
+                <td>_Italic_</td>
+                <td>Ctrl+I</td>
+                <td>Command+I</td>
+                <td><em>Italic</em></td>
+            </tr>
+            <tr>
+                <td>**Bold**</td>
+                <td>__Bold__</td>
+                <td>Ctrl+B</td>
+                <td>Command+B</td>
+                <td><strong>Bold</strong></td>
+            </tr>
+            <tr>
+                <td>++Underscores++</td>
+                <td>&mdash;</td>
+                <td>Shift+U</td>
+                <td>Option+U</td>
+                <td>
+                    <ins>Underscores</ins>
+                </td>
+            </tr>
+            <tr>
+                <td>~~Strikethrough~~</td>
+                <td>&mdash;</td>
+                <td>Shift+S</td>
+                <td>Option+S</td>
+                <td>
+                    <del>Strikethrough</del>
+                </td>
+            </tr>
+            <tr>
+                <td># Heading 1</td>
+                <td>Heading 1<br> =========</td>
+                <td>Ctrl+Alt+1</td>
+                <td>Command+Option+1</td>
+                <td><h1>Heading 1</h1></td>
+            </tr>
+            <tr>
+                <td>## Heading 2</td>
+                <td>Heading 2<br> -----------</td>
+                <td>Ctrl+Alt+2</td>
+                <td>Command+Option+2</td>
+                <td><h2>Heading 2</h2></td>
+            </tr>
+            <tr>
+                <td>[Link](http://a.com)</td>
+                <td>[Link][1]<br> &#8285;<br> [1]: http://b.org</td>
+                <td>Ctrl+L</td>
+                <td>Command+L</td>
+                <td>
+                    <a href="http://commonmark.org/">Link</a>
+                </td>
+            </tr>
+            {#            <tr>#}
+            {#                <td>![Image](http://url/a.png)</td>#}
+            {#                <td>![Image][1]<br> &#8285;<br> [1]: http://url/b.jpg</td>#}
+            {#                <td>Ctrl+Shift+I</td>#}
+            {#                <td>Command+Option+I</td>#}
+            {#                <td><img src="{% static 'plugins/images/commonmark.png' %}" width="36" height="36" alt="Markdown"></td>#}
+            {#            </tr>#}
+            <tr>
+                <td>&gt; Blockquote</td>
+                <td>&mdash;</td>
+                <td>Ctrl+Q</td>
+                <td>Command+Q</td>
+                <td>
+                    <blockquote>Blockquote</blockquote>
+                </td>
+            </tr>
+            <tr>
+                <td>A paragraph.<br><br> A paragraph after 1 blank line.</td>
+                <td>&mdash;</td>
+                <td>&mdash;</td>
+                <td>&mdash;</td>
+                <td><p>A paragraph.</p>
+                    <p>A paragraph after 1 blank line.</p></td>
+            </tr>
+            <tr>
+                <td><p>* List<br> * List<br> * List</p></td>
+                <td><p> - List<br> - List<br> - List<br></p></td>
+                <td>Ctrl+U</td>
+                <td>Command+U</td>
+                <td>
+                    <ul>
+                        <li>List</li>
+                        <li>List</li>
+                        <li>List</li>
+                    </ul>
+                </td>
+            </tr>
+            <tr>
+                <td><p> 1. One<br> 2. Two<br> 3. Three</p></td>
+                <td><p> 1) One<br> 2) Two<br> 3) Three</p></td>
+                <td>Ctrl+Shift+O</td>
+                <td>Command+Option+O</td>
+                <td>
+                    <ol>
+                        <li>One</li>
+                        <li>Two</li>
+                        <li>Three</li>
+                    </ol>
+                </td>
+            </tr>
+            <tr>
+                <td>Horizontal Rule<br><br> -----------</td>
+                <td>Horizontal Rule<br><br> ***********</td>
+                <td>Ctrl+H</td>
+                <td>Command+H</td>
+                <td>Horizontal Rule
+                    <hr>
+                </td>
+            </tr>
+            <tr>
+                <td>`Inline code` with backticks</td>
+                <td>&mdash;</td>
+                <td>Ctrl+Alt+C</td>
+                <td>Command+Option+C</td>
+                <td><code>Inline code</code> with backticks</td>
+            </tr>
+            <tr>
+                <td>```<br> def whatever(foo):<br>&nbsp;&nbsp;&nbsp;&nbsp;return foo<br>```</td>
+                <td><b>with tab / 4 spaces</b><br>....def whatever(foo):<br>....&nbsp;&nbsp;&nbsp;&nbsp;return foo</td>
+                <td>Ctrl+Alt+P</td>
+                <td>Command+Option+P</td>
+                <td>
+                    <pre>def whatever(foo):<br/>    return foo</pre>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+    <div class="modal-footer">
+        <a href="#" class="modal-close waves-effect waves-green btn-flat">Close</a>
+    </div>
+</div>
diff --git a/biscuit/core/templates/martor/toolbar.html b/biscuit/core/templates/martor/toolbar.html
new file mode 100644
index 0000000000000000000000000000000000000000..da37e7a4d5c8936f34b28e8975846cc24fa172b1
--- /dev/null
+++ b/biscuit/core/templates/martor/toolbar.html
@@ -0,0 +1,89 @@
+{% load i18n %}
+<div class="ui right floated item martor-toolbar">
+    <a class="markdown-selector btn-flat markdown-bold" title="{% trans 'Bold' %} (Ctrl+B)">
+        <i class="material-icons">format_bold</i>
+    </a>
+    <a class="markdown-selector btn-flat markdown-italic" title="{% trans 'Italic' %} (Ctrl+I)">
+        <i class="material-icons">format_italic</i>
+    </a>
+    <a class="markdown-selector btn-flat markdown-horizontal"
+       title="{% trans 'Horizontal Line' %} (Ctrl+H)">
+        <i class="material-icons">remove</i>
+    </a>
+
+
+    <a class="dropdown-trigger btn-flat" title="{% trans 'Heading' %}" data-target="dropdown-heading-{{ field_name }}">
+        <i class="material-icons">format_size</i>
+    </a>
+    <ul class="dropdown-content" id="dropdown-heading-{{ field_name }}">
+        <li>
+            <a class=" markdown-selector markdown-h1" title="{% trans 'H' %} 1 (Ctrl+Alt+1)">{% trans 'H' %} 1</a>
+        </li>
+        <li>
+            <a class=" markdown-selector markdown-h2" title="{% trans 'H' %} 2 (Ctrl+Alt+2)">{% trans 'H' %} 2</a>
+        </li>
+        <li>
+            <a class=" markdown-selector markdown-h3" title="{% trans 'H' %} 3 (Ctrl+Alt+3)">{% trans 'H' %} 3</a>
+        </li>
+    </ul>
+
+
+    <a class="dropdown-trigger btn-flat" title="{% trans 'Pre or Code' %}"
+       data-target="dropdown-precode-{{ field_name }}">
+        <i class="material-icons">code</i>
+    </a>
+
+    <ul class="dropdown-content" id="dropdown-precode-{{ field_name }}">
+        <li>
+            <a class="item markdown-selector markdown-pre" title="{% trans 'Pre' %} (Ctrl+Alt+P)">{% trans 'Pre' %}</a>
+        </li>
+        <li>
+            <a class="item markdown-selector markdown-code"
+               title="{% trans 'Code' %} (Ctrl+Alt+C)">{% trans 'Code' %}</a>
+        </li>
+    </ul>
+
+
+    <a class="markdown-selector btn-flat markdown-blockquote"
+       title="{% trans 'Quote' %} (Ctrl+Q)">
+        <i class="material-icons">format_quote</i>
+    </a>
+    <a class="markdown-selector btn-flat markdown-unordered-list"
+       title="{% trans 'Unordered List' %} (Ctrl+U)">
+        <i class="material-icons">format_list_bulleted</i>
+    </a>
+    <a class="markdown-selector btn-flat markdown-ordered-list"
+       title="{% trans 'Ordered List' %} (Ctrl+Shift+O)">
+        <i class="material-icons">format_list_numbered</i>
+    </a>
+
+    <a class="markdown-selector btn-flat markdown-link" title="{% trans 'URL/Link' %} (Ctrl+L)">
+        <i class="material-icons">insert_link</i>
+    </a>
+    {#    <a class="markdown-selector btn-flat markdown-image-link"#}
+    {#       title="{% trans 'Insert Image Link' %} (Ctrl+Shift+I)">#}
+    {#        <i class="material-icons">insert_photo</i>#}
+    {#    </a>#}
+    {#    <a class="markdown-selector btn-flat markdown-image-upload"#}
+    {#       title="{% trans 'Upload an Image' %}">#}
+    {#        <i class="material-icons">file_upload</i>#}
+    {#        <input name="markdown-image-upload" class="button" type="file" accept="image/*"#}
+    {#               title="{% trans 'Upload an Image' %}">#}
+    {#    </a>#}
+    {#    <a class="markdown-selector btn-flat markdown-emoji" title="{% trans 'Insert Emoji' %}">#}
+    {#        <i class="material-icons">face</i>#}
+    {#    </a>#}
+    {#    <a class="markdown-selector btn-flat markdown-direct-mention"#}
+    {#       title="{% trans 'Direct Mention a User' %} (Ctrl+M)">#}
+    {#        <i class="material-icons">people</i>#}
+    {#    </a>#}
+
+    <a class="markdown-selector btn-flat markdown-toggle-maximize"
+       title="{% trans 'Full Screen' %}">
+        <i class="material-icons maximize icon"></i>
+    </a>
+    <a class="markdown-selector btn-flat markdown-help modal-trigger"
+       title="{% trans 'Markdown Guide (Help)' %}" href="#modal-{{ field_name }}">
+        <i class="material-icons">help</i>
+    </a>
+</div>
diff --git a/biscuit/core/templates/partials/footer.html b/biscuit/core/templates/partials/footer.html
new file mode 100644
index 0000000000000000000000000000000000000000..b682bd0cfd699c9148522b80308bd3e1e2c62d66
--- /dev/null
+++ b/biscuit/core/templates/partials/footer.html
@@ -0,0 +1,76 @@
+{% load static %}
+
+
+<footer class="page-footer primary-color">
+    <div class="container">
+        <div class="row no-margin footer-row-large">
+            <div class="col l6 s12 no-pad-left height-inherit">
+                <p class="white-text valign-bot">
+                    <a href="{% url "about" %}" class="blue-text text-lighten-4">Über Schoolapps · Entwickler, Copyright
+                        und Lizenz
+                    </a>
+                </p>
+            </div>
+            <div class="col xl15 l6 offset-xl01 s12 no-pad-right">
+                <ul class="no-margin right">
+                    <a class="blue-text text-lighten-4 btn-flat no-pad-left" href="https://katharineum-zu-luebeck.de"><i
+                            class="material-icons footer-icon left">home</i> Homepage
+                    </a>
+                    <a class="blue-text text-lighten-4 btn-flat" href="https://forum.katharineum.de"><i
+                            class="material-icons footer-icon left">account_balance</i> Forum
+                    </a>
+                    <a class="blue-text text-lighten-4 btn-flat" href="https://nimbus.katharineum.de"><i
+                            class="material-icons footer-icon left">cloud</i> Nimbus
+                    </a>
+                    <a class="blue-text text-lighten-4 btn-flat no-pad-right" href="https://webmail.katharineum.de"><i
+                            class="material-icons footer-icon left">email</i> Webmail
+                    </a>
+                </ul>
+            </div>
+        </div>
+        <div class="row no-margin footer-row-small">
+            <span class="white-text make-it-higher">
+                <a href="{% url "about" %}" class="blue-text text-lighten-4">Über Schoolapps · Entwickler, Copyright
+                    und Lizenz
+                </a>
+            </span>
+            <ul class="no-margin footer-ul">
+                <a class="blue-text text-lighten-4 btn-flat no-pad-left" href="https://katharineum-zu-luebeck.de"><i
+                        class="material-icons footer-icon left">home</i> Homepage
+                </a>
+                <a class="blue-text text-lighten-4 btn-flat" href="https://forum.katharineum.de"><i
+                        class="material-icons footer-icon left">account_balance</i> Forum
+                </a>
+                <a class="blue-text text-lighten-4 btn-flat" href="https://nimbus.katharineum.de"><i
+                        class="material-icons footer-icon left">cloud</i> Nimbus
+                </a>
+                <a class="blue-text text-lighten-4 btn-flat no-pad-right" href="https://webmail.katharineum.de"><i
+                        class="material-icons footer-icon left">email</i> Webmail
+                </a>
+            </ul>
+        </div>
+    </div>
+    <div class="footer-copyright">
+        <div class="container">
+            <span class="left">Version {{ VERSION }} &middot; {{ COPYRIGHT_SHORT }}</span>
+            <span class="right">
+                <span id="doit"></span>
+
+                <a class="blue-text text-lighten-4" href="https://katharineum-zu-luebeck.de/impressum/">Impressum</a>
+                ·
+                <a class="blue-text text-lighten-4" href="https://katharineum-zu-luebeck.de/datenschutzerklaerung/">
+                    Datenschutzerklärung
+                </a>
+            </span>
+        </div>
+    </div>
+</footer>
+
+<!---------------->
+<!-- JavaScript (jquery v. 3.4.1.slim)-->
+<!---------------->
+<script src="{% static 'common/manup.min.js' %}"></script>
+<script src="{% static 'js/materialize.min.js' %}"></script>
+<script src="{% static 'common/helper.js' %}"></script>
+</body>
+</html>
diff --git a/biscuit/core/templates/partials/header.html b/biscuit/core/templates/partials/header.html
new file mode 100644
index 0000000000000000000000000000000000000000..8fe9a3f0cf2afef973a3009e3daeef4cd1cba4ce
--- /dev/null
+++ b/biscuit/core/templates/partials/header.html
@@ -0,0 +1,441 @@
+{% load static %}
+{% load pwa %}
+{% load url_name %}
+
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <meta name="description" content="Selbst programmierte Anwendungen für den Schullaltag am Katharineum zu Lübeck">
+    <title>SchoolApps – Katharineum zu Lübeck</title>
+
+    <!-- Android  -->
+    <meta name="theme-color" content="#da1f3d">
+    <meta name="mobile-web-app-capable" content="yes">
+
+    <!-- iOS -->
+    <meta name="apple-mobile-web-app-title" content="SchoolApps">
+    <meta name="apple-mobile-web-app-capable" content="yes">
+    <meta name="apple-mobile-web-app-status-bar-style" content="default">
+
+    <!-- Windows  -->
+    <meta name="msapplication-navbutton-color" content="#da1f3d">
+    <meta name="msapplication-TileColor" content="#da1f3d">
+    <meta name="msapplication-TileImage" content="ms-icon-144x144.png">
+    <meta name="msapplication-config" content="browserconfig.xml">
+
+    <!-- Pinned Sites  -->
+    <meta name="application-name" content="SchoolApps">
+    <meta name="msapplication-tooltip" content="SchoolApps">
+    <meta name="msapplication-starturl" content="/">
+
+    <!-- Tap highlighting  -->
+    <meta name="msapplication-tap-highlight" content="no">
+
+    <!-- UC Mobile Browser  -->
+    <meta name="full-screen" content="yes">
+    <meta name="browsermode" content="application">
+
+
+    <!-- Main Link Tags  -->
+    <link href="{% static "icons/favicon_16.png" %}" rel="icon" type="image/png" sizes="16x16">
+    <link href="{% static "icons/favicon_32.png" %}" rel="icon" type="image/png" sizes="32x32">
+    <link href="{% static "icons/favicon_48.png" %}" rel="icon" type="image/png" sizes="48x48">
+
+    <!-- iOS  -->
+    <!-- non-retina iPad iOS 7 -->
+    <link rel="apple-touch-icon" href="{% static "icons/apple_76.png" %}" sizes="76x76">
+    <!-- retina iPhone vor iOS 7 -->
+    <link rel="apple-touch-icon" href="{% static "icons/apple_114.png" %}" sizes="114x114">
+    <!-- retina iPad iOS 7 -->
+    <link rel="apple-touch-icon" href="{% static "icons/apple_152.png" %}" sizes="152x152">
+    <!-- retina iPad iOS 7 für iPhone 6 Plus -->
+    <link rel="apple-touch-icon" href="{% static "icons/apple_180.png" %}" sizes="180x180">
+
+
+    <!-- Pinned Tab  -->
+    {#    <link href="path/to/icon.svg" rel="mask-icon" size="any" color="red">#}
+
+    <!-- Android  -->
+    <link href="{% static "icons/android_192.png" %}" rel="icon" sizes="192x192">
+
+    <!-- Others -->
+    {#    <link href="favicon.icon" rel="shortcut icon" type="image/x-icon">#}
+
+
+    <!-- Favicon -->
+    <link rel="shortcut icon" type="image/x-icon" href="{% static 'common/favicon.ico' %}">
+
+    <!-- PWA -->
+    {% progressive_web_app_meta %}
+
+    <!--------->
+    <!-- CSS -->
+    <!--------->
+    <link href="{% static 'css/materialdesignicons-webfont/material-icons.css' %}" rel="stylesheet">
+    <link rel="stylesheet" type="text/css" media="screen"
+          href="{% static 'css/materialize.min.css' %}">
+    <link rel="stylesheet" type="text/css" href="{% static 'common/style.css' %}">
+    <script src="{% static 'js/jquery/jquery-3.3.1.min.js' %}"></script>
+
+
+    <!-- location (for "active" in sidenav -->
+    <meta name="active-loaction" content="{{ request|url_name }}">
+
+    {% if martor %}
+        <link href="{% static 'plugins/css/ace.min.css' %}" type="text/css" media="all" rel="stylesheet"/>
+        <link href="{% static 'plugins/css/resizable.min.css' %}" type="text/css" media="all" rel="stylesheet"/>
+        <link href="{% static 'martor/css/martor.min.css' %}" type="text/css" media="all" rel="stylesheet"/>
+    {% endif %}
+</head>
+<body>
+<header>
+    <!-- Menu button (sidenav) -->
+    <div class="container">
+        <a href="#" data-target="slide-out" class="top-nav sidenav-trigger hide-on-large-only">
+            <i class="material-icons">menu</i>
+        </a>
+    </div>
+
+    <!-- Nav bar (logged in as, logout) -->
+    <nav class="primary-color">
+        <a class="brand-logo" href="/">SchoolApps</a>
+
+        <div class="nav-wrapper">
+            <ul id="nav-mobile" class="right hide-on-med-and-down">
+                {% if user.is_authenticated %}
+                    <li>Angemeldet als {{ user.get_username }}</li>
+                    <li>
+                        <a href="{% url 'logout' %}">Abmelden <i class="material-icons right">exit_to_app</i></a>
+                    </li>
+                {% endif %}
+            </ul>
+        </div>
+    </nav>
+
+    <div id="print-header" class="row">
+        <div class="col s6 logo">
+            <img src="{% static 'common/logo.png' %}">
+        </div>
+        <div class="col s6 right-align">
+            <a href="/"><strong>SchoolApps</strong></a>
+            <br>
+            Katharineum zu Lübeck
+        </div>
+    </div>
+
+    <!-- Main nav (sidenav) -->
+    <ul id="slide-out" class="sidenav sidenav-fixed">
+        <li class="logo">
+            <a id="logo-container" href="/" class="brand-logo">
+                <img src="{% static 'common/logo.png' %}" alt="Logo des Katharineums">
+            </a>
+        </li>
+
+        {% if user.is_authenticated %}
+            <li class="url-dashboard">
+                <a href="{% url 'dashboard' %}">
+                    <i class="material-icons">home</i> Dashboard
+                </a>
+            </li>
+            <li>
+                <div class="divider"></div>
+            </li>
+
+            <li class="no-padding">
+            <ul class="collapsible collapsible-accordion">
+
+            {% if perms.aub.apply_for_aub or perms.aub.check1_aub or perms.aub.check2_aub %}
+                <li class="bold url-aub_index url-aub_details url-ab_edit url-aub_apply_for urlaub_applied_for">
+                    <a class="collapsible-header waves-effect waves-primary" href="{% url 'aub_index' %}"><i
+                            class="material-icons">business_center</i>
+                        Unterrichtsbefreiungen
+                    </a>
+                    <div class="collapsible-body">
+                        <ul>
+                            {% if perms.aub.check1_aub %}
+                                <li class="url-aub_check1">
+                                    <a href="{% url 'aub_check1' %}"><i class="material-icons">done</i> Anträge
+                                        genehmigen 1
+                                    </a>
+                                </li>
+                            {% endif %}
+                            {% if perms.aub.check2_aub %}
+                                <li class="url-aub_check2">
+                                    <a href="{% url 'aub_check2' %}"><i class="material-icons">done_all</i>
+                                        Anträge
+                                        genehmigen 2
+                                    </a>
+                                </li>
+                            {% endif %}
+                            {% if perms.aub.view_archive %}
+                                <li class="url-aub_archive">
+                                    <a href="{% url 'aub_archive' %}"><i class="material-icons">archive</i>
+                                        Archiv
+                                    </a>
+                                </li>
+                            {% endif %}
+                        </ul>
+                    </div>
+                </li>
+
+                <li>
+                    <div class="divider"></div>
+                </li>
+            {% endif %}
+            {% if perms.fibu.request_booking  or perms.fibu.manage_booking or perms.fibu.manage_costcenter or perms.fibu.manage.account %}
+                <li class="bold url-fibu_index url-fibu_bookings_user_edit">
+                    <a class="collapsible-header waves-effect waves-primary" href="{% url 'fibu_index' %}"><i
+                            class="material-icons">euro_symbol</i>
+                        Finanzen
+                    </a>
+                    <div class="collapsible-body">
+                        <ul>
+                            {% if perms.fibu.check_booking %}
+                                <li class="url-fibu_bookings_check">
+                                    <a href="{% url 'fibu_bookings_check' %}"><i class="material-icons">done_all</i>Anträge
+                                        prüfen
+                                    </a>
+                                </li>
+                            {% endif %}
+                            {% if perms.fibu.manage_booking %}
+                                <li class="url-fibu_bookings url-fibu_bookings_edit url-fibu_bookings_new">
+                                    <a href="{% url 'fibu_bookings' %}"><i class="material-icons">receipt</i>Buchungen
+                                    </a>
+                                </li>
+                            {% endif %}
+                            {% if perms.fibu.manage_costcenter %}
+                                <li class="url-fibu_cost_centers url-fibu_cost_centers_edit">
+                                    <a href="{% url 'fibu_cost_centers' %}"><i class="material-icons">done</i>Kostenstellen
+                                    </a>
+                                </li>
+                                <li class="url-fibu_accounts url-fibu_accounts_edit">
+                                    <a href="{% url 'fibu_accounts' %}"><i class="material-icons">done</i>Buchungskonten
+                                    </a>
+                                </li>
+                            {% endif %}
+                            {% if perms.fibu.manage_booking %}
+                                <li class="url-fibu_reports url-fibu_reports_expenses">
+                                    <a href="{% url 'fibu_reports' %}"><i class="material-icons">list</i>Berichte</a>
+                                </li>
+                            {% endif %}
+                        </ul>
+                    </div>
+                </li>
+                <li>
+                    <div class="divider"></div>
+                </li>
+            {% endif %}
+
+            {% if perms.timetable.show_plan %}
+                <li class="bold">
+                    <a class="collapsible-header waves-effect waves-primary"><i class="material-icons">school</i>
+                        Stundenplan
+                    </a>
+                    <div class="collapsible-body">
+                        <ul>
+                            <li class="url-timetable_my_plan">
+                                <a href="{% url 'timetable_my_plan' %}" style="padding-right: 10px;">
+                                    <i class="material-icons">person</i> Mein Plan
+                                    <span class="badge new primary-color sidenav-badge">SMART PLAN</span>
+                                </a>
+                            </li>
+                            {#                <li>#}
+                            {#                    <a href="{% url 'timetable_admin_all' %}">#}
+                            {#                        <i class="material-icons">grid_on</i> Alle Pläne#}
+                            {#                    </a>#}
+                            {#                </li>#}
+                            <li class="url-timetable_quicklaunch url-timetable_smart_plan url-timetable_regular_plan url-timetable_smart_plan_week">
+                                <a href="{% url 'timetable_quicklaunch' %}">
+                                    <i class="material-icons">grid_on</i> Alle Pläne
+                                </a>
+                            </li>
+                            <li class="url-timetable_substitutions_date url-timetable_substitutions">
+                                <a href="{% url 'timetable_substitutions' %}">
+                                    <i class="material-icons">update</i> Vertretungsplan
+                                </a>
+                            </li>
+                            {% if perms.timetable.view_hint %}
+                                <li class="url-timetable_hints url-timetable_add_hint url-timetable_edit_hint url-timetable_delete_hint">
+                                    <a href="{% url 'timetable_hints' %}">
+                                        <i class="material-icons">announcement</i> Hinweismanagement
+                                    </a>
+                                </li>
+                            {% endif %}
+                            {% if perms.debug.can_view_debug_log %}
+                                <li class="url-debug_logs url-debug">
+                                    <a href="{% url 'debug_logs' %}">
+                                        <i class="material-icons">error</i> Debuggingtool
+                                    </a>
+                                </li>
+                            {% endif %}
+                        </ul>
+                    </div>
+                </li>
+
+                <li>
+                    <div class="divider"></div>
+                </li>
+            {% endif %}
+
+            <li>
+                <a href="{% url 'menu_show_current' %}" target="_blank">
+                    <i class="material-icons">picture_as_pdf</i> Aktueller Speiseplan
+                </a>
+            </li>
+
+            {% if perms.menu.add_menu %}
+                <li class="url-menu_index url-menu_upload url-menu_index_msg">
+                    <a href="{% url 'menu_index' %}">
+                        <i class="material-icons">restaurant_menu</i> Speiseplan hochladen
+                    </a>
+                </li>
+            {% endif %}
+
+            <li>
+                <div class="divider"></div>
+            </li>
+
+            <li class="bold">
+                <a class="collapsible-header waves-effect waves-primary"><i class="material-icons">help</i> Support</a>
+                <div class="collapsible-body">
+                    <ul>
+                        <li class="url-rebus">
+                            <a href="{% url 'rebus' %}">
+                                <i class="material-icons">bug_report</i> Fehler melden
+                            </a>
+                        </li>
+                        <li class="url-feedback">
+                            <a href="{% url 'feedback' %}">
+                                <i class="material-icons">feedback</i> Feedback
+                            </a>
+                        </li>
+                        <li class="url-faq">
+                            <a href="{% url 'faq' %}">
+                                <i class="material-icons">question_answer</i>FAQ
+                            </a>
+                        </li>
+                    </ul>
+                </div>
+            </li>
+        {% endif %}
+
+        {% if not user.is_authenticated %}
+            <li class="url-faq">
+                <a href="{% url 'faq' %}">
+                    <i class="material-icons">question_answer</i>FAQ
+                </a>
+            </li>
+        {% endif %}
+
+        {% if user.is_authenticated %}
+            {% if user.is_superuser %}
+                <li>
+                    <div class="divider"></div>
+                </li>
+                <li class="bold">
+                    <a class="collapsible-header waves-effect waves-primary"><i class="material-icons">security</i>Administration
+                    </a>
+                    <div class="collapsible-body">
+                        <ul>
+                            <li id="tools">
+                                <a href="{% url "tools" %}">
+                                    <i class="material-icons">build</i> Tools
+                                </a>
+                            </li>
+                            <li>
+                                <a href="/admin/">
+                                    <i class="material-icons">dashboard</i> Django-Administration
+                                </a>
+                            </li>
+                            <li>
+                                <a href="/settings/">
+                                    <i class="material-icons">settings</i> Einstellungen
+                                </a>
+                            </li>
+                        </ul>
+                    </div>
+                </li>
+            {% endif %}
+
+            <li>
+                <div class="divider"></div>
+            </li>
+
+            <li>
+                <a href="{% url 'logout' %}">
+                    <i class="material-icons">exit_to_app</i> Abmelden
+                </a>
+            </li>
+
+            </ul>
+            </li>
+        {% else %}
+
+            <li class="url-login">
+                <a href="{% url 'login' %}">
+                    <i class="material-icons">lock_open</i> Anmelden
+                </a>
+            </li>
+        {% endif %}
+    </ul>
+</header>
+{#<header class="alert success">#}
+{#    <p>#}
+{#        <i class="material-icons left">info</i>#}
+{#        Du befindest dich in der ersten veröffentlichten Version von SchoolApps. Daher kann es immer mal wieder noch zu#}
+{#        bislang unvorhergesehenen#}
+{#        Problemen kommen. Es würde uns sehr helfen, wenn du uns dann über#}
+{#        <a href="mailto:support@katharineum.de">support@katharineum.de</a>#}
+{#        schreibst oder die in SchoolApps integrierten#}
+{#        <a href="{% url 'rebus' %}">Fehlermelde-</a>#}
+{#        und#}
+{#        <a href="{% url 'feedback' %}">Feedbackfunktionen</a>#}
+{#        nutzt.#}
+{#    </p>#}
+{#</header>#}
+
+{% if messages %}
+    <header>
+        {% for message in messages %}
+            <div class="alert {% if message.tags %}{{ message.tags }}{% else %}info{% endif %}">
+                <p>
+                    {% if message.tags == "success" %}
+                        <i class="material-icons left">check_circle</i>
+                    {% elif  message.tags == "info" %}
+                        <i class="material-icons left">info</i>
+                    {% elif  message.tags == "warning" %}
+                        <i class="material-icons left">warning</i>
+                    {% elif  message.tags == "error" %}
+                        <i class="material-icons left">error</i>
+                    {% endif %}
+                    {{ message }}
+                </p>
+            </div>
+        {% endfor %}
+    </header>
+{% endif %}
+
+{% if user.is_authenticated %}
+    <div class="fixed-action-btn">
+        <a class="btn-floating btn-large green">
+            <i class="large material-icons">help</i>
+        </a>
+        <ul>
+            <li>
+                <a class="btn-floating tooltipped red" data-position="left" data-tooltip="Fehler melden"
+                   href="{% url 'rebus' %}"><i class="material-icons">bug_report</i></a>
+            </li>
+            <li>
+                <a class="btn-floating tooltipped yellow darken-1" data-position="left" data-tooltip="FAQ"
+                   href="{% url 'faq' %}"><i class="material-icons">question_answer</i></a>
+            </li>
+            <li>
+                <a class="btn-floating tooltipped blue" data-position="left" data-tooltip="Frage stellen"
+                   href="{% url 'ask-faq' %}"><i class="material-icons">chat</i></a>
+            </li>
+        </ul>
+    </div>
+{% endif %}
diff --git a/biscuit/core/templates/partials/paper/footer.html b/biscuit/core/templates/partials/paper/footer.html
new file mode 100644
index 0000000000000000000000000000000000000000..1dc3a88b8281509e4eb8d18212b4513d32189845
--- /dev/null
+++ b/biscuit/core/templates/partials/paper/footer.html
@@ -0,0 +1,32 @@
+{% load static %}
+<footer>
+    <div class="left">
+        Katharineum zu Lübeck
+    </div>
+
+    <div class="right">
+        Umsetzung: {{ COPYRIGHT_SHORT }}
+    </div>
+</footer>
+</div>
+</td>
+</tr>
+</tbody>
+<tfoot>
+<tr class="no-border">
+    <td>
+        <div class="footer-space">&nbsp;</div>
+    </td>
+</tr>
+</tfoot>
+</table>
+</main>
+
+<!---------------->
+<!-- JavaScript (jquery v. 3.4.1.slim)-->
+<!---------------->
+<script src="{% static 'common/manup.min.js' %}"></script>
+<script src="{% static 'js/materialize.min.js' %}"></script>
+<script src="{% static 'common/helper.js' %}"></script>
+</body>
+</html>
diff --git a/biscuit/core/templates/partials/paper/header.html b/biscuit/core/templates/partials/paper/header.html
new file mode 100644
index 0000000000000000000000000000000000000000..845672ab118cc0112332f292b7899661a6b64ebd
--- /dev/null
+++ b/biscuit/core/templates/partials/paper/header.html
@@ -0,0 +1,144 @@
+{% load static %}
+{% load pwa %}
+{% load url_name %}
+
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <meta name="description" content="Selbst programmierte Anwendungen für den Schullaltag am Katharineum zu Lübeck">
+    <title>SchoolApps – Katharineum zu Lübeck</title>
+
+    <!--------->
+    <!-- CSS -->
+    <!--------->
+    <link href="{% static 'css/materialdesignicons-webfont/material-icons.css' %}" rel="stylesheet">
+    <link rel="stylesheet" type="text/css" href="{% static 'css/materialize.min.css' %}">
+    <link rel="stylesheet" type="text/css" href="{% static 'css/paper.css' %}">
+    <link rel="stylesheet" type="text/css" href="{% static 'common/style.css' %}">
+    <script src="{% static 'js/jquery/jquery-3.3.1.min.js' %}"></script>
+
+
+    <style>
+        body {
+            font-family: Cabin, sans-serif;
+        }
+
+        @page {
+            size: A4;
+            padding: 30mm;
+        }
+
+        header {
+            display: block;
+            width: 190mm;
+        }
+
+
+        #print-header {
+            display: block !important;
+            border-bottom: 1px solid;
+            margin-bottom: 0;
+            height: 22mm;
+            background: white;
+        }
+
+        header, main, footer {
+            margin: 0;
+        }
+
+        #print-header .col.right-align {
+            padding: 15px;
+        }
+
+        .sheet {
+            padding: 10mm;
+        }
+
+
+        .header-space, .footer-space {
+            height: 0;
+        }
+
+        .print-layout-table td {
+            padding: 0;
+        }
+
+        .print-layout-table .no-border {
+            border: 0;
+        }
+
+
+        footer {
+            margin-top: 5mm;
+            text-align: center;
+            width: 190mm;
+
+        }
+
+        header .row, header .col {
+            padding: 0 !important;
+            margin: 0 !important;
+        }
+
+        @media print {
+            .header-space {
+                height: 32mm;
+            }
+
+            .footer-space {
+                height: 20mm
+            }
+
+            header, footer {
+                height: 22mm;
+            }
+
+            header {
+                position: fixed;
+                top: 10mm;
+            }
+
+            footer {
+                position: fixed;
+                bottom: 0;
+            }
+
+            @page {
+                @bottom-center {
+                    content: "Seite " counter(page) " von " counter(pages);
+                }
+            }
+        }
+    </style>
+</head>
+<body class="A4">
+
+
+<div style="margin-top: -10mm;"></div>
+<main class="sheet infinitive">
+    <table class="print-layout-table">
+        <thead>
+        <tr class="no-border">
+            <td>
+                <div class="header-space">&nbsp;</div>
+            </td>
+        </tr>
+        </thead>
+        <tbody>
+        <tr class="no-border">
+            <td>
+                <div class="content">
+                    <header>
+                        <div id="print-header" class="row">
+                            <div class="col s6 logo">
+                                <img src="{% static 'common/logo.png' %}">
+                            </div>
+                            <div class="col s6 right-align">
+                                <h5>SchoolApps</h5>
+                                {% now "j. F Y H:i" %}
+                            </div>
+                        </div>
+                    </header>
diff --git a/biscuit/core/templates/registration/logged_out.html b/biscuit/core/templates/registration/logged_out.html
new file mode 100644
index 0000000000000000000000000000000000000000..48a7d1535dfb6071d29e3f2fee1fd2247650b763
--- /dev/null
+++ b/biscuit/core/templates/registration/logged_out.html
@@ -0,0 +1,8 @@
+{% include 'partials/header.html' %}
+
+<main>
+    <p class="flow-text">Du bist nun abgemeldet.</p>
+    <a href="{% url 'login' %}">Wieder anmelden?</a>
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/biscuit/core/templatetags/apps.py b/biscuit/core/templatetags/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f48c994917130290f2c4400fca753c4df11b06f
--- /dev/null
+++ b/biscuit/core/templatetags/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class TemplatetagsConfig(AppConfig):
+    name = 'templatetags'
diff --git a/biscuit/core/templatetags/templatetags/__init__.py b/biscuit/core/templatetags/templatetags/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/biscuit/core/templatetags/templatetags/copy_filter.py b/biscuit/core/templatetags/templatetags/copy_filter.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ee6181b430174b1f8dec5eff3276531f7e7e44b
--- /dev/null
+++ b/biscuit/core/templatetags/templatetags/copy_filter.py
@@ -0,0 +1,8 @@
+import copy as copylib
+
+from django import template
+
+register = template.Library()
+
+register.filter("copy", copylib.copy)
+register.filter("deepcopy", copylib.deepcopy)
diff --git a/biscuit/core/templatetags/templatetags/msg_box.py b/biscuit/core/templatetags/templatetags/msg_box.py
new file mode 100644
index 0000000000000000000000000000000000000000..216883cc1f64bc9539cd2bea1941c0c066552fe3
--- /dev/null
+++ b/biscuit/core/templatetags/templatetags/msg_box.py
@@ -0,0 +1,6 @@
+from django.template.loader_tags import register
+
+
+@register.inclusion_tag("components/msgbox.html")
+def msg_box(msg, status="success", icon="info"):
+    return {"msg": msg, "status": status, "icon": icon}
diff --git a/biscuit/core/templatetags/templatetags/tex.py b/biscuit/core/templatetags/templatetags/tex.py
new file mode 100644
index 0000000000000000000000000000000000000000..333c3d31d819eb4c3b41ef6513d3b2b9df3ff30e
--- /dev/null
+++ b/biscuit/core/templatetags/templatetags/tex.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+"""
+Django filters, needed when creating LaTeX files with the django template language
+
+Written by Rocco Schulz (http://is-gr8.com/), modified by SchoolApps-Team
+"""
+from django.template.defaultfilters import stringfilter, register
+from django.template.loader import render_to_string
+
+
+@register.filter
+@stringfilter
+def brackets(value):
+    """
+    surrounds the value with { }
+    You have to use this filter whenever you would need something like
+    {{{ field }}} in a template.
+    """
+    return "{%s}" % value
+
+
+REPLACEMENTS = {
+    "§": "\\textsection{}",
+    "$": "\\textdollar{}",
+    "LaTeX": "\\LaTeX \\ ",
+    " TeX": " \\TeX \\ ",
+    "€": "\\euro",
+    ">": "$>$",
+    "<": "$<$"
+}
+
+ESCAPES = ("&", "{", "}", "%")
+
+
+@register.filter
+@stringfilter
+def texify(value):
+    """
+    escapes/replaces special character with appropriate latex commands
+    """
+    tex_value = []
+
+    # escape special symbols
+    for char in value:
+        tex_value.append("%s" % ("\\%s" % char if char in ESCAPES else char))
+    tex_value = "".join(tex_value)
+
+    # replace symbols / words with latex commands
+    for key, value in REPLACEMENTS.items():
+        tex_value = tex_value.replace(key, value)
+
+    return "%s" % tex_value
diff --git a/biscuit/core/templatetags/templatetags/url_name.py b/biscuit/core/templatetags/templatetags/url_name.py
new file mode 100644
index 0000000000000000000000000000000000000000..20f63c673b00f0d736652db62937f9dc68947db2
--- /dev/null
+++ b/biscuit/core/templatetags/templatetags/url_name.py
@@ -0,0 +1,15 @@
+from django.urls import resolve
+from django import template
+
+register = template.Library()
+
+
+def get_url_name(request):  # Only one argument.
+    """Gets url_name"""
+    try:
+        return resolve(request.path_info).url_name
+    except Exception as e:
+        return e
+
+
+register.filter("url_name", get_url_name)
diff --git a/biscuit/core/userinformation.py b/biscuit/core/userinformation.py
new file mode 100644
index 0000000000000000000000000000000000000000..69bd84938444dcfe31aa44e4259d7c6d7d84672f
--- /dev/null
+++ b/biscuit/core/userinformation.py
@@ -0,0 +1,64 @@
+import re
+
+
+class UserInformation:
+    OTHER = 0
+    TEACHER = 1
+    STUDENT = 2
+
+    @staticmethod
+    def regexr(regex, groups):
+        reg = re.compile(regex)
+        return reg.findall("\n".join(groups))
+
+    @staticmethod
+    def user_groups(user):
+        raw_groups = user.groups.all()
+        groups = [group.name for group in raw_groups]
+        # print(groups)
+        return groups
+
+    @staticmethod
+    def user_type(user):
+        groups = UserInformation.user_groups(user)
+        if "teachers" in groups:
+            return UserInformation.TEACHER
+        elif "students" in groups:
+            return UserInformation.STUDENT
+        else:
+            return UserInformation.OTHER
+
+    @staticmethod
+    def _user_type_formatted(user_type):
+        return "Lehrer" if user_type == UserInformation.TEACHER else (
+            "Schüler" if user_type == UserInformation.STUDENT else "Sonstiges Mitglied")
+
+    @staticmethod
+    def user_type_formatted(user):
+        user_type = UserInformation.user_type(user)
+        return UserInformation._user_type_formatted(user_type)
+
+    @staticmethod
+    def user_classes(user):
+        groups = UserInformation.user_groups(user)
+        classes = UserInformation.regexr(r"class_(\w{1,3})", groups)
+        return classes
+
+    @staticmethod
+    def user_courses(user):
+        groups = UserInformation.user_groups(user)
+        classes = UserInformation.regexr(r"course_(.{1,10})", groups)
+        return classes
+
+    @staticmethod
+    def user_subjects(user):
+        groups = UserInformation.user_groups(user)
+        classes = UserInformation.regexr(r"subject_(\w{1,3})", groups)
+        return classes
+
+    @staticmethod
+    def user_has_wifi(user):
+        groups = UserInformation.user_groups(user)
+        if "teachers" in groups or "students-wifi" in groups:
+            return True
+        return False
diff --git a/biscuit/core/util/helper.py b/biscuit/core/util/helper.py
new file mode 100644
index 0000000000000000000000000000000000000000..04b910255cffad5db90f9c4077b9016a21ae5b82
--- /dev/null
+++ b/biscuit/core/util/helper.py
@@ -0,0 +1,22 @@
+import os
+from uuid import uuid4
+
+from django.template.loader_tags import register
+
+
+def path_and_rename(instance, filename):
+    upload_to = 'menus'
+    ext = filename.split('.')[-1].lower()
+    # get filename
+    if instance.pk:
+        filename = '{}.{}'.format(instance.pk, ext)
+    else:
+        # set filename as random string
+        filename = '{}.{}'.format(uuid4().hex, ext)
+    # return the whole path to the file
+    return os.path.join(upload_to, filename)
+
+
+@register.inclusion_tag("components/msgbox.html")
+def msg_box(msg, status="success", icon="info"):
+    return {"msg": msg, "status": status, "icon": icon}
diff --git a/biscuit/core/util/network.py b/biscuit/core/util/network.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c81df3330006f4ba8f591615d1c3aa06d939bcd
--- /dev/null
+++ b/biscuit/core/util/network.py
@@ -0,0 +1,167 @@
+import re
+
+import requests
+from django.utils import timezone, formats
+from ics import Calendar
+from requests import RequestException
+
+from dashboard import settings
+from dashboard.caches import LATEST_ARTICLE_CACHE, CURRENT_EVENTS_CACHE
+
+WP_DOMAIN: str = "https://katharineum-zu-luebeck.de"
+
+VS_COMPOSER_REGEX = re.compile(r"\[[\w\s\d!\"§$%&/()=?#'+*~’¸´`;,·.:…\-_–]*\]")
+
+
+def get_newest_articles(domain: str = WP_DOMAIN,
+                        limit: int = 5,
+                        author_whitelist: list = None,
+                        author_blacklist: list = None,
+                        category_whitelist: list = None,
+                        category_blacklist: list = None
+                        ):
+    """
+    This function returns the newest articles/posts of a WordPress site.
+
+    :param domain: The domain to get the newest posts from (for example https://wordpress.com). Don't put a slash (/) at the end!
+    :param limit: if 0: all posts will be shown, else nly the certain number
+    :param author_whitelist: If this list is filled, only articles which are written by one of this authors will be shown
+    :param author_blacklist: If the author's id (an integer) is in this list, the article won't be shown
+    :param category_whitelist: If this list is filled, only articles which are in one of this categories will be shown
+    :param category_blacklist: If the category's id (an integer) is in this list, the article won't be shown
+    :return: a list of the newest posts/articles
+    """
+    # Make mutable default arguments unmutable
+    if category_whitelist is None:
+        category_whitelist = []
+    if category_blacklist is None:
+        category_blacklist = []
+    if author_whitelist is None:
+        author_whitelist = []
+    if author_blacklist is None:
+        author_blacklist = []
+
+    suffix: str = "/wp-json/wp/v2/posts"
+    url: str = domain + suffix
+    try:
+        site: requests.request = requests.get(url, timeout=10)
+        data: dict = site.json()
+    except RequestException as e:
+        print("E", str(e))
+        return []
+    posts: list = []
+
+    for post in data:
+        if post["author"] not in author_blacklist:
+            if len(author_whitelist) > 0 and post["author"] not in author_whitelist:
+                continue
+
+            if post["categories"][0] not in category_blacklist:
+                if len(category_whitelist) > 0 and post["categories"][0] not in category_whitelist:
+                    continue
+
+                # Now get the link to the image
+                if post["_links"].get("wp:featuredmedia", False):
+                    media_site: requests.request = requests.get(post["_links"]["wp:featuredmedia"][0]["href"]).json()
+                    image_url: str = media_site["guid"]["rendered"]
+                else:
+                    image_url: str = ""
+
+                # Replace VS composer tags if activated
+                if settings.latest_article_settings.replace_vs_composer_stuff:
+                    excerpt = VS_COMPOSER_REGEX.sub("", post["excerpt"]["rendered"])
+                else:
+                    excerpt = post["excerpt"]["rendered"]
+
+                posts.append(
+                    {
+                        "title": post["title"]["rendered"],
+                        "short_text": excerpt,
+                        "link": post["link"],
+                        "image_url": image_url,
+                    }
+                )
+        if len(posts) >= limit and limit >= 0:
+            break
+
+    return posts
+
+
+@LATEST_ARTICLE_CACHE.decorator
+def get_newest_article_from_news(domain=WP_DOMAIN):
+    newest_articles: list = get_newest_articles(domain=domain, limit=1, category_whitelist=[1, 27])
+    if len(newest_articles) > 0:
+        return newest_articles[0]
+    else:
+        return None
+
+
+def get_current_events(calendar: Calendar, limit: int = 5) -> list:
+    """
+    Get upcoming events from calendar
+    :param calendar: The calendar object
+    :param limit: Count of events
+    :return: List of upcoming events
+    """
+    i: int = 0
+    events: list = []
+    for event in calendar.timeline.start_after(timezone.now()):
+        # Check for limit
+        if i >= limit:
+            break
+        i += 1
+
+        # Create formatted dates and times for begin and end
+        begin_date_formatted = formats.date_format(event.begin)
+        end_date_formatted = formats.date_format(event.end)
+        begin_time_formatted = formats.time_format(event.begin.time())
+        end_time_formatted = formats.time_format(event.end.time())
+
+        if event.begin.date() == event.end.date():
+            # Event is only on one day
+            formatted = begin_date_formatted
+
+            if not event.all_day:
+                # No all day event
+                formatted += " " + begin_time_formatted
+
+            if event.begin.time != event.end.time():
+                # Event has an end time
+                formatted += " – " + end_time_formatted
+
+        else:
+            # Event is on multiple days
+            if event.all_day:
+                # Event is all day
+                formatted = "{} – {}".format(begin_date_formatted, end_date_formatted)
+            else:
+                # Event has begin and end times
+                formatted = "{} {} – {} {}".format(begin_date_formatted, begin_time_formatted, end_date_formatted,
+                                                   end_time_formatted)
+
+        events.append({
+            "name": event.name,
+            "begin_timestamp": event.begin.timestamp,
+            "end_timestamp": event.end.timestamp,
+            "formatted": formatted
+        })
+
+    return events
+
+
+@CURRENT_EVENTS_CACHE.decorator
+def get_current_events_with_cal(limit: int = 5) -> list:
+    # Get URL
+    calendar_url: str = settings.current_events_settings.calendar_url
+    if calendar_url is None or calendar_url == "":
+        return []
+
+    # Get ICS
+    try:
+        calendar: Calendar = Calendar(requests.get(calendar_url, timeout=3).text)
+    except RequestException as e:
+        print("E", str(e))
+        return []
+
+    # Get events
+    return get_current_events(calendar, settings.current_events_settings.events_count)
diff --git a/pyproject.toml b/pyproject.toml
index cd457eb2db51690f9d007517fb8f73322a93e868..d5a86f6480a46521b8fabb5cc53108362a535c36 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,7 @@ packages = [
 readme = "README.rst"
 
 description = "BiscuIT School Information System (SIS) - Next Generation Platform"
-authors = ["Dominik George <dominik.george@teckids.org>", "mirabilos <thorsten.glaser@teckids.org>", "Tom Teichler <tom.teichler@teckids.org>", "Jonathan Weth <sis@jonathanweth.de>"]
+authors = ["Dominik George <dominik.george@teckids.org>", "Martin Gummi <martin.gummi@teckids.org>", "Julian Leucker <leuckeju@katharineum.de>", "mirabilos <thorsten.glaser@teckids.org>", "Frank Poetzsch-Heffter <p-h@katharineum.de>", "Tom Teichler <tom.teichler@teckids.org>", "Jonathan Weth <wethjo@katharineum.de>", "Hangzhi Yu <yuha@katharineum.de>"]
 license = "EUPL-1.2"
 homepage = "https://biscuit.edugit.io/"
 repository = "https://edugit.org/BiscuIT/BiscuIT-ng"
diff --git a/react/babel.sh b/react/babel.sh
new file mode 100755
index 0000000000000000000000000000000000000000..0a8185fa487cda7d479d31ccec1e698b9b471ae1
--- /dev/null
+++ b/react/babel.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+npx babel --watch src/ --out-dir ../schoolapps/static/js/ --presets react-app/prod
diff --git a/react/package.json b/react/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..f2e83345d6861242fe1349e2abdb0b7358653a4d
--- /dev/null
+++ b/react/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "react",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "babel-cli": "^6.26.0",
+    "babel-preset-react-app": "^3.1.2"
+  }
+}
diff --git a/react/src/dashboard.js b/react/src/dashboard.js
new file mode 100644
index 0000000000000000000000000000000000000000..9611e47d5ed6334353e5d9129b83d3c15998f574
--- /dev/null
+++ b/react/src/dashboard.js
@@ -0,0 +1,406 @@
+/*** source for biscuit/core/static/js/dashboard.js ***/
+
+const REFRESH_TIME = 15;
+
+// function WithCheckCircleIcon(props) {
+//     return <div className={"col s12"}>
+//         <i className={"material-icons left green-text"}>check_circle</i>
+//         {props.children}
+//     </div>
+// }
+
+class Dashboard extends React.Component {
+    constructor() {
+        super();
+        this.state = {
+            refreshIn: REFRESH_TIME,
+            isLoading: true,
+            networkProblems: false
+        };
+    }
+
+    updateRefreshTime = () => {
+        if (this.state.refreshIn >= 1) {
+            if (this.state.timeout) {
+                window.clearTimeout(this.state.timeout);
+            }
+            const timeout = window.setTimeout(this.updateRefreshTime, 1000);
+            this.setState({refreshIn: this.state.refreshIn - 1, timeout: timeout});
+        } else {
+            this.updateData();
+        }
+    };
+
+    updateData = () => {
+        if (this.state.networkProblems) {
+            this.setState({isLoading: true, networkProblems: false});
+        }
+
+        const that = this;
+        $.getJSON(API_URL, (data) => {
+            console.log(data);
+            if (data) {
+                that.setState({...data, refreshIn: REFRESH_TIME + 1, isLoading: false});
+                that.updateRefreshTime();
+            }
+        }).fail(() => {
+            console.log("error");
+            that.setState({refreshIn: REFRESH_TIME + 1, networkProblems: true});
+        });
+        $.getJSON(API_URL + "/my-plan", (data) => {
+            console.log(data);
+            if (data && data.lessons) {
+                that.setState({lessons: data.lessons, holiday: data.holiday});
+            }
+        }).fail(() => {
+            console.log("error");
+            that.setState({networkProblems: true});
+        });
+    };
+
+    componentDidMount() {
+        console.log(API_URL);
+        this.updateData();
+    }
+
+    closeNotification(notification) {
+        console.log(notification);
+        $("#not-" + notification.id).addClass("scale-out");
+        window.setTimeout(() => {
+            $("#not-" + notification.id).hide();
+        }, 200);
+        $.getJSON(API_URL + "/notifications/read/" + notification.id);
+        this.updateData();
+        this.setState({time: new Date()});
+    }
+
+    render() {
+        if (this.state.networkProblems) {
+            // Show loading screen until first data are loaded
+            return <div className={"row center-via-flex container"} style={{"height": "20em"}}>
+                <i className={"material-icons large"}>signal_wifi_off</i>
+                <p className={"flow-text text-center"}>Es ist ein Fehler bei der Netzwerkverbindung aufgetreten.</p>
+                <button className={"btn-flat grey-text"} onClick={this.updateData}>
+                    Erneuter Versuch in {this.state.refreshIn} s
+                </button>
+            </div>;
+        }
+
+        if (this.state.isLoading) {
+            // Show loading screen until first data are loaded
+            return <div className={"row center-via-flex container"} style={{"height": "15em"}}>
+                <div className={"center2-via-flex"}>
+                    <div className="preloader-wrapper big active">
+                        <div className="spinner-layer spinner-primary">
+                            <div className="circle-clipper left">
+                                <div className="circle"/>
+                            </div>
+                            <div className="gap-patch">
+                                <div className="circle"/>
+                            </div>
+                            <div className="circle-clipper right">
+                                <div className="circle"/>
+                            </div>
+                        </div>
+                    </div>
+                    <p className={"text-center flow-text"}>Deine aktuellen Informationen werden geladen …</p>
+                </div>
+            </div>;
+        }
+
+
+        const that = this;
+        return <div>
+            {/* REFRESH BUTTON*/}
+            <button className={"btn-flat right grey-text"} onClick={this.updateData}>
+                <i className={"material-icons left"}>refresh</i>
+                in {this.state.refreshIn} s
+            </button>
+
+            {/* GREETINGS */}
+            <p className="flow-text">Moin
+                Moin, {this.state.user.full_name !== "" ? this.state.user.full_name : this.state.user.username}. Hier
+                findest du alle aktuellen Informationen:</p>
+
+            <div className={"alert success"}>
+                <p>
+                    <i className={"material-icons left"}>report_problem</i>
+                    Das neue Dashboard von SchoolApps befindet sich momentan in der <strong>Testphase</strong>. Falls
+                    Fehler auftreten oder du einen Verbesserungsvorschlag für uns hast, schreibe uns bitte unter <a
+                    href={"mailto:support@katharineum.de"}>support@katharineum.de</a>.
+                </p>
+            </div>
+
+            {/* UNREAD NOTIFICATIONS*/}
+            {this.state.unread_notifications && this.state.unread_notifications.length > 0 ?
+                this.state.unread_notifications.map(function (notification) {
+                    return <div className={"alert primary scale-transition"} id={"not-" + notification.id}
+                                key={notification.id}>
+                        <div>
+                            {/* Info icon */}
+                            <i className={"material-icons left"}>info</i>
+
+                            <div className={"right"}>
+                                {/* Button for marking as read*/}
+                                <button className={"btn-flat"} onClick={() => that.closeNotification(notification)}>
+                                    <i className={"material-icons center"}>close</i>
+                                </button>
+                            </div>
+
+                            {/* Notification title and desc */}
+                            <strong>{notification.title}</strong>
+                            <p>{notification.description}</p>
+                        </div>
+                    </div>;
+                }) : ""}
+
+            {/* HINTS */}
+            {this.state.plan && this.state.plan.hints.length > 0 ?
+                <div>
+                    {this.state.plan.hints.map(function (hint, idx) {
+                        return <div className="alert primary" key={idx}>
+                            <div>
+                                <em className="right hide-on-small-and-down">
+                                    Hinweis für {that.state.date_formatted}
+                                </em>
+
+                                <i className="material-icons left">announcement</i>
+                                <p dangerouslySetInnerHTML={{__html: hint.html}}/>
+
+                                <em className="hide-on-med-and-up">
+                                    Hinweis für {that.state.date_formatted}
+                                </em>
+                            </div>
+                        </div>;
+                    })}
+                </div> : ""}
+
+            {/* CARDS */}
+            <div className={"row"}>
+                {/*<div className={"dashboard-cards"}>*/}
+
+                {/* MY PLAN */}
+                {this.state.has_plan ? <div className="col s12 m12 l6 xl4">
+                    <div className={"card"}>
+                        <div className="card-content">
+                            {/* Show individualized title */}
+                            <span className="card-title">
+                                Plan {this.state.plan.type === 2 ? "der" : "für"} <em>
+                                {this.state.plan.name}</em> für {this.state.date_formatted}
+                            </span>
+
+                            {/* Show plan */}
+                            {this.state.holiday ? <div className={"card"}>
+                                    <div className={"card-content"}>
+                                        {/*<i className={"material-icons medium left"}>no_meeting_room</i>*/}
+                                        <span
+                                            className="badge new blue center-align holiday-badge">{this.state.holiday.name}</span>
+                                        <br/>
+                                    </div>
+                                </div> :
+                                (this.state.lessons && this.state.lessons.length > 0 ?
+                                    <div className={"timetable-plan"}>
+                                        {this.state.lessons.map(function (lesson) {
+                                            // Show one lesson row
+                                            return <div className="row">
+                                                {/* Show time information*/}
+                                                <div className="col s4">
+                                                    <div className="card timetable-title-card">
+                                                        <div className="card-content">
+                                                            {/* Lesson number*/}
+                                                            <span className="card-title left">
+                                                                {lesson.time.number_format}
+                                                            </span>
+
+                                                            {/* Times */}
+                                                            <div
+                                                                className="right timetable-time grey-text text-darken-2">
+                                                                <span>{lesson.time.start_format}</span>
+                                                                <br/>
+                                                                <span>{lesson.time.end_format}</span>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+
+                                                {/* Show lesson content (via generated HTML by Django) */}
+                                                <div className={"col s8"}
+                                                     dangerouslySetInnerHTML={{__html: lesson.html}}/>
+                                            </div>;
+                                        })}
+                                    </div> : "")}
+                        </div>
+                        <div className="card-action">
+                            <a href={MY_PLAN_URL}>
+                                <span className="badge new primary-color card-action-badge">SMART PLAN</span>
+                                anzeigen
+                            </a>
+                        </div>
+                    </div>
+                </div> : ""}
+
+                {/* MY STATUS */}
+                {/*<div className="card">*/}
+                {/*    <div className="card-content">*/}
+                {/*        <span className="card-title">Mein Status</span>*/}
+                {/*        <div className={"row"}>*/}
+                {/*            <WithCheckCircleIcon>*/}
+                {/*                {this.state.user_type_formatted}*/}
+                {/*            </WithCheckCircleIcon>*/}
+
+                {/*            {this.state.user_type === 1 || this.state.user_type === 2 ? <WithCheckCircleIcon>*/}
+                {/*                Meine Klassen: {this.state.classes.join(", ")}*/}
+                {/*            </WithCheckCircleIcon> : ""}*/}
+
+                {/*            {this.state.user_type === 1 || this.state.user_type === 2 ? <WithCheckCircleIcon>*/}
+                {/*                Meine Kurse: {this.state.courses.join(", ")}*/}
+                {/*            </WithCheckCircleIcon> : ""}*/}
+
+                {/*            {this.state.user_type === 1 ? <WithCheckCircleIcon>*/}
+                {/*                Meine Fächer: {this.state.subjects.join(", ")}*/}
+                {/*            </WithCheckCircleIcon> : ""}*/}
+                {/*            {this.state.user_type === 1 || this.state.has_wifi ?*/}
+                {/*                <WithCheckCircleIcon>WLAN</WithCheckCircleIcon> : <div className={"col"}>*/}
+                {/*                    <i className={"material-icons left red-text"}>cancel</i>*/}
+                {/*                    Kein WLAN*/}
+                {/*                </div>}*/}
+                {/*        </div>*/}
+                {/*    </div>*/}
+                {/*</div>*/}
+
+                {/* CURRENT EVENTS*/}
+                {this.state.current_events && this.state.current_events.length > 0 ?
+                    <div className={"col s12 m12 l6 xl4"}>
+                        <div className="card ">
+                            <div className="card-content">
+                                <span className="card-title">Aktuelle Termine</span>
+                                {this.state.current_events.map(function (event) {
+                                    return <div className="card-panel event-card">
+                                        <span className={"title"}>{event.name}</span>
+                                        <br/>
+                                        {event.formatted}
+                                    </div>;
+                                })}
+                            </div>
+                            <div className="card-action">
+                                <a href="https://katharineum-zu-luebeck.de/aktuelles/termine/" target={"_blank"}>
+                                    Weitere Termine
+                                </a>
+                            </div>
+                        </div>
+                    </div>
+                    : ""}
+
+                {/* EXAMS */}
+                {/*<div className="card">*/}
+                {/*    <div className="card-content">*/}
+                {/*        <span className="card-title">Klausuren der <em>Eb</em></span>*/}
+                {/*        <div className="card-panel event-card">*/}
+                {/*            <span className={"title"}>Sextanereinschulung</span>*/}
+                {/*            <br/>*/}
+                {/*            28.Aug. 2019 18:30 - 22:00*/}
+                {/*        </div>*/}
+                {/*        <div className="card-panel event-card">*/}
+                {/*            <span className={"title"}>Sextanereinschulung</span>*/}
+                {/*            <br/>*/}
+                {/*            28.Aug. 2019 18:30 - 22:00*/}
+                {/*        </div>*/}
+                {/*    </div>*/}
+                {/*    <div className="card-action">*/}
+                {/*        <a href="https://katharineum-zu-luebeck.de/aktuelles/termine/">Alle Klausuren</a>*/}
+                {/*    </div>*/}
+                {/*</div>*/}
+
+                {/* NEWEST ARTICLE FROM HOMEPAGE*/}
+                {this.state.newest_article ?
+                    <div className={"col s12 m12 l6 xl4"}>
+                        <div className="card">
+                            {/* Image with badge and title */}
+                            <div className="card-image">
+                                <span className={"badge-image z-depth-2"}>Aktuelles von der Homepage</span>
+                                <img src={this.state.newest_article.image_url}
+                                     alt={this.state.newest_article.title}/>
+
+                            </div>
+
+
+                            {/* Short text */}
+                            <div className="card-content">
+                                <span className="card-title"
+                                      dangerouslySetInnerHTML={{__html: this.state.newest_article.title}}/>
+
+                                <p dangerouslySetInnerHTML={{__html: this.state.newest_article.short_text}}/>
+                            </div>
+
+                            {/* Link to article */}
+                            <div className="card-action">
+                                <a href={this.state.newest_article.link} target={"_blank"}>Mehr lesen</a>
+                            </div>
+                        </div>
+
+                        {/* Link to homepage */}
+                        <a className={"btn hundred-percent primary-color"}
+                           href={"https://katharineum-zu-luebeck.de/"}
+                           target={"_blank"}>
+                            Weitere Artikel
+                            <i className={"material-icons right"}>arrow_forward</i>
+                        </a>
+                    </div>
+                    : ""}
+            </div>
+            {/*</div>*/}
+
+            {/* ACITIVITIES */}
+            <div className={"row"}>
+                <div className="col s12 m6">
+                    <h5>Letzte Aktivitäten</h5>
+                    {this.state.activities && this.state.activities.length > 0 ? <ul className={"collection"}>
+                        {this.state.activities.map((activity) => {
+                            return <li className={"collection-item"} key={activity.id}>
+                                <span className="badge new primary-color">{activity.app}</span>
+                                <span className="title">{activity.title}</span>
+                                <p>
+                                    <i className="material-icons left">access_time</i> {activity.created_at}
+                                </p>
+                                <p>
+                                    {activity.description}
+                                </p>
+                            </li>;
+                        })}
+                    </ul> : <p>
+                        Noch keine Aktivitäten vorhanden.
+                    </p>}
+                </div>
+
+                {/* NOTIFICATIONS */}
+                <div className="col s12 m6">
+                    <h5>Letzte Benachrichtigungen</h5>
+                    {this.state.notifications && this.state.notifications.length > 0 ? <ul className={"collection"}>
+                        {this.state.notifications.map((notification) => {
+                            return <li className={"collection-item"} key={notification.id}>
+                                <span className="badge new primary-color">{notification.app}</span>
+                                <span className="title">{notification.title}</span>
+                                <p>
+                                    <i className="material-icons left">access_time</i> {notification.created_at}
+                                </p>
+                                <p>
+                                    {notification.description}
+                                </p>
+                                {notification.link ? <p>
+                                    <a href={notification.link}>Mehr Informationen →</a>
+                                </p> : ""}
+                            </li>;
+                        })}
+                    </ul> : <p>
+                        Noch keine Benachrichtigungen vorhanden.
+                    </p>}
+                </div>
+            </div>
+        </div>;
+    }
+}
+
+$(document).ready(function () {
+    const domContainer = document.querySelector('#dashboard_container');
+    ReactDOM.render(<Dashboard/>, domContainer);
+});
diff --git a/react/src/rebus.js b/react/src/rebus.js
new file mode 100644
index 0000000000000000000000000000000000000000..7a24db362e93ca9c0d70694c9af8615dd236245f
--- /dev/null
+++ b/react/src/rebus.js
@@ -0,0 +1,455 @@
+/*** source for biscuit/core/static/js/rebus.js ***/
+
+const OPTIONS_ONLINE_COMMON = [
+    "Portal ist nicht erreichbar",
+    "Fehlermeldung(en) tauchen auf",
+    "Anmeldung funktioniert nicht",
+    "Zugangsdaten vergessen"
+];
+
+const BASIC_OPTIONS = [
+    {
+        id: "infrastructureIssues",
+        name: "Infrastrukturprobleme",
+        options: [
+            {
+                id: "presentationDeviceIssue",
+                name: "Problem mit Beamer/Fernseher",
+                helpText: "Bitte wähle aus, wo der Beamer bzw. Fernseher steht!"
+            },
+            {
+                id: "printerIssue",
+                name: "Problem mit einem Drucker",
+                helpText: "Bitte nenne uns in der Beschreibung das Modell des Druckers, damit wir genau wissen, welchen Drucker du meinst!"
+            },
+            {
+                id: "subMonitorIssue",
+                name: "Vertretungsplanmonitor funktioniert nicht",
+                helpText: "Nenne uns bitte in der Beschreibung ggf. weitere Informationen!"
+            },
+            {
+                id: "aulaIssue",
+                name: "Problem in der Aula (→Technik-AG)",
+                helpText: "Deine Anfrage wird direkt an die Technik-AG weitergeleitet."
+            },
+            {
+                id: "wlanIssue",
+                name: "Probleme mit dem Schul-WLAN (kath-schueler/lehrer)",
+                helpText: "Nenne uns bitte unbedingt auch den Ort in der Schule, an dem das Problem auftrat."
+            },
+        ]
+    },
+    {
+        id: "onlineIssues",
+        name: "Webservices",
+        options: [
+            {
+                id: "forum",
+                name: "Forum (ILIAS)",
+                options: OPTIONS_ONLINE_COMMON.concat([
+                    "Ich kann meinen Kurs bzw. Klasse nicht sehen/finden.",
+                    "Ich kann keine Dateien hochladen.",
+                    "Es taucht eine weiße Seite auf.",
+                    "Ich habe falsche Informationen gefunden.",
+                ])
+            },
+            {
+                id: "mail",
+                name: "Webmail/Mailserver",
+                options: OPTIONS_ONLINE_COMMON.concat([
+                    "Mein E-Mail-Programm funktioniert mit meiner …@katharineum.de-Adresse nicht.",
+                    "Ich bekomme keine E-Mails bzw. kann keine senden."
+                ])
+            },
+            {
+                id: "schoolapps",
+                name: "SchoolApps",
+                options: OPTIONS_ONLINE_COMMON.concat([
+                    "Der Stundenplan/Vertretungsplan ist falsch.",
+                    "Ich bin der falschen Klasse zugeordnet.",
+                    "Ich habe einen Fehler gefunden."
+                ])
+            },
+            {
+                id: "subOrMenu",
+                name: "Vertretungsplan/Speiseplan",
+                options: OPTIONS_ONLINE_COMMON.concat([
+                    "Kein Vertretungsplan zu sehen",
+                    "Falscher Vertretungsplan zu sehen",
+                    "Kein Speiseplan zu sehen",
+                    "Falscher Speiseplan zu sehen"
+                ])
+            },
+            {
+                id: "website",
+                name: "Website (katharineum-zu-luebeck.de)",
+                options: [
+                    "Website nicht erreichbar",
+                    "Falsche Inhalte vorhanden",
+                    "Typografiefehler"
+                ]
+
+            },
+            {
+                id: "otherOnlineIssue",
+                name: "Andere Anwendung"
+            },
+        ]
+    },
+    {
+        id: "deviceIssues",
+        name: "Probleme am Computer/Notebook",
+        options: [
+            {
+                id: "loginIssue",
+                name: "Anmeldeproblem/Passwort vergessen"
+            },
+            {
+                id: "internetIssue",
+                name: "Internetproblem"
+            },
+            {
+                id: "noReaction",
+                name: "Programm-/Computerabsturz (keine Reaktion)"
+            },
+            {
+                id: "powerOffNoBoot",
+                name: "Computer/Notebook ist ausgegangen/startet nicht"
+            },
+            {
+                id: "speedIssue",
+                name: "Computer/Notebook zu langsam"
+            },
+            {
+                id: "noUSB",
+                name: "USB-Stick wird nicht erkannt"
+            },
+            {
+                id: "noOpenTray",
+                name: "CD/DVD-Laufwerk öffnet sich nicht"
+            },
+            {
+                id: "noCDDVD",
+                name: "CD/DVD wird nicht erkannt/abgespielt"
+            },
+            {
+                id: "keyboardMouse",
+                name: "Tastatur/Maus funktioniert nicht"
+            },
+            {
+                id: "missingHardware",
+                name: "Tastatur/Maus/Lautsprecher/etc. fehlt"
+            },
+            {
+                id: "missingKeys",
+                name: "Fehlende Tasten auf der Tastatur"
+            },
+            {
+                id: "hardwareMisc",
+                name: "Andere Hardware defekt / Äußere Schäden"
+            }
+
+
+        ]
+    },
+
+    {
+        id: "otherIssues",
+        name: "Andere Probleme",
+        options: [
+            {
+                id: "extra",
+                name: "Sonstiges"
+            }
+        ]
+    },
+];
+
+
+const OTHER_LOCATIONS = [
+    "Notebookwagen 1. Stock/R 2.06",
+    "Notebookwagen 2. Stock/R 2.10",
+    "Notebookwagen 3. Stock/Physik",
+    "Internetcafe",
+    "Infopoint/Sekretariatsvorraum",
+    "Lehrerzimmer (Vorraum)",
+    "Lehrerzimmer (Hauptraum)"
+];
+
+
+function getCategoryOfOption(option) {
+    for (const category of BASIC_OPTIONS) {
+        // console.log(category);
+        for (const opt of category.options) {
+            // console.log(opt);
+            if (opt.id === option) {
+                return category.id;
+            }
+        }
+    }
+}
+
+
+function getOption(option) {
+    for (const category of BASIC_OPTIONS) {
+        for (const opt of category.options) {
+            if (opt.id === option) {
+                return opt;
+            }
+        }
+    }
+}
+
+class Select extends React.Component {
+    render() {
+        return <select onChange={this.props.onChange} defaultValue={"no"} required={this.props.show}>
+            <option value={"no"} disabled={true}>Nichts ausgewählt</option>
+            {this.props.values.map(function (val, i) {
+                return <option value={val} key={i}>{val}</option>;
+            })}
+            <option value={"extra"}>{this.props.defaultValue}</option>
+        </select>
+    }
+}
+
+Select.propTypes = {
+    onChange: PropTypes.func.isRequired,
+    values: PropTypes.array.isRequired,
+    defaultValue: PropTypes.string,
+    show: PropTypes.bool.isRequired
+
+};
+
+Select.defaultProps = {
+    defaultValue: "Sonstiges"
+};
+
+class Input extends React.Component {
+    render() {
+        return <div
+            className={(this.props.show ? "" : "hide ") + "input-field col s12 m12 l4"
+            }>
+            <i className={"material-icons prefix"}>{this.props.icon}</i>
+            {this.props.children}
+            <label>{this.props.label}</label>
+        </div>;
+    }
+}
+
+Input.propTypes = {
+    icon: PropTypes.string,
+    show: PropTypes.bool,
+    label: PropTypes.string.isRequired,
+    children: PropTypes.object.isRequired
+};
+
+Input.defaultProps = {
+    icon: "list",
+    show: false,
+};
+
+class REBUSDynSelect extends React.Component {
+    constructor() {
+        super();
+        this.state = {
+            selectedCategory: "noCategory",
+            selectedOption: null,
+            helpText: "Wähle bitte eine Kategorie aus!",
+            valueB: "",
+            valueC: "",
+            step: 0
+        }
+    }
+
+    componentDidMount() {
+        // Init materialize selects
+        const elems = document.querySelectorAll('select');
+        M.FormSelect.init(elems, {});
+    }
+
+    _onCategoryChanges = (e) => {
+        const opt = e.target.value;
+        const category = getCategoryOfOption(opt);
+        const option = getOption(opt);
+
+        // Get matching helper text
+        let helpText = option.helpText || this.state.helpText;
+        if (category === "deviceIssues") {
+            helpText = "Wähle bitte das Gerät mit dem Problem aus! Bitte vergiss nicht, uns das Problem unten genauer zu beschreiben!"
+        } else if (category === "onlineIssues") {
+            helpText = "Bitte konkretisiere das Problem durch eine Auswahl und gib bitte unten genauere Informationen an."
+        } else if (category === "otherIssues") {
+            helpText = "Da es sich scheinbar um ein seltenes oder noch nicht erfasstes Problem handelt, gib uns bitte besonders viele Informationen."
+        }
+
+        // Update state
+        this.setState({
+            selectedCategory: category,
+            selectedOption: option,
+            step: 1,
+            helpText: helpText
+        })
+    };
+
+    _onSetB = (e) => {
+        const val = e.target.value;
+        this.setState({
+            valueB: val,
+            step: 2
+        })
+    };
+
+    _onSetC = (e) => {
+        const val = e.target.value;
+        this.setState({
+            valueC: val,
+            step: 2
+        })
+    };
+
+    render() {
+        let LOCATIONS = this.props.rooms.concat(OTHER_LOCATIONS);
+        let LOCATIONS_WITH_POSSIBLE_PRESENTATION_DEVICE = this.props.rooms;
+        LOCATIONS.sort();
+
+        // console.log(this.state);
+        const that = this;
+        const sC = this.state.selectedCategory;
+        const sO = this.state.selectedOption ? this.state.selectedOption.id : null;
+        const step = this.state.step;
+        // console.log(BASIC_OPTIONS[2].options);
+        return (
+            <div className="App">
+                <div className={"row"}>
+                    < div
+                        className="input-field col s12 m12 l4">
+                        <i className={"material-icons prefix"}>list</i>
+                        <select onChange={this._onCategoryChanges} defaultValue={"noCategory"} className={"validate"}
+                                required={true}>-
+                            <option value={"noCategory"} disabled={true}>Keine Kategorie ausgewählt</option>
+                            {BASIC_OPTIONS.map(function (category) {
+                                return <optgroup label={category.name} key={category.id}>
+                                    {category.options.map(function (option) {
+                                        return <option value={option.id} key={option.id}>{option.name}</option>;
+                                    })}
+                                </optgroup>;
+                            })}
+                            {/*<option value={"extra"}>Sonstiges</option>*/}
+                        </select>
+                        <label>Kategorie</label>
+                    </div>
+
+                    {/* Section B – Device Issues*/}
+                    <Input label={"Ort des Computer/Notebook"} icon={"location_on"} show={sC === "deviceIssues"}>
+                        <Select onChange={this._onSetB} values={LOCATIONS} defaultValue={"Anderer Ort"}
+                                show={sC === "deviceIssues"}/>
+                    </Input>
+
+                    {/* Section B – Presentation Device Issues */}
+                    <Input label={"Ort des Beamer/Fernseher"} icon={"location_on"}
+                           show={sO === "presentationDeviceIssue"}>
+                        <Select onChange={this._onSetB} values={LOCATIONS_WITH_POSSIBLE_PRESENTATION_DEVICE}
+                                defaultValue={"Anderer Raum"} show={sO === "presentationDeviceIssue"}/>
+                    </Input>
+
+                    {/* Section B – Printer Issue */}
+                    <Input label={"Art des Problems"} icon={"bug_report"} show={sO === "printerIssue"}>
+                        <Select onChange={this._onSetB}
+                                values={["Papierstau", "Toner leer", "Papier leer", "Drucker bekommt keine Daten"]}
+                                defaultValue={"Anderes Problem"} show={sO === "printerIssue"}/>
+                    </Input>
+
+                    {/* Section B – Substitution Monitor Issue */}
+                    <Input label={"Art des Problems"} icon={"bug_report"} show={sO === "subMonitorIssue"}>
+                        <Select onChange={this._onSetB}
+                                values={["Schwarzer Bildschirm", "Tage wechseln nicht (Eingefroren)"]}
+                                defaultValue={"Anderes Problem"} show={sO === "subMonitorIssue"}/>
+                    </Input>
+
+                    {/* Section B – WLAN Issue */}
+                    <Input label={"Art des Problems"} icon={"bug_report"} show={sO === "wlanIssue"}>
+                        <Select onChange={this._onSetB}
+                                values={["Kein Empfang", "Zugangsdaten funktionieren nicht", "Geschwindigkeit zu langsam"]}
+                                defaultValue={"Anderes Problem"} show={sO === "wlanIssue"}/>
+                    </Input>
+
+                    {/* Section B – Online Issue*/}
+                    {BASIC_OPTIONS[1].options.map(function (opt) {
+                        if (opt.options) {
+                            return <Input label={"Art des Problems"} icon={"bug_report"}
+                                          show={sC === "onlineIssues" && sO === opt.id} key={opt.id}>
+                                <Select onChange={that._onSetB}
+                                        values={opt.options}
+                                        defaultValue={"Anderes Problem"} show={sC === "onlineIssues" && sO === opt.id}
+                                        key={opt.id}/>
+                            </Input>;
+                        } else {
+                            return <p/>;
+                        }
+                    })}
+
+
+                    {/* Section C – Presentation Device Issues */}
+                    <Input label={"Handelt es sich um einen Beamer oder einen Fernseher?"} icon={"tv"}
+                           show={sO === "presentationDeviceIssue" && step === 2}>
+                        <Select onChange={this._onSetC} values={["Beamer", "Fernseher/Bildschirm"]}
+                                defaultValue={"Sonstiges"} show={sO === "presentationDeviceIssue" && step === 2}/>
+                    </Input>
+
+                    {/* Section C – Presentation Device Issues */}
+                    <Input label={"Ort des Druckers"} icon={"location_on"}
+                           show={sO === "printerIssue" && step === 2}>
+                        <Select onChange={this._onSetC} values={LOCATIONS}
+                                defaultValue={"Anderer Raum"} show={sO === "printerIssue"}/>
+                    </Input>
+
+                    {/* Section C – WLAN Issue */}
+                    <Input label={"Um welches WLAN-Netzwerk handelt es sich?"} icon={"wifi"}
+                           show={sO === "wlanIssue" && step === 2}>
+                        <Select onChange={this._onSetC}
+                                values={["kath-schueler", "kath-lehrer", "kath-edu", "kath-gaeste"]}
+                                defaultValue={"-"} show={sO === "wlanIssue" && step === 2}/>
+                    </Input>
+
+                    {/* Section C – Device Issue */}
+                    <div
+                        className={(sC === "deviceIssues" && step === 2 ? "" : "hide ") + "input-field col s12 m12 l4"
+                        }>
+                        <i className={"material-icons prefix"}>device_unknown</i>
+                        <input type={"text"} id={"valc"} onChange={this._onSetC}
+                               required={sC === "deviceIssues" && step === 2} className={"validate"}/>
+                        <label htmlFor="valc">Um welches Gerät handelt es sich?</label>
+                    </div>
+
+                    {/* Helper Text */}
+                    <div className={"col s12"}>
+                        <p>
+                            <i className={"material-icons left"}>info</i>
+                            {this.state.helpText}
+                        </p>
+                    </div>
+
+
+                </div>
+
+                {/* Prepare values for Django */}
+                <div>
+                    <input type={"hidden"} name={"a"}
+                           value={this.state.selectedOption ? this.state.selectedOption.name : ""}/>
+                    <input type={"hidden"} name={"b"} value={this.state.valueB}/>
+                    <input type={"hidden"} name={"c"} value={this.state.valueC}/>
+                </div>
+
+            </div>
+        );
+    }
+}
+
+REBUSDynSelect.propTypes = {
+    rooms: PropTypes.array.isRequired
+};
+
+$(document).ready(function () {
+    const domContainer = document.querySelector('#dynselect');
+    ReactDOM.render(<REBUSDynSelect {...props}/>, domContainer);
+});
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8b767d1c91300b74fb4bce041688565787ae8949
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,9 @@
+mysqlclient
+django-pdb
+django-filter
+django_react_templatetags
+PyPDF2
+martor
+django-pwa
+django_widget_tweaks
+ics