diff --git a/.gitmodules b/.gitmodules
index a6fdcc7e6261f78d33e2aa5460739210e9c65909..473e630164971d3e1620a9d67eb6810d839c441e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,15 +10,7 @@
 	path = apps/official/AlekSIS-App-Exlibris
 	url = https://edugit.org/AlekSIS/AlekSIS-App-Exlibris
 	ignore = untracked
-[submodule "apps/official/AlekSIS-App-SchILD-NRW"]
-	path = apps/official/AlekSIS-App-SchILD-NRW
-	url = https://edugit.org/AlekSIS/AlekSIS-App-SchILD-NRW
-	ignore = untracked
-[submodule "apps/official/AlekSIS-App-Untis"]
-	path = apps/official/AlekSIS-App-Untis
-	url = https://edugit.org/AlekSIS/AlekSIS-App-Untis
-	ignore = untracked
-[submodule "apps/official/AlekSIS-App-Hjelp"]
-	path = apps/official/AlekSIS-App-Hjelp
-	url = https://edugit.org/AlekSIS/AlekSIS-App-Hjelp.git
+[submodule "apps/official/AlekSIS-App-DashboardFeeds"]
+	path = apps/official/AlekSIS-App-DashboardFeeds
+	url = https://edugit.org/AlekSIS/AlekSIS-App-DashboardFeeds.git
 	ignore = untracked
diff --git a/aleksis/core/dashboard/README.md b/aleksis/core/dashboard/README.md
deleted file mode 100644
index 5246b5c011be74320aa37e498abc2bc982168314..0000000000000000000000000000000000000000
--- a/aleksis/core/dashboard/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# 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
-
diff --git a/aleksis/core/dashboard/settings.py b/aleksis/core/dashboard/settings.py
deleted file mode 100644
index 4dc09f18c3e8d8f2979aa5e8eb6dda8479f85735..0000000000000000000000000000000000000000
--- a/aleksis/core/dashboard/settings.py
+++ /dev/null
@@ -1,31 +0,0 @@
-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/aleksis/core/dashboard/templates/dashboard/index.html b/aleksis/core/dashboard/templates/dashboard/index.html
deleted file mode 100644
index 8450678118187c74ce34ce317a27047a844b8f0e..0000000000000000000000000000000000000000
--- a/aleksis/core/dashboard/templates/dashboard/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% 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/aleksis/core/dashboard/urls.py b/aleksis/core/dashboard/urls.py
deleted file mode 100644
index 18c13061822957f21d4f87a51497170e392683ae..0000000000000000000000000000000000000000
--- a/aleksis/core/dashboard/urls.py
+++ /dev/null
@@ -1,31 +0,0 @@
-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('about/', tools_views.about, name='about')
-]
diff --git a/aleksis/core/dashboard/views/dashboard.py b/aleksis/core/dashboard/views/dashboard.py
deleted file mode 100644
index a45100ed064711904d77895318c5614e4c530266..0000000000000000000000000000000000000000
--- a/aleksis/core/dashboard/views/dashboard.py
+++ /dev/null
@@ -1,142 +0,0 @@
-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/aleksis/core/dashboard/views/tools.py b/aleksis/core/dashboard/views/tools.py
deleted file mode 100644
index 5c088dcf6744118713f15924f9d56069dee7d01a..0000000000000000000000000000000000000000
--- a/aleksis/core/dashboard/views/tools.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.shortcuts import render
-
-from meta import OPEN_SOURCE_COMPONENTS
-
-
-def about(request):
-    return render(request, "common/about.html", context={"components": OPEN_SOURCE_COMPONENTS})
diff --git a/aleksis/core/mailer.py b/aleksis/core/mailer.py
deleted file mode 100644
index e4dc6b4479ccd9e1ca5644108c3e0be7ed6ca4d5..0000000000000000000000000000000000000000
--- a/aleksis/core/mailer.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.core.mail import send_mail
-from django.template.loader import render_to_string
-
-from constance import config
-
-
-mail_out = "{} <{}>".format(config.MAIL_OUT_NAME,
-                            config.MAIL_OUT) if config.MAIL_OUT_NAME else config.MAIL_OUT
-
-
-def send_mail_with_template(title, receivers, plain_template, html_template, context={}, mail_out=mail_out):
-    msg_plain = render_to_string(plain_template, context)
-    msg_html = render_to_string(html_template, context)
-
-    try:
-        send_mail(
-            title,
-            msg_plain,
-            mail_out,
-            receivers,
-            html_message=msg_html,
-        )
-    except Exception as e:
-        print("[EMAIL PROBLEM] ", e)
diff --git a/aleksis/core/migrations/0009_dashboard_widget.py b/aleksis/core/migrations/0009_dashboard_widget.py
new file mode 100644
index 0000000000000000000000000000000000000000..b57c272b4cc8501e41b5ffaf9f2ec3d796795021
--- /dev/null
+++ b/aleksis/core/migrations/0009_dashboard_widget.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.0.2 on 2020-01-29 16:45
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contenttypes', '0002_remove_content_type_name'),
+        ('core', '0008_rename_fields_notification_activity'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='DashboardWidget',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(max_length=150, verbose_name='Widget Title')),
+                ('active', models.BooleanField(blank=True, verbose_name='Activate Widget')),
+                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_core.dashboardwidget_set+', to='contenttypes.ContentType')),
+            ],
+            options={
+                'verbose_name': 'Dashboard Widget',
+                'verbose_name_plural': 'Dashboard Widgets',
+            },
+        ),
+    ]
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index d8d4202ae270c1017f46111069a702a6e6d14ef4..40b0b53339b90311fa8f7aaaa459e91b7db0cdb4 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -6,6 +6,7 @@ from django.db import models
 from django.utils.translation import ugettext_lazy as _
 from image_cropping import ImageCropField, ImageRatioField
 from phonenumber_field.modelfields import PhoneNumberField
+from polymorphic.models import PolymorphicModel
 
 from .mixins import ExtensibleModel
 from .util.notifications import send_notification
@@ -231,3 +232,46 @@ class Notification(models.Model):
     class Meta:
         verbose_name = _("Notification")
         verbose_name_plural = _("Notifications")
+
+
+class DashboardWidget(PolymorphicModel):
+    """ Base class for dashboard widgets on the index page
+
+    To implement a widget, add a model that subclasses DashboardWidget, sets the template
+    and implements the get_context method to return a dictionary to be passed as context
+    to the template.
+
+    If your widget does not add any database fields, you should mark it as a proxy model.
+
+    Example::
+    
+      from aleksis.core.models import DashboardWidget
+
+      class MyWidget(DhasboardWIdget):
+          template = "myapp/widget.html"
+
+          def get_context(self):
+              context = {"some_content": "foo"}
+              return context
+
+          class Meta:
+              proxy = True
+    """
+
+    template = None
+
+    title = models.CharField(max_length=150, verbose_name=_("Widget Title"))
+    active = models.BooleanField(blank=True, verbose_name=_("Activate Widget"))
+
+    def get_context(self):
+        raise NotImplementedError("A widget subclass needs to implement the get_context method.")
+
+    def get_template(self):
+        return self.template
+
+    def __str__(self):
+        return self.title
+
+    class Meta:
+        verbose_name = _("Dashboard Widget")
+        verbose_name_plural = _("Dashboard Widgets")
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 46b070449c3bc6ff46e98ace323953b7f6252ac1..c314e3a7305a2c1655de57df9d4ac05220b4238a 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -51,6 +51,7 @@ INSTALLED_APPS = [
     "django.contrib.sessions",
     "django.contrib.messages",
     "django.contrib.staticfiles",
+    "polymorphic",
     "django_global_request",
     "settings_context_processor",
     "sass_processor",
diff --git a/aleksis/core/static/js/include_ajax_live.js b/aleksis/core/static/js/include_ajax_live.js
new file mode 100644
index 0000000000000000000000000000000000000000..3a4794bad9881ebe502ea7786b8c0d2a740e65f0
--- /dev/null
+++ b/aleksis/core/static/js/include_ajax_live.js
@@ -0,0 +1,35 @@
+const asyncIntervals = [];
+
+const runAsyncInterval = async (cb, interval, intervalIndex) => {
+  await cb();
+  if (asyncIntervals[intervalIndex]) {
+    setTimeout(() => runAsyncInterval(cb, interval, intervalIndex), interval);
+  }
+};
+
+const setAsyncInterval = (cb, interval) => {
+  if (cb && typeof cb === "function") {
+    const intervalIndex = asyncIntervals.length;
+    asyncIntervals.push(true);
+    runAsyncInterval(cb, interval, intervalIndex);
+    return intervalIndex;
+  } else {
+    throw new Error('Callback must be a function');
+  }
+};
+
+const clearAsyncInterval = (intervalIndex) => {
+  if (asyncIntervals[intervalIndex]) {
+    asyncIntervals[intervalIndex] = false;
+  }
+};
+
+let live_load_interval = setAsyncInterval(async () => {
+  console.log('fetching new data');
+  const promise = new Promise((resolve) => {
+    $('#live_load').load(window.location.pathname + " #live_load");
+    resolve(1);
+  });
+  await promise;
+  console.log('data fetched successfully');
+}, 15000);
diff --git a/aleksis/core/templates/components/react.html b/aleksis/core/templates/components/react.html
deleted file mode 100644
index 452959486f3e4a317b650e1219575c08a4065df2..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/components/react.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% 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/aleksis/core/templates/core/index.html b/aleksis/core/templates/core/index.html
index 05875979203262f7f8818db06400da609679c891..22d0d4013929f402276f9a960450c4f2f99725e8 100644
--- a/aleksis/core/templates/core/index.html
+++ b/aleksis/core/templates/core/index.html
@@ -1,5 +1,5 @@
 {% extends 'core/base.html' %}
-{% load i18n %}
+{% load i18n static dashboard %}
 
 {% block browser_title %}{% blocktrans %}Home{% endblocktrans %}{% endblock %}
 
@@ -24,6 +24,14 @@
       </div>
     {% endfor %}
 
+    <div class="row" id="live_load">
+        {% for widget in widgets %}
+          <div class="col s12 m12 l6 xl4">
+              {% include_widget widget %}
+          </div>
+        {% endfor %}
+    </div>
+
     <div class="row">
       <div class="col s12 m6">
         <h5>{% blocktrans %}Last activities{% endblocktrans %}</h5>
@@ -77,4 +85,6 @@
       </div>
     </div>
   {% endif %}
+
+  <script type="text/javascript" src="{% static "js/include_ajax_live.js" %}"></script>
 {% endblock %}
diff --git a/aleksis/core/templates/mail/email.html b/aleksis/core/templates/mail/email.html
deleted file mode 100644
index 58f6f6b639a423649f425ea12997aa28e6ec7007..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/mail/email.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% 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/aleksis/core/templates/mail/email.txt b/aleksis/core/templates/mail/email.txt
deleted file mode 100644
index c781ae1d5fe821a8c1903eea66f284083d4c1ec4..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/mail/email.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-Hallo {{ user.name }},
-vielen Dank, dass du SchoolApps benutzt.
-
-----
-Weitere Informationen findest du hier:
-- FORUM (forum.katharineum.de)
-----
-
-Deine Computer-AG
diff --git a/aleksis/core/templates/mail/header.html b/aleksis/core/templates/mail/header.html
deleted file mode 100644
index ad452e53be3e7ada776b02ce2b64ab605e206b9f..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/mail/header.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<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/aleksis/core/templates/mail/notification.html b/aleksis/core/templates/mail/notification.html
deleted file mode 100644
index a5619f9aa49bdc693e106a8b5cca888a5d8b29a4..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/mail/notification.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% 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/aleksis/core/templates/mail/notification.txt b/aleksis/core/templates/mail/notification.txt
deleted file mode 100644
index ca927a4fd764d6d1e02d225a9dc43a201adf695d..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/mail/notification.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-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/aleksis/core/templates/partials/footer.html b/aleksis/core/templates/partials/footer.html
deleted file mode 100644
index b682bd0cfd699c9148522b80308bd3e1e2c62d66..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/partials/footer.html
+++ /dev/null
@@ -1,76 +0,0 @@
-{% 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/aleksis/core/templates/partials/header.html b/aleksis/core/templates/partials/header.html
deleted file mode 100644
index c3edc1caa05eb2e6863ffef1ea3e7672059b05bb..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/partials/header.html
+++ /dev/null
@@ -1,420 +0,0 @@
-{% 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' %}">
-
-
-  <!--------->
-    <!-- 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 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/aleksis/core/templates/partials/paper/footer.html b/aleksis/core/templates/partials/paper/footer.html
deleted file mode 100644
index 1dc3a88b8281509e4eb8d18212b4513d32189845..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/partials/paper/footer.html
+++ /dev/null
@@ -1,32 +0,0 @@
-{% 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/aleksis/core/templates/partials/paper/header.html b/aleksis/core/templates/partials/paper/header.html
deleted file mode 100644
index 845672ab118cc0112332f292b7899661a6b64ebd..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/partials/paper/header.html
+++ /dev/null
@@ -1,144 +0,0 @@
-{% 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/aleksis/core/templatetags/dashboard.py b/aleksis/core/templatetags/dashboard.py
new file mode 100644
index 0000000000000000000000000000000000000000..b051084f88008a1b30758d46f1d91c9695a408e1
--- /dev/null
+++ b/aleksis/core/templatetags/dashboard.py
@@ -0,0 +1,13 @@
+from django.template import Library, loader
+
+register = Library()
+
+
+@register.simple_tag
+def include_widget(widget) -> dict:
+    """ Render a template with context from a defined widget """
+
+    template = loader.get_template(widget.get_template())
+    context = widget.get_context()
+
+    return template.render(context)
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 63929088ba38406d0e560fcaee218404ad1462bf..6f1d4275e0fd15f2b1cd315353beb56d219342fd 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -66,4 +66,9 @@ for app_config in apps.app_configs.values():
     if not app_config.name.startswith("aleksis.apps."):
         continue
 
-    urlpatterns.append(path("app/%s/" % app_config.label, include("%s.urls" % app_config.name)))
+    try:
+        urlpatterns.append(path("app/%s/" % app_config.label, include("%s.urls" % app_config.name)))
+    except ModuleNotFoundError:
+        # Ignore exception as app just has no URLs
+        pass  # noqa
+
diff --git a/aleksis/core/userinformation.py b/aleksis/core/userinformation.py
deleted file mode 100644
index 69bd84938444dcfe31aa44e4259d7c6d7d84672f..0000000000000000000000000000000000000000
--- a/aleksis/core/userinformation.py
+++ /dev/null
@@ -1,64 +0,0 @@
-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/aleksis/core/util/network.py b/aleksis/core/util/network.py
deleted file mode 100644
index 6c81df3330006f4ba8f591615d1c3aa06d939bcd..0000000000000000000000000000000000000000
--- a/aleksis/core/util/network.py
+++ /dev/null
@@ -1,167 +0,0 @@
-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/aleksis/core/views.py b/aleksis/core/views.py
index ff11482f68268d5f5d090502039d1c4be12e99a2..c610ec8abf640246355fd04e56130e15f103f483 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -16,7 +16,7 @@ from .forms import (
     EditTermForm,
     PersonsAccountsFormSet,
 )
-from .models import Activity, Group, Notification, Person, School
+from .models import Activity, Group, Notification, Person, School, DashboardWidget
 from .tables import GroupsTable, PersonsTable
 from .util import messages
 
@@ -33,6 +33,8 @@ def index(request: HttpRequest) -> HttpResponse:
     context["notifications"] = notifications
     context["unread_notifications"] = unread_notifications
 
+    context["widgets"] = DashboardWidget.objects.filter(active=True)
+
     return render(request, "core/index.html", context)
 
 
diff --git a/apps/official/AlekSIS-App-Alsijil b/apps/official/AlekSIS-App-Alsijil
index dfba5000317d5df96a24187523b0a321aff08135..02cd44ea72e5989358c4d8031e2eeeec5db76eb5 160000
--- a/apps/official/AlekSIS-App-Alsijil
+++ b/apps/official/AlekSIS-App-Alsijil
@@ -1 +1 @@
-Subproject commit dfba5000317d5df96a24187523b0a321aff08135
+Subproject commit 02cd44ea72e5989358c4d8031e2eeeec5db76eb5
diff --git a/apps/official/AlekSIS-App-Chronos b/apps/official/AlekSIS-App-Chronos
index de78f40b39b970570ffbd2d059a0329376141e75..35d64da9a6c6ed5077f8651a36a316306e49acb0 160000
--- a/apps/official/AlekSIS-App-Chronos
+++ b/apps/official/AlekSIS-App-Chronos
@@ -1 +1 @@
-Subproject commit de78f40b39b970570ffbd2d059a0329376141e75
+Subproject commit 35d64da9a6c6ed5077f8651a36a316306e49acb0
diff --git a/apps/official/AlekSIS-App-DashboardFeeds b/apps/official/AlekSIS-App-DashboardFeeds
new file mode 160000
index 0000000000000000000000000000000000000000..8207d487baa1767cff5ee43cc1706ab025bf644e
--- /dev/null
+++ b/apps/official/AlekSIS-App-DashboardFeeds
@@ -0,0 +1 @@
+Subproject commit 8207d487baa1767cff5ee43cc1706ab025bf644e
diff --git a/apps/official/AlekSIS-App-Hjelp b/apps/official/AlekSIS-App-Hjelp
deleted file mode 160000
index 1415a4851bd53ffe4e14c0961422e5b66060d0f8..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-Hjelp
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 1415a4851bd53ffe4e14c0961422e5b66060d0f8
diff --git a/apps/official/AlekSIS-App-SchILD-NRW b/apps/official/AlekSIS-App-SchILD-NRW
deleted file mode 160000
index f36180d17d774f5866a799d3b256823edba1a19b..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-SchILD-NRW
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit f36180d17d774f5866a799d3b256823edba1a19b
diff --git a/apps/official/AlekSIS-App-Untis b/apps/official/AlekSIS-App-Untis
deleted file mode 160000
index 2aacff98111708c558c95849b72509f6c21a348f..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-Untis
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 2aacff98111708c558c95849b72509f6c21a348f
diff --git a/pyproject.toml b/pyproject.toml
index 1c3bdb77b59e270f075470b6b0dd82525d126073..99d24b842d98224ee165ce0ee0191a50a6523b01 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,6 +63,7 @@ django-celery-results = {version="^1.1.2", optional=true}
 django-celery-beat = {version="^1.5.0", optional=true}
 django-celery-email = {version="^3.0.0", optional=true}
 django-jsonstore = "^0.4.1"
+django-polymorphic = "^2.1.2"
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]