diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index dc5a3275ceea0f049f2e61782efcb796e2deff18..aa0cdbc0d149f9e597899d04054e1007fd04770e 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -9,6 +9,57 @@ and this project adheres to `Semantic Versioning`_.
 Unreleased
 ----------
 
+Added
+~~~~~
+
+* Allow to disable exception mails to admins
+* Add possibility to create iCal feeds in all apps and dynamically create user-specific urls.
+
+Fixed
+~~~~~
+
+* The menu button used to be displayed twice on smaller screens.
+* The icons were loaded from external servers instead from local server.
+* Weekdays were not translated if system locales were missing
+  * Added locales-all to base image and note to docs
+* The icons in the account menu were still the old ones.
+* Due to a merge error, the once removed account menu in the sidenav appeared again.
+* Scheduled notifications were shown on dashboard before time.
+* Remove broken notifications menu item in favor of item next to account menu.
+* Serve OAuth discovery information under root of domain
+
+Changed
+~~~~~~~
+
+* [Dev] ActionForm now checks permissions on objects before executing
+* [Dev] ActionForm now returns a proper return value from the executed action
+
+2.8.1`_ - 2022-03-13
+--------------------
+
+Changed
+~~~~~~~
+
+* Official apps can now override any setting
+
+`2.8`_ - 2022-03-11
+-------------------
+
+Added
+~~~~~
+
+* Add iconify icons
+* Use identicons where avatars are missing.
+* Display personal photos instead of avatars based on a site preference.
+* Add an account menu in the top navbar.
+* Create a reusable snippet for avatar content.
+* Allow to configure if additional field is required
+* Allow to configure description of additional fields
+* Allow configuring regex for allowed usernames
+* [Dev] Support scheduled notifications.
+* Implement StaticContentWidget
+* Allow to enable password change independently of password reset
+
 Changed
 ~~~~~~~
 
@@ -17,7 +68,12 @@ Changed
 Fixed
 ~~~~~
 
-* Serve OAuth discovery information under root of domain
+* The ``reset password`` button on the login site used to overflow the card on smaller devices.
+
+Deprecated
+~~~~~~~~~~
+
+* Legacy material icon font will be removed in AlekSIS-Core 3.0
 
 `2.7.4`_ - 2022-02-09
 ---------------------
@@ -763,3 +819,5 @@ Fixed
 .. _2.7.2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.7.2
 .. _2.7.3: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.7.3
 .. _2.7.4: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.7.4
+.. _2.8: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.8
+.. _2.8.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.8.1
diff --git a/Dockerfile b/Dockerfile
index acb08f98ffe79dccc32f048590bb123c2763302f..4864ac54613a8823fbc118fe73291e127b7939e1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -36,6 +36,7 @@ RUN apt-get -y update && \
         less \
 	libpq-dev \
 	libssl-dev \
+	locales-all \
 	postgresql-client-14 \
         pspg \
 	python3-dev \
diff --git a/README.rst b/README.rst
index a6732de3b038f5d1d4f5b59d2c05f3c77840c2b9..290669c5779841ac7713f7f8004cc3141de0b1f9 100644
--- a/README.rst
+++ b/README.rst
@@ -64,7 +64,7 @@ Licence
   Copyright © 2017, 2018, 2019, 2020, 2021, 2022 Jonathan Weth <dev@jonathanweth.de>
   Copyright © 2017, 2018, 2019, 2020 Frank Poetzsch-Heffter <p-h@katharineum.de>
   Copyright © 2018, 2019, 2020, 2021, 2022 Hangzhi Yu <yuha@katharineum.de>
-  Copyright © 2018, 2019, 2020, 2021 Julian Leucker <leuckeju@katharineum.de>
+  Copyright © 2018, 2019, 2020, 2021, 2022 Julian Leucker <leuckeju@katharineum.de>
   Copyright © 2019, 2020, 2021, 2022 Dominik George <dominik.george@teckids.org>
   Copyright © 2019, 2020, 2021, 2022 Tom Teichler <tom.teichler@teckids.org>
   Copyright © 2019 mirabilos <thorsten.glaser@teckids.org>
diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py
index 77e4b2a6327d9b88254b160375b1ce85be73ed68..08c965a02a7d6d0c4c53fb1bbcc5919ad35f7b7e 100644
--- a/aleksis/core/apps.py
+++ b/aleksis/core/apps.py
@@ -39,7 +39,7 @@ class CoreConfig(AppConfig):
         ([2017, 2018, 2019, 2020, 2021, 2022], "Jonathan Weth", "wethjo@katharineum.de"),
         ([2017, 2018, 2019, 2020], "Frank Poetzsch-Heffter", "p-h@katharineum.de"),
         ([2018, 2019, 2020, 2021, 2022], "Hangzhi Yu", "yuha@katharineum.de"),
-        ([2018, 2019, 2020, 2021], "Julian Leucker", "leuckeju@katharineum.de"),
+        ([2018, 2019, 2020, 2021, 2022], "Julian Leucker", "leuckeju@katharineum.de"),
         ([2019, 2020, 2021, 2022], "Dominik George", "dominik.george@teckids.org"),
         ([2019, 2020, 2021, 2022], "Tom Teichler", "tom.teichler@teckids.org"),
         ([2019], "mirabilos", "thorsten.glaser@teckids.org"),
diff --git a/aleksis/core/feeds.py b/aleksis/core/feeds.py
new file mode 100644
index 0000000000000000000000000000000000000000..fdfd6bc1f1123683f15e8b4baeada483892fa8f5
--- /dev/null
+++ b/aleksis/core/feeds.py
@@ -0,0 +1,80 @@
+from django.conf import settings
+from django.utils.formats import date_format
+from django.utils.functional import classproperty
+from django.utils.translation import gettext_lazy as _
+
+from django_ical.utils import build_rrule_from_text
+from django_ical.views import ICalFeed
+
+from aleksis.core import models
+from aleksis.core.util.core_helpers import get_site_preferences, queryset_rules_filter
+
+
+class PersonalICalFeedBase(ICalFeed):
+    """Base class for personal iCal feeds."""
+
+    @property
+    def product_id(self):
+        lang = self.request.LANGUAGE_CODE
+        title = get_site_preferences()["general__title"]
+        return f"-//AlekSIS//{title}//{lang}"
+
+    link = settings.BASE_URL
+    timezone = settings.TIME_ZONE
+    person = None
+    request = None
+
+    def get_object(self, request, *args, **kwargs):
+        if kwargs.get("person"):
+            self.person = kwargs.pop("person")
+        self.request = request
+        return super().get_object(request, *args, **kwargs)
+
+    @classproperty
+    def subclasses_list(cls):
+        return cls.__subclasses__()
+
+    @classproperty
+    def subclasses_dict(cls):
+        return {subclass.__name__: subclass for subclass in cls.subclasses_list}
+
+    @classproperty
+    def subclass_choices(cls):
+        return [
+            (subclass.__name__, f"{subclass.title} – {subclass.description}")
+            for subclass in cls.subclasses_list
+        ]
+
+
+class BirthdayFeed(PersonalICalFeedBase):
+    """Birthday calendar feed."""
+
+    title = _("Birthday Calendar")
+    description = _("A Calendar of Birthdays")
+    file_name = "birthdays.ics"
+
+    def items(self):
+        from aleksis.core.models import Person
+
+        return queryset_rules_filter(
+            obj=self.person.user,
+            perm="core.view_personal_details_rule",
+            queryset=Person.objects.filter(date_of_birth__isnull=False),
+        )
+
+    def item_title(self, item: "models.Person"):
+        return _("%(name)s's birthday") % {
+            "name": item.addressing_name,
+        }
+
+    def item_description(self, item: "models.Person"):
+        return _("%(name)s was born on %(birthday)s") % {
+            "name": item.addressing_name,
+            "birthday": date_format(item.date_of_birth),
+        }
+
+    def item_start_datetime(self, item):
+        return item.date_of_birth
+
+    def item_rrule(self, item):
+        return build_rrule_from_text("FREQ=YEARLY")
diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py
index ef4184812f73d22bb1dcfa1ff84f74f722c3c774..3fb938822b093e250299a3fac04f0e0358e6f47a 100644
--- a/aleksis/core/forms.py
+++ b/aleksis/core/forms.py
@@ -39,7 +39,7 @@ from .registries import (
     site_preferences_registry,
 )
 from .util.auth_helpers import AppScopes
-from .util.core_helpers import get_site_preferences
+from .util.core_helpers import get_site_preferences, queryset_rules_filter
 
 
 class PersonForm(ExtensibleForm):
@@ -722,17 +722,37 @@ class ActionForm(forms.Form):
         self.fields["selected_objects"].queryset = self.queryset
         self.fields["action"].choices = self._get_action_choices()
 
-    def execute(self) -> bool:
+    def clean_action(self):
+        action = self._get_actions_dict().get(self.cleaned_data["action"], None)
+        if not action:
+            raise ValidationError(_("The selected action does not exist."))
+        return action
+
+    def clean_selected_objects(self):
+        action = self.cleaned_data["action"]
+        if hasattr(action, "permission"):
+            selected_objects = queryset_rules_filter(
+                self.request, self.cleaned_data["selected_objects"], action.permission
+            )
+            if selected_objects.count() < self.cleaned_data["selected_objects"].count():
+                raise ValidationError(
+                    _("You do not have permission to run {} on all selected objects.").format(
+                        getattr(value, "short_description", value.__name__)
+                    )
+                )
+        return self.cleaned_data["selected_objects"]
+
+    def execute(self) -> Any:
         """Execute the selected action on all selected objects.
 
-        :return: If the form is not valid, it will return ``False``.
+        :return: the return value of the action
         """
         if self.is_valid():
             data = self.cleaned_data["selected_objects"]
-            action = self._get_actions_dict()[self.cleaned_data["action"]]
-            action(None, self.request, data)
-            return True
-        return False
+            action = self.cleaned_data["action"]
+            return action(None, self.request, data)
+
+        raise TypeError("execute() must be called on a pre-validated form.")
 
 
 class ListActionForm(ActionForm):
diff --git a/aleksis/core/locale/ar/LC_MESSAGES/django.po b/aleksis/core/locale/ar/LC_MESSAGES/django.po
index a382635f245835c9c747b72cb4b661bd39eb7e04..ee356cc39b7d01a0aad1fd7fa3e0343f42da7a6c 100644
--- a/aleksis/core/locale/ar/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/ar/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: AlekSIS (School Information System) 0.1\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:19+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -38,10 +38,10 @@ msgstr ""
 msgid "Home and mobile phone"
 msgstr ""
 
-#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:265
-#: aleksis/core/models.py:462 aleksis/core/templates/core/group/list.html:8
+#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:185
+#: aleksis/core/models.py:487 aleksis/core/templates/core/group/list.html:8
 #: aleksis/core/templates/core/group/list.html:9
-#: aleksis/core/templates/core/person/full.html:250
+#: aleksis/core/templates/core/person/full.html:246
 msgid "Groups"
 msgstr ""
 
@@ -66,8 +66,8 @@ msgstr ""
 msgid "The DashboardWidget was reported broken automatically."
 msgstr ""
 
-#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:105
-#: aleksis/core/templates/core/base.html:106
+#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:139
+#: aleksis/core/templates/core/base.html:140
 #: aleksis/core/templates/core/group/list.html:20
 #: aleksis/core/templates/core/person/list.html:24
 #: aleksis/core/templates/search/search.html:7
@@ -91,11 +91,11 @@ msgstr ""
 msgid "Content type"
 msgstr ""
 
-#: aleksis/core/filters.py:113 aleksis/core/models.py:688
+#: aleksis/core/filters.py:113 aleksis/core/models.py:713
 msgid "User"
 msgstr ""
 
-#: aleksis/core/filters.py:135 aleksis/core/models.py:461
+#: aleksis/core/filters.py:135 aleksis/core/models.py:486
 msgid "Group"
 msgstr ""
 
@@ -131,7 +131,7 @@ msgstr ""
 msgid "This username is already in use."
 msgstr ""
 
-#: aleksis/core/forms.py:153 aleksis/core/models.py:130
+#: aleksis/core/forms.py:153 aleksis/core/models.py:134
 msgid "School term"
 msgstr ""
 
@@ -140,7 +140,7 @@ msgid "Common data"
 msgstr ""
 
 #: aleksis/core/forms.py:155 aleksis/core/forms.py:207
-#: aleksis/core/menus.py:254 aleksis/core/models.py:153
+#: aleksis/core/menus.py:174 aleksis/core/models.py:157
 #: aleksis/core/templates/core/person/list.html:8
 #: aleksis/core/templates/core/person/list.html:9
 msgid "Persons"
@@ -150,18 +150,18 @@ msgstr ""
 msgid "Additional data"
 msgstr ""
 
-#: aleksis/core/forms.py:157 aleksis/core/models.py:206
-#: aleksis/core/models.py:514
+#: aleksis/core/forms.py:157 aleksis/core/models.py:210
+#: aleksis/core/models.py:539
 msgid "Photo"
 msgstr ""
 
 #: aleksis/core/forms.py:199 aleksis/core/forms.py:202
-#: aleksis/core/models.py:73
+#: aleksis/core/models.py:77
 msgid "Date"
 msgstr ""
 
 #: aleksis/core/forms.py:200 aleksis/core/forms.py:203
-#: aleksis/core/models.py:81
+#: aleksis/core/models.py:85
 msgid "Time"
 msgstr ""
 
@@ -197,11 +197,11 @@ msgstr ""
 msgid "Please enter your invitation code."
 msgstr ""
 
-#: aleksis/core/forms.py:418 aleksis/core/models.py:181
+#: aleksis/core/forms.py:418 aleksis/core/models.py:185
 msgid "First name"
 msgstr ""
 
-#: aleksis/core/forms.py:419 aleksis/core/models.py:182
+#: aleksis/core/forms.py:419 aleksis/core/models.py:186
 msgid "Last name"
 msgstr ""
 
@@ -249,7 +249,15 @@ msgstr ""
 msgid "Password (again)"
 msgstr ""
 
-#: aleksis/core/forms.py:775
+#: aleksis/core/forms.py:728
+msgid "The selected action does not exist."
+msgstr ""
+
+#: aleksis/core/forms.py:739
+msgid "You do not have permission to run {} on all selected objects."
+msgstr ""
+
+#: aleksis/core/forms.py:795
 msgid "No valid selection."
 msgstr ""
 
@@ -292,678 +300,698 @@ msgstr ""
 msgid "Dashboard"
 msgstr ""
 
-#: aleksis/core/menus.py:41 aleksis/core/models.py:734
-#: aleksis/core/preferences.py:29
+#: aleksis/core/menus.py:41 aleksis/core/models.py:765
+#: aleksis/core/preferences.py:29 aleksis/core/templates/core/base.html:81
 #: aleksis/core/templates/core/notifications.html:4
 #: aleksis/core/templates/core/notifications.html:5
 msgid "Notifications"
 msgstr ""
 
 #: aleksis/core/menus.py:53
-msgid "Account"
-msgstr ""
-
-#: aleksis/core/menus.py:60
-msgid "Stop impersonation"
-msgstr ""
-
-#: aleksis/core/menus.py:69 aleksis/core/templates/core/base.html:80
-msgid "Logout"
-msgstr ""
-
-#: aleksis/core/menus.py:75
-msgid "2FA"
-msgstr ""
-
-#: aleksis/core/menus.py:83
-#: aleksis/core/templates/account/password_change.html:5
-#: aleksis/core/templates/account/password_change.html:6
-#: aleksis/core/templates/account/password_change.html:19
-#: aleksis/core/templates/account/password_reset_from_key.html:5
-#: aleksis/core/templates/account/password_reset_from_key.html:42
-#: aleksis/core/templates/account/password_reset_from_key.html:46
-#: aleksis/core/templates/account/password_reset_from_key_done.html:5
-#: aleksis/core/templates/account/password_reset_from_key_done.html:6
-msgid "Change password"
-msgstr ""
-
-#: aleksis/core/menus.py:95
-msgid "Me"
-msgstr ""
-
-#: aleksis/core/menus.py:104
-#: aleksis/core/templates/dynamic_preferences/form.html:5
-msgid "Preferences"
-msgstr ""
-
-#: aleksis/core/menus.py:113
-msgid "Third-party accounts"
-msgstr ""
-
-#: aleksis/core/menus.py:122
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
-msgid "Authorized applications"
-msgstr ""
-
-#: aleksis/core/menus.py:133
 msgid "Admin"
 msgstr ""
 
-#: aleksis/core/menus.py:141 aleksis/core/models.py:834
+#: aleksis/core/menus.py:61 aleksis/core/models.py:865
 #: aleksis/core/templates/core/announcement/list.html:7
 #: aleksis/core/templates/core/announcement/list.html:8
 msgid "Announcements"
 msgstr ""
 
-#: aleksis/core/menus.py:152 aleksis/core/models.py:131
+#: aleksis/core/menus.py:72 aleksis/core/models.py:135
 #: aleksis/core/templates/core/school_term/list.html:8
 #: aleksis/core/templates/core/school_term/list.html:9
 msgid "School terms"
 msgstr ""
 
-#: aleksis/core/menus.py:163
+#: aleksis/core/menus.py:83
 #: aleksis/core/templates/core/dashboard_widget/list.html:8
 #: aleksis/core/templates/core/dashboard_widget/list.html:9
 msgid "Dashboard widgets"
 msgstr ""
 
-#: aleksis/core/menus.py:174
+#: aleksis/core/menus.py:94
 #: aleksis/core/templates/core/management/data_management.html:6
 #: aleksis/core/templates/core/management/data_management.html:7
 msgid "Data management"
 msgstr ""
 
-#: aleksis/core/menus.py:185
+#: aleksis/core/menus.py:105
 #: aleksis/core/templates/core/pages/system_status.html:5
 #: aleksis/core/templates/core/pages/system_status.html:7
 msgid "System status"
 msgstr ""
 
-#: aleksis/core/menus.py:196
+#: aleksis/core/menus.py:116
 msgid "Configuration"
 msgstr ""
 
-#: aleksis/core/menus.py:207 aleksis/core/templates/core/data_check/list.html:9
+#: aleksis/core/menus.py:127 aleksis/core/templates/core/data_check/list.html:9
 #: aleksis/core/templates/core/data_check/list.html:10
 msgid "Data checks"
 msgstr ""
 
-#: aleksis/core/menus.py:213 aleksis/core/templates/core/perms/list.html:13
+#: aleksis/core/menus.py:133 aleksis/core/templates/core/perms/list.html:13
 #: aleksis/core/templates/core/perms/list.html:14
 msgid "Manage permissions"
 msgstr ""
 
-#: aleksis/core/menus.py:224
+#: aleksis/core/menus.py:144
 msgid "Backend Admin"
 msgstr ""
 
-#: aleksis/core/menus.py:232
+#: aleksis/core/menus.py:152
 #: aleksis/core/templates/oauth2_provider/application/list.html:5
 #: aleksis/core/templates/oauth2_provider/application/list.html:6
 msgid "OAuth2 Applications"
 msgstr ""
 
-#: aleksis/core/menus.py:245
+#: aleksis/core/menus.py:165
 msgid "People"
 msgstr ""
 
-#: aleksis/core/menus.py:276 aleksis/core/models.py:1055
+#: aleksis/core/menus.py:196 aleksis/core/models.py:1099
 #: aleksis/core/templates/core/group_type/list.html:8
 #: aleksis/core/templates/core/group_type/list.html:9
 msgid "Group types"
 msgstr ""
 
-#: aleksis/core/menus.py:287
+#: aleksis/core/menus.py:207
 msgid "Groups and child groups"
 msgstr ""
 
-#: aleksis/core/menus.py:298 aleksis/core/models.py:510
+#: aleksis/core/menus.py:218 aleksis/core/models.py:535
 #: aleksis/core/templates/core/additional_field/list.html:8
 #: aleksis/core/templates/core/additional_field/list.html:9
 msgid "Additional fields"
 msgstr ""
 
-#: aleksis/core/menus.py:309
+#: aleksis/core/menus.py:229
 msgid "Invite person"
 msgstr ""
 
-#: aleksis/core/menus.py:322
+#: aleksis/core/menus.py:242
 #: aleksis/core/templates/core/group/child_groups.html:7
 #: aleksis/core/templates/core/group/child_groups.html:9
 msgid "Assign child groups to groups"
 msgstr ""
 
+#: aleksis/core/menus.py:254
+msgid "Stop impersonation"
+msgstr ""
+
+#: aleksis/core/menus.py:263
+msgid "Account"
+msgstr ""
+
+#: aleksis/core/menus.py:272
+#: aleksis/core/templates/dynamic_preferences/form.html:5
+msgid "Preferences"
+msgstr ""
+
+#: aleksis/core/menus.py:281
+msgid "2FA"
+msgstr ""
+
+#: aleksis/core/menus.py:289
+#: aleksis/core/templates/account/password_change.html:5
+#: aleksis/core/templates/account/password_change.html:6
+#: aleksis/core/templates/account/password_change.html:19
+#: aleksis/core/templates/account/password_reset_from_key.html:5
+#: aleksis/core/templates/account/password_reset_from_key.html:42
+#: aleksis/core/templates/account/password_reset_from_key.html:46
+#: aleksis/core/templates/account/password_reset_from_key_done.html:5
+#: aleksis/core/templates/account/password_reset_from_key_done.html:6
+msgid "Change password"
+msgstr ""
+
+#: aleksis/core/menus.py:301
+msgid "Third-party accounts"
+msgstr ""
+
+#: aleksis/core/menus.py:310
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
+msgid "Authorized applications"
+msgstr ""
+
+#: aleksis/core/menus.py:320
+msgid "Logout"
+msgstr ""
+
 #: aleksis/core/mixins.py:511
 msgid "Linked school term"
 msgstr ""
 
-#: aleksis/core/models.py:71
+#: aleksis/core/models.py:75
 msgid "Boolean (Yes/No)"
 msgstr ""
 
-#: aleksis/core/models.py:72
+#: aleksis/core/models.py:76
 msgid "Text (one line)"
 msgstr ""
 
-#: aleksis/core/models.py:74
+#: aleksis/core/models.py:78
 msgid "Date and time"
 msgstr ""
 
-#: aleksis/core/models.py:75
+#: aleksis/core/models.py:79
 msgid "Decimal number"
 msgstr ""
 
-#: aleksis/core/models.py:76 aleksis/core/models.py:199
+#: aleksis/core/models.py:80 aleksis/core/models.py:203
 msgid "E-mail address"
 msgstr ""
 
-#: aleksis/core/models.py:77
+#: aleksis/core/models.py:81
 msgid "Integer"
 msgstr ""
 
-#: aleksis/core/models.py:78
+#: aleksis/core/models.py:82
 msgid "IP address"
 msgstr ""
 
-#: aleksis/core/models.py:79
+#: aleksis/core/models.py:83
 msgid "Boolean or empty (Yes/No/Neither)"
 msgstr ""
 
-#: aleksis/core/models.py:80
+#: aleksis/core/models.py:84
 msgid "Text (multi-line)"
 msgstr ""
 
-#: aleksis/core/models.py:82
+#: aleksis/core/models.py:86
 msgid "URL / Link"
 msgstr ""
 
-#: aleksis/core/models.py:94 aleksis/core/models.py:1024
+#: aleksis/core/models.py:98 aleksis/core/models.py:1068
 msgid "Name"
 msgstr ""
 
-#: aleksis/core/models.py:96
+#: aleksis/core/models.py:100
 msgid "Start date"
 msgstr ""
 
-#: aleksis/core/models.py:97
+#: aleksis/core/models.py:101
 msgid "End date"
 msgstr ""
 
-#: aleksis/core/models.py:116
+#: aleksis/core/models.py:120
 msgid "The start date must be earlier than the end date."
 msgstr ""
 
-#: aleksis/core/models.py:123
+#: aleksis/core/models.py:127
 msgid "There is already a school term for this time or a part of this time."
 msgstr ""
 
-#: aleksis/core/models.py:152 aleksis/core/models.py:973
+#: aleksis/core/models.py:156 aleksis/core/models.py:1017
 msgid "Person"
 msgstr ""
 
-#: aleksis/core/models.py:155
+#: aleksis/core/models.py:159
 msgid "Can view address"
 msgstr ""
 
-#: aleksis/core/models.py:156
+#: aleksis/core/models.py:160
 msgid "Can view contact details"
 msgstr ""
 
-#: aleksis/core/models.py:157
+#: aleksis/core/models.py:161
 msgid "Can view photo"
 msgstr ""
 
-#: aleksis/core/models.py:158
+#: aleksis/core/models.py:162
 msgid "Can view avatar image"
 msgstr ""
 
-#: aleksis/core/models.py:159
+#: aleksis/core/models.py:163
 msgid "Can view persons groups"
 msgstr ""
 
-#: aleksis/core/models.py:160
+#: aleksis/core/models.py:164
 msgid "Can view personal details"
 msgstr ""
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "female"
 msgstr ""
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "male"
 msgstr ""
 
-#: aleksis/core/models.py:178 aleksis/core/models.py:1227
+#: aleksis/core/models.py:182 aleksis/core/models.py:1271
 msgid "Linked user"
 msgstr ""
 
-#: aleksis/core/models.py:184
+#: aleksis/core/models.py:188
 msgid "Additional name(s)"
 msgstr ""
 
-#: aleksis/core/models.py:188 aleksis/core/models.py:479
+#: aleksis/core/models.py:192 aleksis/core/models.py:504
 msgid "Short name"
 msgstr ""
 
-#: aleksis/core/models.py:191
+#: aleksis/core/models.py:195
 msgid "Street"
 msgstr ""
 
-#: aleksis/core/models.py:192
+#: aleksis/core/models.py:196
 msgid "Street number"
 msgstr ""
 
-#: aleksis/core/models.py:193
+#: aleksis/core/models.py:197
 msgid "Postal code"
 msgstr ""
 
-#: aleksis/core/models.py:194
+#: aleksis/core/models.py:198
 msgid "Place"
 msgstr ""
 
-#: aleksis/core/models.py:196 aleksis/core/templates/core/person/full.html:172
+#: aleksis/core/models.py:200 aleksis/core/templates/core/person/full.html:160
 msgid "Home phone"
 msgstr ""
 
-#: aleksis/core/models.py:197 aleksis/core/templates/core/person/full.html:182
+#: aleksis/core/models.py:201 aleksis/core/templates/core/person/full.html:170
 msgid "Mobile phone"
 msgstr ""
 
-#: aleksis/core/models.py:201
+#: aleksis/core/models.py:205
 msgid "Date of birth"
 msgstr ""
 
-#: aleksis/core/models.py:202
+#: aleksis/core/models.py:206
 msgid "Place of birth"
 msgstr ""
 
-#: aleksis/core/models.py:203
+#: aleksis/core/models.py:207
 msgid "Sex"
 msgstr ""
 
-#: aleksis/core/models.py:210 aleksis/core/models.py:518
+#: aleksis/core/models.py:214 aleksis/core/models.py:543
 msgid "This is an official photo, used for official documents and for internal use cases."
 msgstr ""
 
-#: aleksis/core/models.py:215 aleksis/core/models.py:522
+#: aleksis/core/models.py:219 aleksis/core/models.py:547
 msgid "Display picture / Avatar"
 msgstr ""
 
-#: aleksis/core/models.py:218 aleksis/core/models.py:525
+#: aleksis/core/models.py:222 aleksis/core/models.py:550
 msgid "This is a picture or an avatar for public display."
 msgstr ""
 
-#: aleksis/core/models.py:223 aleksis/core/templates/core/person/full.html:239
+#: aleksis/core/models.py:227 aleksis/core/templates/core/person/full.html:235
 msgid "Guardians / Parents"
 msgstr ""
 
-#: aleksis/core/models.py:230
+#: aleksis/core/models.py:234
 msgid "Primary group"
 msgstr ""
 
-#: aleksis/core/models.py:233 aleksis/core/models.py:692
-#: aleksis/core/models.py:716 aleksis/core/models.py:801
-#: aleksis/core/models.py:1048
+#: aleksis/core/models.py:237 aleksis/core/models.py:717
+#: aleksis/core/models.py:741 aleksis/core/models.py:832
+#: aleksis/core/models.py:1092
 msgid "Description"
 msgstr ""
 
-#: aleksis/core/models.py:434
+#: aleksis/core/models.py:457
 msgid "Title of field"
 msgstr ""
 
-#: aleksis/core/models.py:436
+#: aleksis/core/models.py:459
 msgid "Type of field"
 msgstr ""
 
-#: aleksis/core/models.py:443
+#: aleksis/core/models.py:461
+msgid "Required"
+msgstr ""
+
+#: aleksis/core/models.py:462
+msgid "Help text / description"
+msgstr ""
+
+#: aleksis/core/models.py:468
 msgid "Addtitional field for groups"
 msgstr ""
 
-#: aleksis/core/models.py:444
+#: aleksis/core/models.py:469
 msgid "Addtitional fields for groups"
 msgstr ""
 
-#: aleksis/core/models.py:464
+#: aleksis/core/models.py:489
 msgid "Can assign child groups to groups"
 msgstr ""
 
-#: aleksis/core/models.py:465
+#: aleksis/core/models.py:490
 msgid "Can view statistics about group."
 msgstr ""
 
-#: aleksis/core/models.py:477
+#: aleksis/core/models.py:502
 msgid "Long name"
 msgstr ""
 
-#: aleksis/core/models.py:487 aleksis/core/templates/core/group/full.html:85
+#: aleksis/core/models.py:512 aleksis/core/templates/core/group/full.html:85
 msgid "Members"
 msgstr ""
 
-#: aleksis/core/models.py:490 aleksis/core/templates/core/group/full.html:82
+#: aleksis/core/models.py:515 aleksis/core/templates/core/group/full.html:82
 msgid "Owners"
 msgstr ""
 
-#: aleksis/core/models.py:497 aleksis/core/templates/core/group/full.html:55
+#: aleksis/core/models.py:522 aleksis/core/templates/core/group/full.html:55
 msgid "Parent groups"
 msgstr ""
 
-#: aleksis/core/models.py:505
+#: aleksis/core/models.py:530
 msgid "Type of group"
 msgstr ""
 
-#: aleksis/core/models.py:691 aleksis/core/models.py:715
-#: aleksis/core/models.py:800
+#: aleksis/core/models.py:716 aleksis/core/models.py:740
+#: aleksis/core/models.py:831
 #: aleksis/core/templates/core/announcement/list.html:18
 msgid "Title"
 msgstr ""
 
-#: aleksis/core/models.py:694
+#: aleksis/core/models.py:719
 msgid "Application"
 msgstr ""
 
-#: aleksis/core/models.py:700
+#: aleksis/core/models.py:725
 msgid "Activity"
 msgstr ""
 
-#: aleksis/core/models.py:701
+#: aleksis/core/models.py:726
 msgid "Activities"
 msgstr ""
 
-#: aleksis/core/models.py:707
+#: aleksis/core/models.py:732
 msgid "Sender"
 msgstr ""
 
-#: aleksis/core/models.py:712
+#: aleksis/core/models.py:737
 msgid "Recipient"
 msgstr ""
 
-#: aleksis/core/models.py:717 aleksis/core/models.py:1025
+#: aleksis/core/models.py:742 aleksis/core/models.py:1069
 msgid "Link"
 msgstr ""
 
-#: aleksis/core/models.py:719
+#: aleksis/core/models.py:744
+msgid "Send notification at"
+msgstr ""
+
+#: aleksis/core/models.py:746
 msgid "Read"
 msgstr ""
 
-#: aleksis/core/models.py:720
+#: aleksis/core/models.py:747
 msgid "Sent"
 msgstr ""
 
-#: aleksis/core/models.py:733
+#: aleksis/core/models.py:764
 msgid "Notification"
 msgstr ""
 
-#: aleksis/core/models.py:802
+#: aleksis/core/models.py:833
 msgid "Link to detailed view"
 msgstr ""
 
-#: aleksis/core/models.py:805
+#: aleksis/core/models.py:836
 msgid "Date and time from when to show"
 msgstr ""
 
-#: aleksis/core/models.py:808
+#: aleksis/core/models.py:839
 msgid "Date and time until when to show"
 msgstr ""
 
-#: aleksis/core/models.py:833
+#: aleksis/core/models.py:864
 msgid "Announcement"
 msgstr ""
 
-#: aleksis/core/models.py:871
+#: aleksis/core/models.py:902
 msgid "Announcement recipient"
 msgstr ""
 
-#: aleksis/core/models.py:872
+#: aleksis/core/models.py:903
 msgid "Announcement recipients"
 msgstr ""
 
-#: aleksis/core/models.py:894
+#: aleksis/core/models.py:925
 msgid "Widget Title"
 msgstr ""
 
-#: aleksis/core/models.py:895
+#: aleksis/core/models.py:926
 msgid "Activate Widget"
 msgstr ""
 
-#: aleksis/core/models.py:896
+#: aleksis/core/models.py:927
 msgid "Widget is broken"
 msgstr ""
 
-#: aleksis/core/models.py:899
+#: aleksis/core/models.py:930
 msgid "Size on mobile devices"
 msgstr ""
 
-#: aleksis/core/models.py:900
+#: aleksis/core/models.py:931
 msgid "<= 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:905
+#: aleksis/core/models.py:936
 msgid "Size on tablet devices"
 msgstr ""
 
-#: aleksis/core/models.py:906
+#: aleksis/core/models.py:937
 msgid "> 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:911
+#: aleksis/core/models.py:942
 msgid "Size on desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:912
+#: aleksis/core/models.py:943
 msgid "> 992 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:917
+#: aleksis/core/models.py:948
 msgid "Size on large desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:918
+#: aleksis/core/models.py:949
 msgid "> 1200 px>, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:949
+#: aleksis/core/models.py:980
 msgid "Can edit default dashboard"
 msgstr ""
 
-#: aleksis/core/models.py:950
+#: aleksis/core/models.py:981
 msgid "Dashboard Widget"
 msgstr ""
 
-#: aleksis/core/models.py:951
+#: aleksis/core/models.py:982
 msgid "Dashboard Widgets"
 msgstr ""
 
-#: aleksis/core/models.py:957
+#: aleksis/core/models.py:988
 msgid "URL"
 msgstr ""
 
-#: aleksis/core/models.py:958
+#: aleksis/core/models.py:989
 msgid "Icon URL"
 msgstr ""
 
-#: aleksis/core/models.py:964
+#: aleksis/core/models.py:995
 msgid "External link widget"
 msgstr ""
 
-#: aleksis/core/models.py:965
+#: aleksis/core/models.py:996
 msgid "External link widgets"
 msgstr ""
 
-#: aleksis/core/models.py:970
+#: aleksis/core/models.py:1002
+msgid "Content"
+msgstr ""
+
+#: aleksis/core/models.py:1008
+msgid "Static content widget"
+msgstr ""
+
+#: aleksis/core/models.py:1009
+msgid "Static content widgets"
+msgstr ""
+
+#: aleksis/core/models.py:1014
 msgid "Dashboard widget"
 msgstr ""
 
-#: aleksis/core/models.py:975
+#: aleksis/core/models.py:1019
 msgid "Order"
 msgstr ""
 
-#: aleksis/core/models.py:976
+#: aleksis/core/models.py:1020
 msgid "Part of the default dashboard"
 msgstr ""
 
-#: aleksis/core/models.py:991
+#: aleksis/core/models.py:1035
 msgid "Dashboard widget order"
 msgstr ""
 
-#: aleksis/core/models.py:992
+#: aleksis/core/models.py:1036
 msgid "Dashboard widget orders"
 msgstr ""
 
-#: aleksis/core/models.py:998
+#: aleksis/core/models.py:1042
 msgid "Menu ID"
 msgstr ""
 
-#: aleksis/core/models.py:1011
+#: aleksis/core/models.py:1055
 msgid "Custom menu"
 msgstr ""
 
-#: aleksis/core/models.py:1012
+#: aleksis/core/models.py:1056
 msgid "Custom menus"
 msgstr ""
 
-#: aleksis/core/models.py:1022
+#: aleksis/core/models.py:1066
 msgid "Menu"
 msgstr ""
 
-#: aleksis/core/models.py:1026 aleksis/core/models.py:1274
+#: aleksis/core/models.py:1070 aleksis/core/models.py:1318
 #: aleksis/core/templates/oauth2_provider/application/detail.html:26
 msgid "Icon"
 msgstr ""
 
-#: aleksis/core/models.py:1032
+#: aleksis/core/models.py:1076
 msgid "Custom menu item"
 msgstr ""
 
-#: aleksis/core/models.py:1033
+#: aleksis/core/models.py:1077
 msgid "Custom menu items"
 msgstr ""
 
-#: aleksis/core/models.py:1047
+#: aleksis/core/models.py:1091
 msgid "Title of type"
 msgstr ""
 
-#: aleksis/core/models.py:1054 aleksis/core/templates/core/group/full.html:47
+#: aleksis/core/models.py:1098 aleksis/core/templates/core/group/full.html:47
 msgid "Group type"
 msgstr ""
 
-#: aleksis/core/models.py:1068
+#: aleksis/core/models.py:1112
 msgid "Can view system status"
 msgstr ""
 
-#: aleksis/core/models.py:1069
+#: aleksis/core/models.py:1113
 msgid "Can manage data"
 msgstr ""
 
-#: aleksis/core/models.py:1070
+#: aleksis/core/models.py:1114
 msgid "Can impersonate"
 msgstr ""
 
-#: aleksis/core/models.py:1071
+#: aleksis/core/models.py:1115
 msgid "Can use search"
 msgstr ""
 
-#: aleksis/core/models.py:1072
+#: aleksis/core/models.py:1116
 msgid "Can change site preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1073
+#: aleksis/core/models.py:1117
 msgid "Can change person preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1074
+#: aleksis/core/models.py:1118
 msgid "Can change group preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1075
+#: aleksis/core/models.py:1119
 msgid "Can test PDF generation"
 msgstr ""
 
-#: aleksis/core/models.py:1076
+#: aleksis/core/models.py:1120
 msgid "Can invite persons"
 msgstr ""
 
-#: aleksis/core/models.py:1112
+#: aleksis/core/models.py:1156
 msgid "Related data check task"
 msgstr ""
 
-#: aleksis/core/models.py:1120
+#: aleksis/core/models.py:1164
 msgid "Issue solved"
 msgstr ""
 
-#: aleksis/core/models.py:1121
+#: aleksis/core/models.py:1165
 msgid "Notification sent"
 msgstr ""
 
-#: aleksis/core/models.py:1134
+#: aleksis/core/models.py:1178
 msgid "Data check result"
 msgstr ""
 
-#: aleksis/core/models.py:1135
+#: aleksis/core/models.py:1179
 msgid "Data check results"
 msgstr ""
 
-#: aleksis/core/models.py:1137
+#: aleksis/core/models.py:1181
 msgid "Can run data checks"
 msgstr ""
 
-#: aleksis/core/models.py:1138
+#: aleksis/core/models.py:1182
 msgid "Can solve data check problems"
 msgstr ""
 
-#: aleksis/core/models.py:1145
+#: aleksis/core/models.py:1189
 msgid "E-Mail address"
 msgstr ""
 
-#: aleksis/core/models.py:1177
+#: aleksis/core/models.py:1221
 msgid "Owner"
 msgstr ""
 
-#: aleksis/core/models.py:1181
+#: aleksis/core/models.py:1225
 msgid "File expires at"
 msgstr ""
 
-#: aleksis/core/models.py:1183
+#: aleksis/core/models.py:1227
 msgid "Generated HTML file"
 msgstr ""
 
-#: aleksis/core/models.py:1185
+#: aleksis/core/models.py:1229
 msgid "Generated PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1192
+#: aleksis/core/models.py:1236
 msgid "PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1193
+#: aleksis/core/models.py:1237
 msgid "PDF files"
 msgstr ""
 
-#: aleksis/core/models.py:1198
+#: aleksis/core/models.py:1242
 msgid "Task result"
 msgstr ""
 
-#: aleksis/core/models.py:1201
+#: aleksis/core/models.py:1245
 msgid "Task user"
 msgstr ""
 
-#: aleksis/core/models.py:1213
+#: aleksis/core/models.py:1257
 msgid "Task user assignment"
 msgstr ""
 
-#: aleksis/core/models.py:1214
+#: aleksis/core/models.py:1258
 msgid "Task user assignments"
 msgstr ""
 
-#: aleksis/core/models.py:1230
+#: aleksis/core/models.py:1274
 msgid "Additional attributes"
 msgstr ""
 
-#: aleksis/core/models.py:1268
+#: aleksis/core/models.py:1312
 msgid "Allowed scopes that clients can request"
 msgstr ""
 
-#: aleksis/core/models.py:1278
+#: aleksis/core/models.py:1322
 msgid "This image will be shown as icon in the authorization flow. It should be squared."
 msgstr ""
 
@@ -1084,86 +1112,98 @@ msgid "Allow users to change their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:279
-msgid "Enable signup"
+msgid "Allow users to reset their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:287
-msgid "Enable invitations"
+msgid "Enable signup"
 msgstr ""
 
 #: aleksis/core/preferences.py:295
-msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgid "Regular expression for allowed usernames"
 msgstr ""
 
 #: aleksis/core/preferences.py:303
+msgid "Enable invitations"
+msgstr ""
+
+#: aleksis/core/preferences.py:311
+msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgstr ""
+
+#: aleksis/core/preferences.py:319
 msgid "Size of packets. (Default 5: abcde)"
 msgstr ""
 
-#: aleksis/core/preferences.py:314
+#: aleksis/core/preferences.py:330
 msgid "Allowed Grant Flows for OAuth applications"
 msgstr ""
 
-#: aleksis/core/preferences.py:328
+#: aleksis/core/preferences.py:344
 msgid "Available languages"
 msgstr ""
 
-#: aleksis/core/preferences.py:341
+#: aleksis/core/preferences.py:357
 msgid "Send emails if data checks detect problems"
 msgstr ""
 
-#: aleksis/core/preferences.py:352
+#: aleksis/core/preferences.py:368
 msgid "Email recipients for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:363
+#: aleksis/core/preferences.py:379
 msgid "Email recipient groups for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:372
+#: aleksis/core/preferences.py:388
 msgid "Show dashboard to users without login"
 msgstr ""
 
-#: aleksis/core/preferences.py:381
+#: aleksis/core/preferences.py:397
 msgid "Allow users to edit their dashboard"
 msgstr ""
 
-#: aleksis/core/preferences.py:392
+#: aleksis/core/preferences.py:408
 msgid "Fields on person model which are editable by themselves."
 msgstr ""
 
-#: aleksis/core/preferences.py:407
+#: aleksis/core/preferences.py:423
 msgid "Editable fields on person model which should trigger a notification on change"
 msgstr ""
 
-#: aleksis/core/preferences.py:421
+#: aleksis/core/preferences.py:437
 msgid "Contact for notification if a person changes their data"
 msgstr ""
 
-#: aleksis/core/preferences.py:432
+#: aleksis/core/preferences.py:448
+msgid "Prefer personal photos over avatars"
+msgstr ""
+
+#: aleksis/core/preferences.py:458
 msgid "PDF file expiration duration"
 msgstr ""
 
-#: aleksis/core/preferences.py:433
+#: aleksis/core/preferences.py:459
 msgid "in minutes"
 msgstr ""
 
-#: aleksis/core/preferences.py:443
+#: aleksis/core/preferences.py:469
 msgid "Automatically update the dashboard and its widgets"
 msgstr ""
 
-#: aleksis/core/preferences.py:453
+#: aleksis/core/preferences.py:479
 msgid "Automatically update the dashboard and its widgets sitewide"
 msgstr ""
 
-#: aleksis/core/preferences.py:463
+#: aleksis/core/preferences.py:489
 msgid "Country for phone number parsing"
 msgstr ""
 
-#: aleksis/core/settings.py:529
+#: aleksis/core/settings.py:540
 msgid "English"
 msgstr ""
 
-#: aleksis/core/settings.py:530
+#: aleksis/core/settings.py:541
 msgid "German"
 msgstr ""
 
@@ -1171,7 +1211,7 @@ msgstr ""
 #: aleksis/core/templates/core/announcement/list.html:36
 #: aleksis/core/templates/core/group/full.html:24
 #: aleksis/core/templates/core/person/full.html:26
-#: aleksis/core/templates/core/person/full.html:98
+#: aleksis/core/templates/core/person/full.html:86
 #: aleksis/core/templates/oauth2_provider/application/detail.html:17
 msgid "Edit"
 msgstr ""
@@ -1189,7 +1229,7 @@ msgstr ""
 #: aleksis/core/templates/core/group/full.html:31
 #: aleksis/core/templates/core/pages/delete.html:22
 #: aleksis/core/templates/core/person/full.html:33
-#: aleksis/core/templates/core/person/full.html:105
+#: aleksis/core/templates/core/person/full.html:93
 #: aleksis/core/templates/oauth2_provider/application/detail.html:21
 msgid "Delete"
 msgstr ""
@@ -1236,6 +1276,10 @@ msgid ""
 "          "
 msgstr ""
 
+#: aleksis/core/templates/500.html:21
+msgid "Retry"
+msgstr ""
+
 #: aleksis/core/templates/503.html:10
 msgid ""
 "The maintenance mode is currently enabled. Please try again\n"
@@ -1494,19 +1538,15 @@ msgstr ""
 msgid "There are no announcements."
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:78
-msgid "Logged in as"
-msgstr ""
-
-#: aleksis/core/templates/core/base.html:179
+#: aleksis/core/templates/core/base.html:213
 msgid "About AlekSIS® — The Free School Information System"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:187
+#: aleksis/core/templates/core/base.html:221
 msgid "Imprint"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:195
+#: aleksis/core/templates/core/base.html:229
 msgid "Privacy Policy"
 msgstr ""
 
@@ -1727,7 +1767,7 @@ msgstr ""
 
 #: aleksis/core/templates/core/group/full.html:38
 #: aleksis/core/templates/core/person/full.html:40
-#: aleksis/core/templates/core/person/full.html:112
+#: aleksis/core/templates/core/person/full.html:100
 msgid "Change preferences"
 msgstr ""
 
@@ -1917,83 +1957,83 @@ msgstr ""
 msgid "System checks"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:21
+#: aleksis/core/templates/core/pages/system_status.html:22
 msgid "Maintenance mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:23
+#: aleksis/core/templates/core/pages/system_status.html:24
 msgid ""
 "\n"
 "                Only admin and visitors from internal IPs can access thesite.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:34
+#: aleksis/core/templates/core/pages/system_status.html:36
 msgid "Maintenance mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:35
+#: aleksis/core/templates/core/pages/system_status.html:37
 msgid "Everyone can access the site."
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:45
+#: aleksis/core/templates/core/pages/system_status.html:47
 msgid "Debug mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:47
+#: aleksis/core/templates/core/pages/system_status.html:49
 msgid ""
 "\n"
 "                The web server throws back debug information on errors. Do not use in production!\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:54
+#: aleksis/core/templates/core/pages/system_status.html:56
 msgid "Debug mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:56
+#: aleksis/core/templates/core/pages/system_status.html:58
 msgid ""
 "\n"
 "                Debug mode is disabled. Default error pages are displayed on errors.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:69
+#: aleksis/core/templates/core/pages/system_status.html:71
 msgid "System health checks"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:75
+#: aleksis/core/templates/core/pages/system_status.html:77
 msgid "Service"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:76
-#: aleksis/core/templates/core/pages/system_status.html:115
+#: aleksis/core/templates/core/pages/system_status.html:78
+#: aleksis/core/templates/core/pages/system_status.html:119
 msgid "Status"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:77
+#: aleksis/core/templates/core/pages/system_status.html:79
 msgid "Time taken"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:96
+#: aleksis/core/templates/core/pages/system_status.html:100
 msgid "seconds"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:107
+#: aleksis/core/templates/core/pages/system_status.html:111
 msgid "Celery task results"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:112
+#: aleksis/core/templates/core/pages/system_status.html:116
 #: aleksis/core/templates/templated_email/celery_failure.email:9
 #: aleksis/core/templates/templated_email/celery_failure.email:28
 msgid "Task"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:113
+#: aleksis/core/templates/core/pages/system_status.html:117
 msgid "ID"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:114
+#: aleksis/core/templates/core/pages/system_status.html:118
 msgid "Date done"
 msgstr ""
 
@@ -2034,6 +2074,18 @@ msgid ""
 "            "
 msgstr ""
 
+#: aleksis/core/templates/core/partials/avatar_content.html:14
+#: aleksis/core/templates/core/partials/avatar_content.html:15
+#: aleksis/core/templates/core/person/full.html:213
+#: aleksis/core/templates/core/person/full.html:214
+msgid "Avatar"
+msgstr ""
+
+#: aleksis/core/templates/core/partials/avatar_content.html:19
+#: aleksis/core/templates/core/partials/avatar_content.html:20
+msgid "Identicon"
+msgstr ""
+
 #: aleksis/core/templates/core/partials/crud_events.html:15
 msgid "Changed by"
 msgstr ""
@@ -2124,24 +2176,24 @@ msgid "Edit person"
 msgstr ""
 
 #: aleksis/core/templates/core/person/full.html:47
-#: aleksis/core/templates/core/person/full.html:119
+#: aleksis/core/templates/core/person/full.html:107
 msgid "Impersonate"
 msgstr ""
 
 #: aleksis/core/templates/core/person/full.html:54
-#: aleksis/core/templates/core/person/full.html:126
+#: aleksis/core/templates/core/person/full.html:114
 msgid "Invite user"
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:133
+#: aleksis/core/templates/core/person/full.html:121
 msgid "Contact details"
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:224
+#: aleksis/core/templates/core/person/full.html:220
 msgid "This person didn't upload a personal photo."
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:232
+#: aleksis/core/templates/core/person/full.html:228
 msgid "Children"
 msgstr ""
 
@@ -2346,13 +2398,11 @@ msgstr ""
 msgid "Network error"
 msgstr ""
 
-#: aleksis/core/templates/offline.html:8
-msgid ""
-"No internet\n"
-"    connection."
+#: aleksis/core/templates/offline.html:10
+msgid "No internet connection."
 msgstr ""
 
-#: aleksis/core/templates/offline.html:12
+#: aleksis/core/templates/offline.html:14
 msgid ""
 "\n"
 "      There was an error accessing this page. You probably don't have an internet connection. Check to see if your WiFi\n"
@@ -2596,7 +2646,7 @@ msgstr ""
 #: aleksis/core/templates/two_factor/_base_focus.html:6
 #: aleksis/core/templates/two_factor/core/otp_required.html:22
 #: aleksis/core/templates/two_factor/core/setup.html:5
-#: aleksis/core/templates/two_factor/profile/profile.html:87
+#: aleksis/core/templates/two_factor/profile/profile.html:88
 msgid "Enable Two-Factor Authentication"
 msgstr ""
 
@@ -2700,15 +2750,15 @@ msgstr ""
 msgid "Or, alternatively, use one of your backup phones:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:121
+#: aleksis/core/templates/two_factor/core/login.html:122
 msgid "As a last resort, you can use a backup token:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:124
+#: aleksis/core/templates/two_factor/core/login.html:125
 msgid "Use Backup Token"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:135
+#: aleksis/core/templates/two_factor/core/login.html:136
 msgid "Use alternative login options"
 msgstr ""
 
@@ -2949,11 +2999,11 @@ msgid ""
 "      "
 msgstr ""
 
-#: aleksis/core/util/notifications.py:63
+#: aleksis/core/util/notifications.py:64
 msgid "E-Mail"
 msgstr ""
 
-#: aleksis/core/util/notifications.py:64
+#: aleksis/core/util/notifications.py:65
 msgid "SMS"
 msgstr ""
 
@@ -2977,156 +3027,156 @@ msgstr ""
 msgid "Download PDF"
 msgstr ""
 
-#: aleksis/core/views.py:285
+#: aleksis/core/views.py:289
 msgid "The school term has been created."
 msgstr ""
 
-#: aleksis/core/views.py:297
+#: aleksis/core/views.py:301
 msgid "The school term has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:417
+#: aleksis/core/views.py:421
 msgid "The child groups were successfully saved."
 msgstr ""
 
-#: aleksis/core/views.py:436 aleksis/core/views.py:446
+#: aleksis/core/views.py:440 aleksis/core/views.py:450
 msgid "The person has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:496
+#: aleksis/core/views.py:500
 msgid "The group has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:593
+#: aleksis/core/views.py:597
 msgid "The announcement has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:609
+#: aleksis/core/views.py:613
 msgid "The announcement has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:677
+#: aleksis/core/views.py:681
 msgid "The requested preference registry does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:696
+#: aleksis/core/views.py:700
 msgid "The preferences have been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:720
+#: aleksis/core/views.py:724
 msgid "The person has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:734
+#: aleksis/core/views.py:738
 msgid "The group has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:766
+#: aleksis/core/views.py:770
 msgid "The additional_field has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:800
+#: aleksis/core/views.py:804
 msgid "The additional field has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:825
+#: aleksis/core/views.py:829
 msgid "The group type has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:855
+#: aleksis/core/views.py:859
 msgid "The group type has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:888
+#: aleksis/core/views.py:892
 msgid "Progress: Run data checks"
 msgstr ""
 
-#: aleksis/core/views.py:889
+#: aleksis/core/views.py:893
 msgid "Run data checks …"
 msgstr ""
 
-#: aleksis/core/views.py:890
+#: aleksis/core/views.py:894
 msgid "The data checks were run successfully."
 msgstr ""
 
-#: aleksis/core/views.py:891
+#: aleksis/core/views.py:895
 msgid "There was a problem while running data checks."
 msgstr ""
 
-#: aleksis/core/views.py:907
+#: aleksis/core/views.py:911
 #, python-brace-format
 msgid "The solve option '{solve_option_obj.verbose_name}' "
 msgstr ""
 
-#: aleksis/core/views.py:917
+#: aleksis/core/views.py:921
 msgid "The requested solve option does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:949
+#: aleksis/core/views.py:953
 msgid "The dashboard widget has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:979
+#: aleksis/core/views.py:983
 msgid "The dashboard widget has been created."
 msgstr ""
 
-#: aleksis/core/views.py:989
+#: aleksis/core/views.py:993
 msgid "The dashboard widget has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1060
+#: aleksis/core/views.py:1064
 msgid "Your dashboard configuration has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1062
+#: aleksis/core/views.py:1066
 msgid "The configuration of the default dashboard has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1138
+#: aleksis/core/views.py:1142
 #, python-brace-format
 msgid "The invitation was successfully created. The invitation code is {code}"
 msgstr ""
 
-#: aleksis/core/views.py:1229
+#: aleksis/core/views.py:1233
 msgid "We have successfully assigned the permissions."
 msgstr ""
 
-#: aleksis/core/views.py:1239
+#: aleksis/core/views.py:1243
 msgid "The global user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1249
+#: aleksis/core/views.py:1253
 msgid "The global group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1259
+#: aleksis/core/views.py:1263
 msgid "The object user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1269
+#: aleksis/core/views.py:1273
 msgid "The object group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1337
+#: aleksis/core/views.py:1341
 msgid "The requested PDF file does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:1346 aleksis/core/views.py:1350
+#: aleksis/core/views.py:1350 aleksis/core/views.py:1354
 msgid "The requested task does not exist or is not accessible"
 msgstr ""
 
-#: aleksis/core/views.py:1388
+#: aleksis/core/views.py:1406
 msgid "The third-party account could not be disconnected because it is the only login method available."
 msgstr ""
 
-#: aleksis/core/views.py:1395
+#: aleksis/core/views.py:1413
 msgid "The third-party account has been successfully disconnected."
 msgstr ""
 
-#: aleksis/core/views.py:1466
+#: aleksis/core/views.py:1484
 msgid "Person was invited successfully and an email with further instructions has been send to them."
 msgstr ""
 
-#: aleksis/core/views.py:1477
+#: aleksis/core/views.py:1495
 msgid "Person was already invited."
 msgstr ""
diff --git a/aleksis/core/locale/ar/LC_MESSAGES/djangojs.po b/aleksis/core/locale/ar/LC_MESSAGES/djangojs.po
index 8a53fc2fc168227f37f344c17ae359b5d5764596..31b839fb203db1208029422282b10d1fa3e95123 100644
--- a/aleksis/core/locale/ar/LC_MESSAGES/djangojs.po
+++ b/aleksis/core/locale/ar/LC_MESSAGES/djangojs.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:20+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -30,6 +30,6 @@ msgstr ""
 msgid "OK"
 msgstr ""
 
-#: aleksis/core/static/js/main.js:191
+#: aleksis/core/static/js/main.js:195
 msgid "This page may contain outdated information since there is no internet connection."
 msgstr ""
diff --git a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po
index 56ff3ab652cb345ae714d6b1eb8b54e7ae5f27a2..e39ddc518a4ec11696d1f0cb411139a7615e90c1 100644
--- a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po
@@ -7,9 +7,9 @@ msgid ""
 msgstr ""
 "Project-Id-Version: AlekSIS (School Information System) 0.1\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
-"PO-Revision-Date: 2022-02-08 23:24+0000\n"
-"Last-Translator: Tom Teichler <tom.teichler@teckids.org>\n"
+"POT-Creation-Date: 2022-03-23 11:19+0100\n"
+"PO-Revision-Date: 2022-03-23 11:06+0000\n"
+"Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
 "Language-Team: German <https://translate.edugit.org/projects/aleksis/"
 "aleksis-core/de/>\n"
 "Language: de_DE\n"
@@ -39,10 +39,10 @@ msgstr "E-Mail-Adresse"
 msgid "Home and mobile phone"
 msgstr "Festnetz- und Mobilfunknummer"
 
-#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:265
-#: aleksis/core/models.py:462 aleksis/core/templates/core/group/list.html:8
+#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:185
+#: aleksis/core/models.py:487 aleksis/core/templates/core/group/list.html:8
 #: aleksis/core/templates/core/group/list.html:9
-#: aleksis/core/templates/core/person/full.html:250
+#: aleksis/core/templates/core/person/full.html:246
 msgid "Groups"
 msgstr "Gruppen"
 
@@ -67,8 +67,8 @@ msgstr "Sicherstellen, dass es keine kaputten Dashboard-Widgets gibt."
 msgid "The DashboardWidget was reported broken automatically."
 msgstr "Das Dashboard-Widget wurde automatisch als kaputt gemeldet."
 
-#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:105
-#: aleksis/core/templates/core/base.html:106
+#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:139
+#: aleksis/core/templates/core/base.html:140
 #: aleksis/core/templates/core/group/list.html:20
 #: aleksis/core/templates/core/person/list.html:24
 #: aleksis/core/templates/search/search.html:7
@@ -92,11 +92,11 @@ msgstr "Berechtigung"
 msgid "Content type"
 msgstr "Inhaltstyp"
 
-#: aleksis/core/filters.py:113 aleksis/core/models.py:688
+#: aleksis/core/filters.py:113 aleksis/core/models.py:713
 msgid "User"
 msgstr "Benutzer"
 
-#: aleksis/core/filters.py:135 aleksis/core/models.py:461
+#: aleksis/core/filters.py:135 aleksis/core/models.py:486
 msgid "Group"
 msgstr "Gruppe"
 
@@ -132,7 +132,7 @@ msgstr "Sie können keine neuen Benutzer erstellen, wenn Sie gleichzeitig einen
 msgid "This username is already in use."
 msgstr "Dieser Benutzername wird bereits genutzt."
 
-#: aleksis/core/forms.py:153 aleksis/core/models.py:130
+#: aleksis/core/forms.py:153 aleksis/core/models.py:134
 msgid "School term"
 msgstr "Schuljahr"
 
@@ -141,7 +141,7 @@ msgid "Common data"
 msgstr "Allgemeine Daten"
 
 #: aleksis/core/forms.py:155 aleksis/core/forms.py:207
-#: aleksis/core/menus.py:254 aleksis/core/models.py:153
+#: aleksis/core/menus.py:174 aleksis/core/models.py:157
 #: aleksis/core/templates/core/person/list.html:8
 #: aleksis/core/templates/core/person/list.html:9
 msgid "Persons"
@@ -151,18 +151,18 @@ msgstr "Personen"
 msgid "Additional data"
 msgstr "Zusätzliche Datne"
 
-#: aleksis/core/forms.py:157 aleksis/core/models.py:206
-#: aleksis/core/models.py:514
+#: aleksis/core/forms.py:157 aleksis/core/models.py:210
+#: aleksis/core/models.py:539
 msgid "Photo"
 msgstr "Foto"
 
 #: aleksis/core/forms.py:199 aleksis/core/forms.py:202
-#: aleksis/core/models.py:73
+#: aleksis/core/models.py:77
 msgid "Date"
 msgstr "Datum"
 
 #: aleksis/core/forms.py:200 aleksis/core/forms.py:203
-#: aleksis/core/models.py:81
+#: aleksis/core/models.py:85
 msgid "Time"
 msgstr "Zeit"
 
@@ -198,11 +198,11 @@ msgstr "Einladungscode"
 msgid "Please enter your invitation code."
 msgstr "Bitte geben Sie Ihren Einladungscode ein."
 
-#: aleksis/core/forms.py:418 aleksis/core/models.py:181
+#: aleksis/core/forms.py:418 aleksis/core/models.py:185
 msgid "First name"
 msgstr "Vorname"
 
-#: aleksis/core/forms.py:419 aleksis/core/models.py:182
+#: aleksis/core/forms.py:419 aleksis/core/models.py:186
 msgid "Last name"
 msgstr "Nachname"
 
@@ -250,7 +250,17 @@ msgstr "Passwort"
 msgid "Password (again)"
 msgstr "Passwort wiederholen"
 
-#: aleksis/core/forms.py:775
+#: aleksis/core/forms.py:728
+msgid "The selected action does not exist."
+msgstr "Die ausgewählte Aktion existiert nicht."
+
+#: aleksis/core/forms.py:739
+msgid "You do not have permission to run {} on all selected objects."
+msgstr ""
+"Sie haben nicht die Berechtigung, {} auf alle ausgewählten Objekte "
+"auszuführen."
+
+#: aleksis/core/forms.py:795
 msgid "No valid selection."
 msgstr "Keine gültige Auswahl."
 
@@ -293,678 +303,698 @@ msgstr "Einladung akzeptieren"
 msgid "Dashboard"
 msgstr "Dashboard"
 
-#: aleksis/core/menus.py:41 aleksis/core/models.py:734
-#: aleksis/core/preferences.py:29
+#: aleksis/core/menus.py:41 aleksis/core/models.py:765
+#: aleksis/core/preferences.py:29 aleksis/core/templates/core/base.html:81
 #: aleksis/core/templates/core/notifications.html:4
 #: aleksis/core/templates/core/notifications.html:5
 msgid "Notifications"
 msgstr "Benachrichtigungen"
 
 #: aleksis/core/menus.py:53
-msgid "Account"
-msgstr "Konto"
-
-#: aleksis/core/menus.py:60
-msgid "Stop impersonation"
-msgstr "Verkleidung beenden"
-
-#: aleksis/core/menus.py:69 aleksis/core/templates/core/base.html:80
-msgid "Logout"
-msgstr "Abmelden"
-
-#: aleksis/core/menus.py:75
-msgid "2FA"
-msgstr "2FA"
-
-#: aleksis/core/menus.py:83
-#: aleksis/core/templates/account/password_change.html:5
-#: aleksis/core/templates/account/password_change.html:6
-#: aleksis/core/templates/account/password_change.html:19
-#: aleksis/core/templates/account/password_reset_from_key.html:5
-#: aleksis/core/templates/account/password_reset_from_key.html:42
-#: aleksis/core/templates/account/password_reset_from_key.html:46
-#: aleksis/core/templates/account/password_reset_from_key_done.html:5
-#: aleksis/core/templates/account/password_reset_from_key_done.html:6
-msgid "Change password"
-msgstr "Passwort ändern"
-
-#: aleksis/core/menus.py:95
-msgid "Me"
-msgstr "Ich"
-
-#: aleksis/core/menus.py:104
-#: aleksis/core/templates/dynamic_preferences/form.html:5
-msgid "Preferences"
-msgstr "Einstellungen"
-
-#: aleksis/core/menus.py:113
-msgid "Third-party accounts"
-msgstr "Drittanbieter-Konten"
-
-#: aleksis/core/menus.py:122
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
-msgid "Authorized applications"
-msgstr "Autorisierte Anwendungen"
-
-#: aleksis/core/menus.py:133
 msgid "Admin"
 msgstr "Admin"
 
-#: aleksis/core/menus.py:141 aleksis/core/models.py:834
+#: aleksis/core/menus.py:61 aleksis/core/models.py:865
 #: aleksis/core/templates/core/announcement/list.html:7
 #: aleksis/core/templates/core/announcement/list.html:8
 msgid "Announcements"
 msgstr "Ankündigungen"
 
-#: aleksis/core/menus.py:152 aleksis/core/models.py:131
+#: aleksis/core/menus.py:72 aleksis/core/models.py:135
 #: aleksis/core/templates/core/school_term/list.html:8
 #: aleksis/core/templates/core/school_term/list.html:9
 msgid "School terms"
 msgstr "Schuljahre"
 
-#: aleksis/core/menus.py:163
+#: aleksis/core/menus.py:83
 #: aleksis/core/templates/core/dashboard_widget/list.html:8
 #: aleksis/core/templates/core/dashboard_widget/list.html:9
 msgid "Dashboard widgets"
 msgstr "Dashboard-Widgets"
 
-#: aleksis/core/menus.py:174
+#: aleksis/core/menus.py:94
 #: aleksis/core/templates/core/management/data_management.html:6
 #: aleksis/core/templates/core/management/data_management.html:7
 msgid "Data management"
 msgstr "Datenverwaltung"
 
-#: aleksis/core/menus.py:185
+#: aleksis/core/menus.py:105
 #: aleksis/core/templates/core/pages/system_status.html:5
 #: aleksis/core/templates/core/pages/system_status.html:7
 msgid "System status"
 msgstr "Systemstatus"
 
-#: aleksis/core/menus.py:196
+#: aleksis/core/menus.py:116
 msgid "Configuration"
 msgstr "Konfiguration"
 
-#: aleksis/core/menus.py:207 aleksis/core/templates/core/data_check/list.html:9
+#: aleksis/core/menus.py:127 aleksis/core/templates/core/data_check/list.html:9
 #: aleksis/core/templates/core/data_check/list.html:10
 msgid "Data checks"
 msgstr "Datenprüfungen"
 
-#: aleksis/core/menus.py:213 aleksis/core/templates/core/perms/list.html:13
+#: aleksis/core/menus.py:133 aleksis/core/templates/core/perms/list.html:13
 #: aleksis/core/templates/core/perms/list.html:14
 msgid "Manage permissions"
 msgstr "Berechtigungen verwalten"
 
-#: aleksis/core/menus.py:224
+#: aleksis/core/menus.py:144
 msgid "Backend Admin"
 msgstr "Backend-Administration"
 
-#: aleksis/core/menus.py:232
+#: aleksis/core/menus.py:152
 #: aleksis/core/templates/oauth2_provider/application/list.html:5
 #: aleksis/core/templates/oauth2_provider/application/list.html:6
 msgid "OAuth2 Applications"
 msgstr "OAuth2-Anwendungen"
 
-#: aleksis/core/menus.py:245
+#: aleksis/core/menus.py:165
 msgid "People"
 msgstr "Leute"
 
-#: aleksis/core/menus.py:276 aleksis/core/models.py:1055
+#: aleksis/core/menus.py:196 aleksis/core/models.py:1099
 #: aleksis/core/templates/core/group_type/list.html:8
 #: aleksis/core/templates/core/group_type/list.html:9
 msgid "Group types"
 msgstr "Gruppentypen"
 
-#: aleksis/core/menus.py:287
+#: aleksis/core/menus.py:207
 msgid "Groups and child groups"
 msgstr "Gruppen und Kindgruppen"
 
-#: aleksis/core/menus.py:298 aleksis/core/models.py:510
+#: aleksis/core/menus.py:218 aleksis/core/models.py:535
 #: aleksis/core/templates/core/additional_field/list.html:8
 #: aleksis/core/templates/core/additional_field/list.html:9
 msgid "Additional fields"
 msgstr "Zusätzliche Felder"
 
-#: aleksis/core/menus.py:309
+#: aleksis/core/menus.py:229
 msgid "Invite person"
 msgstr "Person einladen"
 
-#: aleksis/core/menus.py:322
+#: aleksis/core/menus.py:242
 #: aleksis/core/templates/core/group/child_groups.html:7
 #: aleksis/core/templates/core/group/child_groups.html:9
 msgid "Assign child groups to groups"
 msgstr "Kindgruppen zu Gruppen zuordnen"
 
+#: aleksis/core/menus.py:254
+msgid "Stop impersonation"
+msgstr "Verkleidung beenden"
+
+#: aleksis/core/menus.py:263
+msgid "Account"
+msgstr "Konto"
+
+#: aleksis/core/menus.py:272
+#: aleksis/core/templates/dynamic_preferences/form.html:5
+msgid "Preferences"
+msgstr "Einstellungen"
+
+#: aleksis/core/menus.py:281
+msgid "2FA"
+msgstr "2FA"
+
+#: aleksis/core/menus.py:289
+#: aleksis/core/templates/account/password_change.html:5
+#: aleksis/core/templates/account/password_change.html:6
+#: aleksis/core/templates/account/password_change.html:19
+#: aleksis/core/templates/account/password_reset_from_key.html:5
+#: aleksis/core/templates/account/password_reset_from_key.html:42
+#: aleksis/core/templates/account/password_reset_from_key.html:46
+#: aleksis/core/templates/account/password_reset_from_key_done.html:5
+#: aleksis/core/templates/account/password_reset_from_key_done.html:6
+msgid "Change password"
+msgstr "Passwort ändern"
+
+#: aleksis/core/menus.py:301
+msgid "Third-party accounts"
+msgstr "Drittanbieter-Konten"
+
+#: aleksis/core/menus.py:310
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
+msgid "Authorized applications"
+msgstr "Autorisierte Anwendungen"
+
+#: aleksis/core/menus.py:320
+msgid "Logout"
+msgstr "Abmelden"
+
 #: aleksis/core/mixins.py:511
 msgid "Linked school term"
 msgstr "Zugeordnetes Schuljahr"
 
-#: aleksis/core/models.py:71
+#: aleksis/core/models.py:75
 msgid "Boolean (Yes/No)"
 msgstr "Boolean (Ja/Nein)"
 
-#: aleksis/core/models.py:72
+#: aleksis/core/models.py:76
 msgid "Text (one line)"
 msgstr "Text (eine Zeile)"
 
-#: aleksis/core/models.py:74
+#: aleksis/core/models.py:78
 msgid "Date and time"
 msgstr "Datum und Uhrzeit"
 
-#: aleksis/core/models.py:75
+#: aleksis/core/models.py:79
 msgid "Decimal number"
 msgstr "Dezimalzahl"
 
-#: aleksis/core/models.py:76 aleksis/core/models.py:199
+#: aleksis/core/models.py:80 aleksis/core/models.py:203
 msgid "E-mail address"
 msgstr "E-Mail-Adresse"
 
-#: aleksis/core/models.py:77
+#: aleksis/core/models.py:81
 msgid "Integer"
 msgstr "Ganze Zahl"
 
-#: aleksis/core/models.py:78
+#: aleksis/core/models.py:82
 msgid "IP address"
 msgstr "IP-Adresse"
 
-#: aleksis/core/models.py:79
+#: aleksis/core/models.py:83
 msgid "Boolean or empty (Yes/No/Neither)"
 msgstr "Boolean oder leer (Ja/Nein/weder)"
 
-#: aleksis/core/models.py:80
+#: aleksis/core/models.py:84
 msgid "Text (multi-line)"
 msgstr "Text (mehrzeilig)"
 
-#: aleksis/core/models.py:82
+#: aleksis/core/models.py:86
 msgid "URL / Link"
 msgstr "URL / Link"
 
-#: aleksis/core/models.py:94 aleksis/core/models.py:1024
+#: aleksis/core/models.py:98 aleksis/core/models.py:1068
 msgid "Name"
 msgstr "Name"
 
-#: aleksis/core/models.py:96
+#: aleksis/core/models.py:100
 msgid "Start date"
 msgstr "Startdatum"
 
-#: aleksis/core/models.py:97
+#: aleksis/core/models.py:101
 msgid "End date"
 msgstr "Enddatum"
 
-#: aleksis/core/models.py:116
+#: aleksis/core/models.py:120
 msgid "The start date must be earlier than the end date."
 msgstr "Das Startdatum muss vor dem Enddatum liegen."
 
-#: aleksis/core/models.py:123
+#: aleksis/core/models.py:127
 msgid "There is already a school term for this time or a part of this time."
 msgstr "Es gibt bereits ein Schuljahr für diesen Zeitraum oder einen Teilzeitraum."
 
-#: aleksis/core/models.py:152 aleksis/core/models.py:973
+#: aleksis/core/models.py:156 aleksis/core/models.py:1017
 msgid "Person"
 msgstr "Person"
 
-#: aleksis/core/models.py:155
+#: aleksis/core/models.py:159
 msgid "Can view address"
 msgstr "Kann Adresse sehen"
 
-#: aleksis/core/models.py:156
+#: aleksis/core/models.py:160
 msgid "Can view contact details"
 msgstr "Kann Kontaktdetails sehen"
 
-#: aleksis/core/models.py:157
+#: aleksis/core/models.py:161
 msgid "Can view photo"
 msgstr "Kann Foto sehen"
 
-#: aleksis/core/models.py:158
+#: aleksis/core/models.py:162
 msgid "Can view avatar image"
 msgstr "Kann Avatar-Bild sehen"
 
-#: aleksis/core/models.py:159
+#: aleksis/core/models.py:163
 msgid "Can view persons groups"
 msgstr "Kann Gruppen einer Person sehen"
 
-#: aleksis/core/models.py:160
+#: aleksis/core/models.py:164
 msgid "Can view personal details"
 msgstr "Kann persönliche Daten sehen"
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "female"
 msgstr "weiblich"
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "male"
 msgstr "männlich"
 
-#: aleksis/core/models.py:178 aleksis/core/models.py:1227
+#: aleksis/core/models.py:182 aleksis/core/models.py:1271
 msgid "Linked user"
 msgstr "Verknüpfter Benutzer"
 
-#: aleksis/core/models.py:184
+#: aleksis/core/models.py:188
 msgid "Additional name(s)"
 msgstr "Zusätzliche Namen"
 
-#: aleksis/core/models.py:188 aleksis/core/models.py:479
+#: aleksis/core/models.py:192 aleksis/core/models.py:504
 msgid "Short name"
 msgstr "Kurzname"
 
-#: aleksis/core/models.py:191
+#: aleksis/core/models.py:195
 msgid "Street"
 msgstr "Straße"
 
-#: aleksis/core/models.py:192
+#: aleksis/core/models.py:196
 msgid "Street number"
 msgstr "Hausnummer"
 
-#: aleksis/core/models.py:193
+#: aleksis/core/models.py:197
 msgid "Postal code"
 msgstr "Postleitzahl"
 
-#: aleksis/core/models.py:194
+#: aleksis/core/models.py:198
 msgid "Place"
 msgstr "Ort"
 
-#: aleksis/core/models.py:196 aleksis/core/templates/core/person/full.html:172
+#: aleksis/core/models.py:200 aleksis/core/templates/core/person/full.html:160
 msgid "Home phone"
 msgstr "Festnetz"
 
-#: aleksis/core/models.py:197 aleksis/core/templates/core/person/full.html:182
+#: aleksis/core/models.py:201 aleksis/core/templates/core/person/full.html:170
 msgid "Mobile phone"
 msgstr "Handy"
 
-#: aleksis/core/models.py:201
+#: aleksis/core/models.py:205
 msgid "Date of birth"
 msgstr "Geburtsdatum"
 
-#: aleksis/core/models.py:202
+#: aleksis/core/models.py:206
 msgid "Place of birth"
 msgstr "Geburtsort"
 
-#: aleksis/core/models.py:203
+#: aleksis/core/models.py:207
 msgid "Sex"
 msgstr "Geschlecht"
 
-#: aleksis/core/models.py:210 aleksis/core/models.py:518
+#: aleksis/core/models.py:214 aleksis/core/models.py:543
 msgid "This is an official photo, used for official documents and for internal use cases."
 msgstr "Dies ist ein offizielles Foto, genutzt für offizielle Dokumente und interne Zwecke."
 
-#: aleksis/core/models.py:215 aleksis/core/models.py:522
+#: aleksis/core/models.py:219 aleksis/core/models.py:547
 msgid "Display picture / Avatar"
 msgstr "Bild/Avatar anzeigen"
 
-#: aleksis/core/models.py:218 aleksis/core/models.py:525
+#: aleksis/core/models.py:222 aleksis/core/models.py:550
 msgid "This is a picture or an avatar for public display."
 msgstr "Dies ist ein Bild oder ein Avatar für die öffentliche Darstellung."
 
-#: aleksis/core/models.py:223 aleksis/core/templates/core/person/full.html:239
+#: aleksis/core/models.py:227 aleksis/core/templates/core/person/full.html:235
 msgid "Guardians / Parents"
 msgstr "Erziehungsberechtigte / Eltern"
 
-#: aleksis/core/models.py:230
+#: aleksis/core/models.py:234
 msgid "Primary group"
 msgstr "Primärgruppe"
 
-#: aleksis/core/models.py:233 aleksis/core/models.py:692
-#: aleksis/core/models.py:716 aleksis/core/models.py:801
-#: aleksis/core/models.py:1048
+#: aleksis/core/models.py:237 aleksis/core/models.py:717
+#: aleksis/core/models.py:741 aleksis/core/models.py:832
+#: aleksis/core/models.py:1092
 msgid "Description"
 msgstr "Beschreibung"
 
-#: aleksis/core/models.py:434
+#: aleksis/core/models.py:457
 msgid "Title of field"
 msgstr "Feldtitel"
 
-#: aleksis/core/models.py:436
+#: aleksis/core/models.py:459
 msgid "Type of field"
 msgstr "Feldtyp"
 
-#: aleksis/core/models.py:443
+#: aleksis/core/models.py:461
+msgid "Required"
+msgstr "Pflichtfeld"
+
+#: aleksis/core/models.py:462
+msgid "Help text / description"
+msgstr "Hilfetext/Beschreibung"
+
+#: aleksis/core/models.py:468
 msgid "Addtitional field for groups"
 msgstr "Zusätzliche Felder für Gruppen"
 
-#: aleksis/core/models.py:444
+#: aleksis/core/models.py:469
 msgid "Addtitional fields for groups"
 msgstr "Zusätzliche Felder für Gruppen"
 
-#: aleksis/core/models.py:464
+#: aleksis/core/models.py:489
 msgid "Can assign child groups to groups"
 msgstr "Kann Kindgruppen zu Gruppen zuordnen"
 
-#: aleksis/core/models.py:465
+#: aleksis/core/models.py:490
 msgid "Can view statistics about group."
 msgstr "Kann Statistiken über Gruppen sehen."
 
-#: aleksis/core/models.py:477
+#: aleksis/core/models.py:502
 msgid "Long name"
 msgstr "Langname"
 
-#: aleksis/core/models.py:487 aleksis/core/templates/core/group/full.html:85
+#: aleksis/core/models.py:512 aleksis/core/templates/core/group/full.html:85
 msgid "Members"
 msgstr "Mitglieder"
 
-#: aleksis/core/models.py:490 aleksis/core/templates/core/group/full.html:82
+#: aleksis/core/models.py:515 aleksis/core/templates/core/group/full.html:82
 msgid "Owners"
 msgstr "Leiter/-innen"
 
-#: aleksis/core/models.py:497 aleksis/core/templates/core/group/full.html:55
+#: aleksis/core/models.py:522 aleksis/core/templates/core/group/full.html:55
 msgid "Parent groups"
 msgstr "Ãœbergeordnete Gruppen"
 
-#: aleksis/core/models.py:505
+#: aleksis/core/models.py:530
 msgid "Type of group"
 msgstr "Gruppentyp"
 
-#: aleksis/core/models.py:691 aleksis/core/models.py:715
-#: aleksis/core/models.py:800
+#: aleksis/core/models.py:716 aleksis/core/models.py:740
+#: aleksis/core/models.py:831
 #: aleksis/core/templates/core/announcement/list.html:18
 msgid "Title"
 msgstr "Titel"
 
-#: aleksis/core/models.py:694
+#: aleksis/core/models.py:719
 msgid "Application"
 msgstr "Anwendung"
 
-#: aleksis/core/models.py:700
+#: aleksis/core/models.py:725
 msgid "Activity"
 msgstr "Aktivität"
 
-#: aleksis/core/models.py:701
+#: aleksis/core/models.py:726
 msgid "Activities"
 msgstr "Aktivitäten"
 
-#: aleksis/core/models.py:707
+#: aleksis/core/models.py:732
 msgid "Sender"
 msgstr "Absender"
 
-#: aleksis/core/models.py:712
+#: aleksis/core/models.py:737
 msgid "Recipient"
 msgstr "Empfänger"
 
-#: aleksis/core/models.py:717 aleksis/core/models.py:1025
+#: aleksis/core/models.py:742 aleksis/core/models.py:1069
 msgid "Link"
 msgstr "Link"
 
-#: aleksis/core/models.py:719
+#: aleksis/core/models.py:744
+msgid "Send notification at"
+msgstr "Benachrichtigung schicken am"
+
+#: aleksis/core/models.py:746
 msgid "Read"
 msgstr "Gelesen"
 
-#: aleksis/core/models.py:720
+#: aleksis/core/models.py:747
 msgid "Sent"
 msgstr "Versandt"
 
-#: aleksis/core/models.py:733
+#: aleksis/core/models.py:764
 msgid "Notification"
 msgstr "Benachrichtigung"
 
-#: aleksis/core/models.py:802
+#: aleksis/core/models.py:833
 msgid "Link to detailed view"
 msgstr "Link zur detaillierten Ansicht"
 
-#: aleksis/core/models.py:805
+#: aleksis/core/models.py:836
 msgid "Date and time from when to show"
 msgstr "Datum und Uhrzeit des Anzeigestarts"
 
-#: aleksis/core/models.py:808
+#: aleksis/core/models.py:839
 msgid "Date and time until when to show"
 msgstr "Anzeigezeitraum"
 
-#: aleksis/core/models.py:833
+#: aleksis/core/models.py:864
 msgid "Announcement"
 msgstr "Ankündigung"
 
-#: aleksis/core/models.py:871
+#: aleksis/core/models.py:902
 msgid "Announcement recipient"
 msgstr "Empfänger der Ankündigung"
 
-#: aleksis/core/models.py:872
+#: aleksis/core/models.py:903
 msgid "Announcement recipients"
 msgstr "Empfänger der Ankündigung"
 
-#: aleksis/core/models.py:894
+#: aleksis/core/models.py:925
 msgid "Widget Title"
 msgstr "Widget-Titel"
 
-#: aleksis/core/models.py:895
+#: aleksis/core/models.py:926
 msgid "Activate Widget"
 msgstr "Widget aktivieren"
 
-#: aleksis/core/models.py:896
+#: aleksis/core/models.py:927
 msgid "Widget is broken"
 msgstr "Widget ist kaputt"
 
-#: aleksis/core/models.py:899
+#: aleksis/core/models.py:930
 msgid "Size on mobile devices"
 msgstr "Größe auf Mobilgeräten"
 
-#: aleksis/core/models.py:900
+#: aleksis/core/models.py:931
 msgid "<= 600 px, 12 columns"
 msgstr "<= 600 px, 12 Spalten"
 
-#: aleksis/core/models.py:905
+#: aleksis/core/models.py:936
 msgid "Size on tablet devices"
 msgstr "Größe auf Tablets"
 
-#: aleksis/core/models.py:906
+#: aleksis/core/models.py:937
 msgid "> 600 px, 12 columns"
 msgstr "> 600px, 12 Spalten"
 
-#: aleksis/core/models.py:911
+#: aleksis/core/models.py:942
 msgid "Size on desktop devices"
 msgstr "Größe auf Desktopgeräten"
 
-#: aleksis/core/models.py:912
+#: aleksis/core/models.py:943
 msgid "> 992 px, 12 columns"
 msgstr "> 992 px, 12 Spalten"
 
-#: aleksis/core/models.py:917
+#: aleksis/core/models.py:948
 msgid "Size on large desktop devices"
 msgstr "Größe auf großen Desktopgeräten"
 
-#: aleksis/core/models.py:918
+#: aleksis/core/models.py:949
 msgid "> 1200 px>, 12 columns"
 msgstr "> 1200 px, 12 Spalten"
 
-#: aleksis/core/models.py:949
+#: aleksis/core/models.py:980
 msgid "Can edit default dashboard"
 msgstr "Kann Standarddashboard bearbeiten"
 
-#: aleksis/core/models.py:950
+#: aleksis/core/models.py:981
 msgid "Dashboard Widget"
 msgstr "Dashboard-Widget"
 
-#: aleksis/core/models.py:951
+#: aleksis/core/models.py:982
 msgid "Dashboard Widgets"
 msgstr "Dashboard-Widgets"
 
-#: aleksis/core/models.py:957
+#: aleksis/core/models.py:988
 msgid "URL"
 msgstr "URL"
 
-#: aleksis/core/models.py:958
+#: aleksis/core/models.py:989
 msgid "Icon URL"
 msgstr "Symbol-URL"
 
-#: aleksis/core/models.py:964
+#: aleksis/core/models.py:995
 msgid "External link widget"
 msgstr "Externer-Link-Widget"
 
-#: aleksis/core/models.py:965
+#: aleksis/core/models.py:996
 msgid "External link widgets"
 msgstr "Externer-Link-Widgets"
 
-#: aleksis/core/models.py:970
+#: aleksis/core/models.py:1002
+msgid "Content"
+msgstr "Inhalt"
+
+#: aleksis/core/models.py:1008
+msgid "Static content widget"
+msgstr "Statischer-Inhalt-Widget"
+
+#: aleksis/core/models.py:1009
+msgid "Static content widgets"
+msgstr "Statischer-Inhalt-Widgets"
+
+#: aleksis/core/models.py:1014
 msgid "Dashboard widget"
 msgstr "Dashboard-Widget"
 
-#: aleksis/core/models.py:975
+#: aleksis/core/models.py:1019
 msgid "Order"
 msgstr "Reihenfolge"
 
-#: aleksis/core/models.py:976
+#: aleksis/core/models.py:1020
 msgid "Part of the default dashboard"
 msgstr "Teil des Standarddashboards"
 
-#: aleksis/core/models.py:991
+#: aleksis/core/models.py:1035
 msgid "Dashboard widget order"
 msgstr "Reihenfolge der Dashboard-Widgets"
 
-#: aleksis/core/models.py:992
+#: aleksis/core/models.py:1036
 msgid "Dashboard widget orders"
 msgstr "Reihenfolgen der Dashboard-Widgets"
 
-#: aleksis/core/models.py:998
+#: aleksis/core/models.py:1042
 msgid "Menu ID"
 msgstr "Menü-ID"
 
-#: aleksis/core/models.py:1011
+#: aleksis/core/models.py:1055
 msgid "Custom menu"
 msgstr "Benutzerdefiniertes Menü"
 
-#: aleksis/core/models.py:1012
+#: aleksis/core/models.py:1056
 msgid "Custom menus"
 msgstr "Benutzerdefinierte Menüs"
 
-#: aleksis/core/models.py:1022
+#: aleksis/core/models.py:1066
 msgid "Menu"
 msgstr "Menü"
 
-#: aleksis/core/models.py:1026 aleksis/core/models.py:1274
+#: aleksis/core/models.py:1070 aleksis/core/models.py:1318
 #: aleksis/core/templates/oauth2_provider/application/detail.html:26
 msgid "Icon"
 msgstr "Symbol"
 
-#: aleksis/core/models.py:1032
+#: aleksis/core/models.py:1076
 msgid "Custom menu item"
 msgstr "Benutzerdefiniertes Menüelement"
 
-#: aleksis/core/models.py:1033
+#: aleksis/core/models.py:1077
 msgid "Custom menu items"
 msgstr "Benutzerdefinierte Menüelemente"
 
-#: aleksis/core/models.py:1047
+#: aleksis/core/models.py:1091
 msgid "Title of type"
 msgstr "Titel des Typs"
 
-#: aleksis/core/models.py:1054 aleksis/core/templates/core/group/full.html:47
+#: aleksis/core/models.py:1098 aleksis/core/templates/core/group/full.html:47
 msgid "Group type"
 msgstr "Gruppentyp"
 
-#: aleksis/core/models.py:1068
+#: aleksis/core/models.py:1112
 msgid "Can view system status"
 msgstr "Kann Systemstatus sehen"
 
-#: aleksis/core/models.py:1069
+#: aleksis/core/models.py:1113
 msgid "Can manage data"
 msgstr "Kann Daten verwalten"
 
-#: aleksis/core/models.py:1070
+#: aleksis/core/models.py:1114
 msgid "Can impersonate"
 msgstr "Kann sich verkleiden"
 
-#: aleksis/core/models.py:1071
+#: aleksis/core/models.py:1115
 msgid "Can use search"
 msgstr "Kann Suche benutzen"
 
-#: aleksis/core/models.py:1072
+#: aleksis/core/models.py:1116
 msgid "Can change site preferences"
 msgstr "Kann Konfiguration ändern"
 
-#: aleksis/core/models.py:1073
+#: aleksis/core/models.py:1117
 msgid "Can change person preferences"
 msgstr "Kann Einstellungen einer Person verändern"
 
-#: aleksis/core/models.py:1074
+#: aleksis/core/models.py:1118
 msgid "Can change group preferences"
 msgstr "Kann Einstellungen einer Gruppe verändern"
 
-#: aleksis/core/models.py:1075
+#: aleksis/core/models.py:1119
 msgid "Can test PDF generation"
 msgstr "Kann die PDF-Generierung testen"
 
-#: aleksis/core/models.py:1076
+#: aleksis/core/models.py:1120
 msgid "Can invite persons"
 msgstr "Kann Personen einladen"
 
-#: aleksis/core/models.py:1112
+#: aleksis/core/models.py:1156
 msgid "Related data check task"
 msgstr "Zugehörige Datenprüfungsaufgabe"
 
-#: aleksis/core/models.py:1120
+#: aleksis/core/models.py:1164
 msgid "Issue solved"
 msgstr "Problem gelöst"
 
-#: aleksis/core/models.py:1121
+#: aleksis/core/models.py:1165
 msgid "Notification sent"
 msgstr "Benachrichtigung gesendet"
 
-#: aleksis/core/models.py:1134
+#: aleksis/core/models.py:1178
 msgid "Data check result"
 msgstr "Datenprüfungsergebnis"
 
-#: aleksis/core/models.py:1135
+#: aleksis/core/models.py:1179
 msgid "Data check results"
 msgstr "Datenprüfungsergebnisse"
 
-#: aleksis/core/models.py:1137
+#: aleksis/core/models.py:1181
 msgid "Can run data checks"
 msgstr "Kann Datenprüfungen ausführen"
 
-#: aleksis/core/models.py:1138
+#: aleksis/core/models.py:1182
 msgid "Can solve data check problems"
 msgstr "Kann Datenprüfungsprobleme lösen"
 
-#: aleksis/core/models.py:1145
+#: aleksis/core/models.py:1189
 msgid "E-Mail address"
 msgstr "E-Mail-Adresse"
 
-#: aleksis/core/models.py:1177
+#: aleksis/core/models.py:1221
 msgid "Owner"
 msgstr "Leiter"
 
-#: aleksis/core/models.py:1181
+#: aleksis/core/models.py:1225
 msgid "File expires at"
 msgstr "Datei abgelaufen am"
 
-#: aleksis/core/models.py:1183
+#: aleksis/core/models.py:1227
 msgid "Generated HTML file"
 msgstr "Generierte HTML-Datei"
 
-#: aleksis/core/models.py:1185
+#: aleksis/core/models.py:1229
 msgid "Generated PDF file"
 msgstr "Generierte PDF-Datei"
 
-#: aleksis/core/models.py:1192
+#: aleksis/core/models.py:1236
 msgid "PDF file"
 msgstr "PDF-Datei"
 
-#: aleksis/core/models.py:1193
+#: aleksis/core/models.py:1237
 msgid "PDF files"
 msgstr "PDF-Dateien"
 
-#: aleksis/core/models.py:1198
+#: aleksis/core/models.py:1242
 msgid "Task result"
 msgstr "Task-Ergebnis"
 
-#: aleksis/core/models.py:1201
+#: aleksis/core/models.py:1245
 msgid "Task user"
 msgstr "Task-Benutzer"
 
-#: aleksis/core/models.py:1213
+#: aleksis/core/models.py:1257
 msgid "Task user assignment"
 msgstr "Task-Benutzer-Zuordnung"
 
-#: aleksis/core/models.py:1214
+#: aleksis/core/models.py:1258
 msgid "Task user assignments"
 msgstr "Task-Benutzer-Zuordnungen"
 
-#: aleksis/core/models.py:1230
+#: aleksis/core/models.py:1274
 msgid "Additional attributes"
 msgstr "Zusätzliche Attribute"
 
-#: aleksis/core/models.py:1268
+#: aleksis/core/models.py:1312
 msgid "Allowed scopes that clients can request"
 msgstr "Erlaubte Scopes, die ein Client anfordern kann"
 
-#: aleksis/core/models.py:1278
+#: aleksis/core/models.py:1322
 msgid "This image will be shown as icon in the authorization flow. It should be squared."
 msgstr "Dieses Bild wird im Autorisierungs-Vorgang als Symbol angezeigt werden. Es sollte rechteckig sein."
 
@@ -1085,86 +1115,98 @@ msgid "Allow users to change their passwords"
 msgstr "Erlaube Benutzern, ihr Passwort zu ändern"
 
 #: aleksis/core/preferences.py:279
+msgid "Allow users to reset their passwords"
+msgstr "Erlaube Benutzern, ihr Passwort zurückzusetzen"
+
+#: aleksis/core/preferences.py:287
 msgid "Enable signup"
 msgstr "Registrierung aktivieren"
 
-#: aleksis/core/preferences.py:287
+#: aleksis/core/preferences.py:295
+msgid "Regular expression for allowed usernames"
+msgstr "Regulärer Ausdruck für erlaubte Benutzernamen"
+
+#: aleksis/core/preferences.py:303
 msgid "Enable invitations"
 msgstr "Einladungen aktivieren"
 
-#: aleksis/core/preferences.py:295
+#: aleksis/core/preferences.py:311
 msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
 msgstr "Länge des Einladungscodes. (Standard: 3: abcde-acbde-abcde)"
 
-#: aleksis/core/preferences.py:303
+#: aleksis/core/preferences.py:319
 msgid "Size of packets. (Default 5: abcde)"
 msgstr "Größe der Pakete. (Standard 5: abcde)"
 
-#: aleksis/core/preferences.py:314
+#: aleksis/core/preferences.py:330
 msgid "Allowed Grant Flows for OAuth applications"
 msgstr "Erlaubte Grant Flows für OAuth-Anwendungen"
 
-#: aleksis/core/preferences.py:328
+#: aleksis/core/preferences.py:344
 msgid "Available languages"
 msgstr "Verfügbare Sprachen"
 
-#: aleksis/core/preferences.py:341
+#: aleksis/core/preferences.py:357
 msgid "Send emails if data checks detect problems"
 msgstr "E-Mails versenden, wenn Datenprüfungen Probleme finden"
 
-#: aleksis/core/preferences.py:352
+#: aleksis/core/preferences.py:368
 msgid "Email recipients for data checks problem emails"
 msgstr "E-Mailempfänger für Datenprüfungsproblem-E-Mails"
 
-#: aleksis/core/preferences.py:363
+#: aleksis/core/preferences.py:379
 msgid "Email recipient groups for data checks problem emails"
 msgstr "E-Mail-Empfängergruppen für Datenprüfungsproblem-E-Mails"
 
-#: aleksis/core/preferences.py:372
+#: aleksis/core/preferences.py:388
 msgid "Show dashboard to users without login"
 msgstr "Zeige Dashboard für Benutzer ohne Login"
 
-#: aleksis/core/preferences.py:381
+#: aleksis/core/preferences.py:397
 msgid "Allow users to edit their dashboard"
 msgstr "Erlaube Benutzern, ihr Dashboard zu bearbeiten"
 
-#: aleksis/core/preferences.py:392
+#: aleksis/core/preferences.py:408
 msgid "Fields on person model which are editable by themselves."
 msgstr "Felder des Personen-Models welche von ihnen selbst editierbar sind."
 
-#: aleksis/core/preferences.py:407
+#: aleksis/core/preferences.py:423
 msgid "Editable fields on person model which should trigger a notification on change"
 msgstr "Editierbare Felder des Personen-Models welche eine Benachrichtigung für Änderungen auslösen soll"
 
-#: aleksis/core/preferences.py:421
+#: aleksis/core/preferences.py:437
 msgid "Contact for notification if a person changes their data"
 msgstr "Kontakt für Benachrichtigung, wenn eine Person ihre Daten ändert"
 
-#: aleksis/core/preferences.py:432
+#: aleksis/core/preferences.py:448
+msgid "Prefer personal photos over avatars"
+msgstr "Persönliche Fotos Avataren vorziehen"
+
+#: aleksis/core/preferences.py:458
 msgid "PDF file expiration duration"
 msgstr "PDF-Datei-Ablaufdauer"
 
-#: aleksis/core/preferences.py:433
+#: aleksis/core/preferences.py:459
 msgid "in minutes"
 msgstr "in Minuten"
 
-#: aleksis/core/preferences.py:443
+#: aleksis/core/preferences.py:469
 msgid "Automatically update the dashboard and its widgets"
 msgstr "Automatisch das Dashboard und seine Widgets aktualisieren"
 
-#: aleksis/core/preferences.py:453
+#: aleksis/core/preferences.py:479
 msgid "Automatically update the dashboard and its widgets sitewide"
 msgstr "Automatisch das Dashboard und seine Widgets aktualisieren (auf der ganzen Seite)"
 
-#: aleksis/core/preferences.py:463
+#: aleksis/core/preferences.py:489
 msgid "Country for phone number parsing"
 msgstr "Land für das Einlesen von Telefonnummern"
 
-#: aleksis/core/settings.py:529
+#: aleksis/core/settings.py:540
 msgid "English"
 msgstr "Englisch"
 
-#: aleksis/core/settings.py:530
+#: aleksis/core/settings.py:541
 msgid "German"
 msgstr "Deutsch"
 
@@ -1172,7 +1214,7 @@ msgstr "Deutsch"
 #: aleksis/core/templates/core/announcement/list.html:36
 #: aleksis/core/templates/core/group/full.html:24
 #: aleksis/core/templates/core/person/full.html:26
-#: aleksis/core/templates/core/person/full.html:98
+#: aleksis/core/templates/core/person/full.html:86
 #: aleksis/core/templates/oauth2_provider/application/detail.html:17
 msgid "Edit"
 msgstr "Bearbeiten"
@@ -1190,7 +1232,7 @@ msgstr "Aktionen"
 #: aleksis/core/templates/core/group/full.html:31
 #: aleksis/core/templates/core/pages/delete.html:22
 #: aleksis/core/templates/core/person/full.html:33
-#: aleksis/core/templates/core/person/full.html:105
+#: aleksis/core/templates/core/person/full.html:93
 #: aleksis/core/templates/oauth2_provider/application/detail.html:21
 msgid "Delete"
 msgstr "Löschen"
@@ -1253,6 +1295,10 @@ msgstr ""
 "      Sie können diese auch direkt kontaktieren:\n"
 "          "
 
+#: aleksis/core/templates/500.html:21
+msgid "Retry"
+msgstr "Wiederholen"
+
 #: aleksis/core/templates/503.html:10
 msgid ""
 "The maintenance mode is currently enabled. Please try again\n"
@@ -1553,19 +1599,15 @@ msgstr "Empfänger"
 msgid "There are no announcements."
 msgstr "Es gibt aktuell keine Ankündigungen."
 
-#: aleksis/core/templates/core/base.html:78
-msgid "Logged in as"
-msgstr "Angemeldet als"
-
-#: aleksis/core/templates/core/base.html:179
+#: aleksis/core/templates/core/base.html:213
 msgid "About AlekSIS® — The Free School Information System"
 msgstr "Über AlekSIS® — The Free School Information System"
 
-#: aleksis/core/templates/core/base.html:187
+#: aleksis/core/templates/core/base.html:221
 msgid "Imprint"
 msgstr "Impressum"
 
-#: aleksis/core/templates/core/base.html:195
+#: aleksis/core/templates/core/base.html:229
 msgid "Privacy Policy"
 msgstr "Datenschutzerklärung"
 
@@ -1817,7 +1859,7 @@ msgstr "Gruppe editieren"
 
 #: aleksis/core/templates/core/group/full.html:38
 #: aleksis/core/templates/core/person/full.html:40
-#: aleksis/core/templates/core/person/full.html:112
+#: aleksis/core/templates/core/person/full.html:100
 msgid "Change preferences"
 msgstr "Einstellungen ändern"
 
@@ -2033,11 +2075,11 @@ msgstr "Zurück"
 msgid "System checks"
 msgstr "Systemprüfungen"
 
-#: aleksis/core/templates/core/pages/system_status.html:21
+#: aleksis/core/templates/core/pages/system_status.html:22
 msgid "Maintenance mode enabled"
 msgstr "Wartungsmodus aktiviert"
 
-#: aleksis/core/templates/core/pages/system_status.html:23
+#: aleksis/core/templates/core/pages/system_status.html:24
 msgid ""
 "\n"
 "                Only admin and visitors from internal IPs can access thesite.\n"
@@ -2047,19 +2089,19 @@ msgstr ""
 "                Nur Administratoren und Besucher von internen IP-Adressen können die Seite aufrufen.\n"
 "              "
 
-#: aleksis/core/templates/core/pages/system_status.html:34
+#: aleksis/core/templates/core/pages/system_status.html:36
 msgid "Maintenance mode disabled"
 msgstr "Wartungsmodus deaktiviert"
 
-#: aleksis/core/templates/core/pages/system_status.html:35
+#: aleksis/core/templates/core/pages/system_status.html:37
 msgid "Everyone can access the site."
 msgstr "Jeder kann die Seite aufrufen."
 
-#: aleksis/core/templates/core/pages/system_status.html:45
+#: aleksis/core/templates/core/pages/system_status.html:47
 msgid "Debug mode enabled"
 msgstr "Debug-Modus aktiviert"
 
-#: aleksis/core/templates/core/pages/system_status.html:47
+#: aleksis/core/templates/core/pages/system_status.html:49
 msgid ""
 "\n"
 "                The web server throws back debug information on errors. Do not use in production!\n"
@@ -2069,11 +2111,11 @@ msgstr ""
 "                Der Server gibt Debug-Informationen bei Fehlern zurück. Nicht im Produktivbetrieb nutzen!\n"
 "              "
 
-#: aleksis/core/templates/core/pages/system_status.html:54
+#: aleksis/core/templates/core/pages/system_status.html:56
 msgid "Debug mode disabled"
 msgstr "Debug-Modus deaktivert"
 
-#: aleksis/core/templates/core/pages/system_status.html:56
+#: aleksis/core/templates/core/pages/system_status.html:58
 msgid ""
 "\n"
 "                Debug mode is disabled. Default error pages are displayed on errors.\n"
@@ -2083,42 +2125,42 @@ msgstr ""
 "                Debug-Modus ist deaktiviert. Standard-Fehlerseiten werden bei Fehlern angezeigt.\n"
 "              "
 
-#: aleksis/core/templates/core/pages/system_status.html:69
+#: aleksis/core/templates/core/pages/system_status.html:71
 msgid "System health checks"
 msgstr "Systemprüfungen"
 
-#: aleksis/core/templates/core/pages/system_status.html:75
+#: aleksis/core/templates/core/pages/system_status.html:77
 msgid "Service"
 msgstr "Dienst"
 
-#: aleksis/core/templates/core/pages/system_status.html:76
-#: aleksis/core/templates/core/pages/system_status.html:115
+#: aleksis/core/templates/core/pages/system_status.html:78
+#: aleksis/core/templates/core/pages/system_status.html:119
 msgid "Status"
 msgstr "Status"
 
-#: aleksis/core/templates/core/pages/system_status.html:77
+#: aleksis/core/templates/core/pages/system_status.html:79
 msgid "Time taken"
 msgstr "Dauer"
 
-#: aleksis/core/templates/core/pages/system_status.html:96
+#: aleksis/core/templates/core/pages/system_status.html:100
 msgid "seconds"
 msgstr "Sekunden"
 
-#: aleksis/core/templates/core/pages/system_status.html:107
+#: aleksis/core/templates/core/pages/system_status.html:111
 msgid "Celery task results"
 msgstr "Celery Task-Ergebnisse"
 
-#: aleksis/core/templates/core/pages/system_status.html:112
+#: aleksis/core/templates/core/pages/system_status.html:116
 #: aleksis/core/templates/templated_email/celery_failure.email:9
 #: aleksis/core/templates/templated_email/celery_failure.email:28
 msgid "Task"
 msgstr "Task"
 
-#: aleksis/core/templates/core/pages/system_status.html:113
+#: aleksis/core/templates/core/pages/system_status.html:117
 msgid "ID"
 msgstr "ID"
 
-#: aleksis/core/templates/core/pages/system_status.html:114
+#: aleksis/core/templates/core/pages/system_status.html:118
 msgid "Date done"
 msgstr "Erledigungszeitpunkt"
 
@@ -2171,6 +2213,18 @@ msgstr ""
 "              Gültig von %(from)s – %(until)s\n"
 "            "
 
+#: aleksis/core/templates/core/partials/avatar_content.html:14
+#: aleksis/core/templates/core/partials/avatar_content.html:15
+#: aleksis/core/templates/core/person/full.html:213
+#: aleksis/core/templates/core/person/full.html:214
+msgid "Avatar"
+msgstr "Avatar"
+
+#: aleksis/core/templates/core/partials/avatar_content.html:19
+#: aleksis/core/templates/core/partials/avatar_content.html:20
+msgid "Identicon"
+msgstr "Identicon"
+
 #: aleksis/core/templates/core/partials/crud_events.html:15
 msgid "Changed by"
 msgstr "Verändert von"
@@ -2270,24 +2324,24 @@ msgid "Edit person"
 msgstr "Person editieren"
 
 #: aleksis/core/templates/core/person/full.html:47
-#: aleksis/core/templates/core/person/full.html:119
+#: aleksis/core/templates/core/person/full.html:107
 msgid "Impersonate"
 msgstr "Verkleiden"
 
 #: aleksis/core/templates/core/person/full.html:54
-#: aleksis/core/templates/core/person/full.html:126
+#: aleksis/core/templates/core/person/full.html:114
 msgid "Invite user"
 msgstr "Benutzer einladen"
 
-#: aleksis/core/templates/core/person/full.html:133
+#: aleksis/core/templates/core/person/full.html:121
 msgid "Contact details"
 msgstr "Kontaktdetails"
 
-#: aleksis/core/templates/core/person/full.html:224
+#: aleksis/core/templates/core/person/full.html:220
 msgid "This person didn't upload a personal photo."
 msgstr "Diese Person hat kein persönliches Foto hochgeladen."
 
-#: aleksis/core/templates/core/person/full.html:232
+#: aleksis/core/templates/core/person/full.html:228
 msgid "Children"
 msgstr "Kinder"
 
@@ -2337,9 +2391,7 @@ msgstr "Die Einladungsfunktion ist deaktiviert."
 
 #: aleksis/core/templates/invitations/disabled.html:15
 msgid "To enable it, switch on the corresponding checkbox in the authentication section of the "
-msgstr ""
-"Um sie zu aktivieren, nutzen Sie die Checkbox im Abschnitt \"Authentifikation"
-"\" der "
+msgstr "Um sie zu aktivieren, nutzen Sie die Checkbox im Abschnitt \"Authentifikation\" der "
 
 #: aleksis/core/templates/invitations/disabled.html:16
 msgid "site preferences page"
@@ -2498,15 +2550,11 @@ msgstr "Keine autorisierten Anwendungen."
 msgid "Network error"
 msgstr "Netzwerkfehler"
 
-#: aleksis/core/templates/offline.html:8
-msgid ""
-"No internet\n"
-"    connection."
-msgstr ""
-"Keine\n"
-"    Internetverbindung."
+#: aleksis/core/templates/offline.html:10
+msgid "No internet connection."
+msgstr "Keine Internetverbindung."
 
-#: aleksis/core/templates/offline.html:12
+#: aleksis/core/templates/offline.html:14
 msgid ""
 "\n"
 "      There was an error accessing this page. You probably don't have an internet connection. Check to see if your WiFi\n"
@@ -2788,7 +2836,7 @@ msgstr ""
 #: aleksis/core/templates/two_factor/_base_focus.html:6
 #: aleksis/core/templates/two_factor/core/otp_required.html:22
 #: aleksis/core/templates/two_factor/core/setup.html:5
-#: aleksis/core/templates/two_factor/profile/profile.html:87
+#: aleksis/core/templates/two_factor/profile/profile.html:88
 msgid "Enable Two-Factor Authentication"
 msgstr "Zwei-Faktor-Authentifizierung aktivieren"
 
@@ -2920,15 +2968,15 @@ msgstr "Gerät aktuell nicht verfügbar?"
 msgid "Or, alternatively, use one of your backup phones:"
 msgstr "Oder, alternativ, nutzen Sie eins Ihrer Backup-Telefone:"
 
-#: aleksis/core/templates/two_factor/core/login.html:121
+#: aleksis/core/templates/two_factor/core/login.html:122
 msgid "As a last resort, you can use a backup token:"
 msgstr "Als letzte Möglichkeit können Sie einen Backup-Token nutzen:"
 
-#: aleksis/core/templates/two_factor/core/login.html:124
+#: aleksis/core/templates/two_factor/core/login.html:125
 msgid "Use Backup Token"
 msgstr "Backup-Token nutzen"
 
-#: aleksis/core/templates/two_factor/core/login.html:135
+#: aleksis/core/templates/two_factor/core/login.html:136
 msgid "Use alternative login options"
 msgstr "Alternative Anmeldemöglichkeiten nutzen"
 
@@ -3241,11 +3289,11 @@ msgstr ""
 "          Accountsicherheit.\n"
 "      "
 
-#: aleksis/core/util/notifications.py:63
+#: aleksis/core/util/notifications.py:64
 msgid "E-Mail"
 msgstr "E-Mail"
 
-#: aleksis/core/util/notifications.py:64
+#: aleksis/core/util/notifications.py:65
 msgid "SMS"
 msgstr "SMS"
 
@@ -3269,162 +3317,166 @@ msgstr "Es ist ein Fehler beim Generieren der PDF-Datei aufgetreten."
 msgid "Download PDF"
 msgstr "PDF herunterladen"
 
-#: aleksis/core/views.py:285
+#: aleksis/core/views.py:289
 msgid "The school term has been created."
 msgstr "Das Schuljahr wurde erstellt."
 
-#: aleksis/core/views.py:297
+#: aleksis/core/views.py:301
 msgid "The school term has been saved."
 msgstr "Das Schuljahr wurde gespeichert."
 
-#: aleksis/core/views.py:417
+#: aleksis/core/views.py:421
 msgid "The child groups were successfully saved."
 msgstr "Die Untergruppen wurden gespeichert."
 
-#: aleksis/core/views.py:436 aleksis/core/views.py:446
+#: aleksis/core/views.py:440 aleksis/core/views.py:450
 msgid "The person has been saved."
 msgstr "Die Person wurde gespeichert."
 
-#: aleksis/core/views.py:496
+#: aleksis/core/views.py:500
 msgid "The group has been saved."
 msgstr "Die Gruppe wurde gespeichert."
 
-#: aleksis/core/views.py:593
+#: aleksis/core/views.py:597
 msgid "The announcement has been saved."
 msgstr "Die Ankündigung wurde gespeichert."
 
-#: aleksis/core/views.py:609
+#: aleksis/core/views.py:613
 msgid "The announcement has been deleted."
 msgstr "Ankündigung wurde gelöscht."
 
-#: aleksis/core/views.py:677
+#: aleksis/core/views.py:681
 msgid "The requested preference registry does not exist"
 msgstr "Das angeforderte Einstellungsregister existiert nicht"
 
-#: aleksis/core/views.py:696
+#: aleksis/core/views.py:700
 msgid "The preferences have been saved successfully."
 msgstr "Die Einstellungen wurde gespeichert."
 
-#: aleksis/core/views.py:720
+#: aleksis/core/views.py:724
 msgid "The person has been deleted."
 msgstr "Die Person wurde gelöscht."
 
-#: aleksis/core/views.py:734
+#: aleksis/core/views.py:738
 msgid "The group has been deleted."
 msgstr "Die Gruppe wurde gelöscht."
 
-#: aleksis/core/views.py:766
+#: aleksis/core/views.py:770
 msgid "The additional_field has been saved."
 msgstr "Das zusätzliche Feld wurde gespeichert."
 
-#: aleksis/core/views.py:800
+#: aleksis/core/views.py:804
 msgid "The additional field has been deleted."
 msgstr "Das zusätzliche Feld wurde gelöscht."
 
-#: aleksis/core/views.py:825
+#: aleksis/core/views.py:829
 msgid "The group type has been saved."
 msgstr "Der Gruppentyp wurde gespeichert."
 
-#: aleksis/core/views.py:855
+#: aleksis/core/views.py:859
 msgid "The group type has been deleted."
 msgstr "Der Gruppentyp wurde gelöscht."
 
-#: aleksis/core/views.py:888
+#: aleksis/core/views.py:892
 msgid "Progress: Run data checks"
 msgstr "Fortschritt: Datenprüfungen ausführen"
 
-#: aleksis/core/views.py:889
+#: aleksis/core/views.py:893
 msgid "Run data checks …"
 msgstr "Datenprüfungen laufen …"
 
-#: aleksis/core/views.py:890
+#: aleksis/core/views.py:894
 msgid "The data checks were run successfully."
 msgstr "Die Datenprüfungen wurden erfolgreich ausgeführt."
 
-#: aleksis/core/views.py:891
+#: aleksis/core/views.py:895
 msgid "There was a problem while running data checks."
 msgstr "Es gab ein Problem beim Ausführen der Datenprüfungen."
 
-#: aleksis/core/views.py:907
+#: aleksis/core/views.py:911
 #, python-brace-format
 msgid "The solve option '{solve_option_obj.verbose_name}' "
 msgstr "Die Lösungsoption \"{solve_option_obj.verbose_name}\" "
 
-#: aleksis/core/views.py:917
+#: aleksis/core/views.py:921
 msgid "The requested solve option does not exist"
 msgstr "Die angeforderte Lösungsoption existiert nicht"
 
-#: aleksis/core/views.py:949
+#: aleksis/core/views.py:953
 msgid "The dashboard widget has been saved."
 msgstr "Das Dashboard-Widget wurde gespeichert."
 
-#: aleksis/core/views.py:979
+#: aleksis/core/views.py:983
 msgid "The dashboard widget has been created."
 msgstr "Das Dashboard-Widget wurde erstellt."
 
-#: aleksis/core/views.py:989
+#: aleksis/core/views.py:993
 msgid "The dashboard widget has been deleted."
 msgstr "Das Dashboard-Widget wurde gelöscht."
 
-#: aleksis/core/views.py:1060
+#: aleksis/core/views.py:1064
 msgid "Your dashboard configuration has been saved successfully."
 msgstr "Ihre Dashboardkonfiguration wurde erfolgreich gespeichert."
 
-#: aleksis/core/views.py:1062
+#: aleksis/core/views.py:1066
 msgid "The configuration of the default dashboard has been saved successfully."
 msgstr "Die Konfiguration des Standard-Dashboardes wurde erfolgreich gespeichert."
 
-#: aleksis/core/views.py:1138
+#: aleksis/core/views.py:1142
 #, python-brace-format
 msgid "The invitation was successfully created. The invitation code is {code}"
 msgstr "Die Einladung wurde erfolgreich erstellt. Der Einladungscode ist {code}"
 
-#: aleksis/core/views.py:1229
+#: aleksis/core/views.py:1233
 msgid "We have successfully assigned the permissions."
 msgstr "Wir haben die Berechtigungen erfolgreich zugewiesen."
 
-#: aleksis/core/views.py:1239
+#: aleksis/core/views.py:1243
 msgid "The global user permission has been deleted."
 msgstr "Die globale Benutzerberechtigung wurde gelöscht."
 
-#: aleksis/core/views.py:1249
+#: aleksis/core/views.py:1253
 msgid "The global group permission has been deleted."
 msgstr "Die globale Gruppenberechtigung wurde gelöscht."
 
-#: aleksis/core/views.py:1259
+#: aleksis/core/views.py:1263
 msgid "The object user permission has been deleted."
 msgstr "Die Objekt-Benutzerberechtigung wurde gelöscht."
 
-#: aleksis/core/views.py:1269
+#: aleksis/core/views.py:1273
 msgid "The object group permission has been deleted."
 msgstr "Die Objekt-Gruppenberechtigung wurde gelöscht."
 
-#: aleksis/core/views.py:1337
+#: aleksis/core/views.py:1341
 msgid "The requested PDF file does not exist"
 msgstr "Die angeforderte PDF-Datei existiert nicht"
 
-#: aleksis/core/views.py:1346 aleksis/core/views.py:1350
+#: aleksis/core/views.py:1350 aleksis/core/views.py:1354
 msgid "The requested task does not exist or is not accessible"
 msgstr "Der angeforderte Task existiert nicht oder ist nicht abrufbar"
 
-#: aleksis/core/views.py:1388
+#: aleksis/core/views.py:1406
 msgid "The third-party account could not be disconnected because it is the only login method available."
 msgstr "Das Drittanbieter-Konto konnte nicht deaktiviert werden, weil es die einzige verfügbare Anmeldeoption ist."
 
-#: aleksis/core/views.py:1395
+#: aleksis/core/views.py:1413
 msgid "The third-party account has been successfully disconnected."
 msgstr "Das Drittanbieter-Konto wurde erfolgreich getrennt."
 
-#: aleksis/core/views.py:1466
+#: aleksis/core/views.py:1484
 msgid "Person was invited successfully and an email with further instructions has been send to them."
-msgstr ""
-"Die Person wurde erfolgreich eingeladen und eine E-Mail mit weiteren "
-"Anweisungen wurde an sie verschickt."
+msgstr "Die Person wurde erfolgreich eingeladen und eine E-Mail mit weiteren Anweisungen wurde an sie verschickt."
 
-#: aleksis/core/views.py:1477
+#: aleksis/core/views.py:1495
 msgid "Person was already invited."
 msgstr "Person wurde bereits eingeladen."
 
+#~ msgid "Me"
+#~ msgstr "Ich"
+
+#~ msgid "Logged in as"
+#~ msgstr "Angemeldet als"
+
 #~ msgid "Person was invited successfully."
 #~ msgstr "Person wurde erfolgreich eingeladen."
 
diff --git a/aleksis/core/locale/de_DE/LC_MESSAGES/djangojs.po b/aleksis/core/locale/de_DE/LC_MESSAGES/djangojs.po
index 376788f8a7ea941b89dff0620fdbc78a40c106a5..c0b719bf0961c3f0c22071603f7df5b175571354 100644
--- a/aleksis/core/locale/de_DE/LC_MESSAGES/djangojs.po
+++ b/aleksis/core/locale/de_DE/LC_MESSAGES/djangojs.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:20+0100\n"
 "PO-Revision-Date: 2021-10-28 14:37+0000\n"
 "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
 "Language-Team: German <https://translate.edugit.org/projects/aleksis/aleksis-core-js/de/>\n"
@@ -30,6 +30,6 @@ msgstr "Abbrechen"
 msgid "OK"
 msgstr "OK"
 
-#: aleksis/core/static/js/main.js:191
+#: aleksis/core/static/js/main.js:195
 msgid "This page may contain outdated information since there is no internet connection."
 msgstr "Diese Seite enthält vielleicht veraltete Informationen, da es keine Internetverbindung gibt."
diff --git a/aleksis/core/locale/fr/LC_MESSAGES/django.po b/aleksis/core/locale/fr/LC_MESSAGES/django.po
index ac9b5f99c721e75f4dd0e5bf94368eaee2c7867f..231a5f739692d128aa759a0b397450df36c54169 100644
--- a/aleksis/core/locale/fr/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/fr/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: AlekSIS (School Information System) 0.1\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:19+0100\n"
 "PO-Revision-Date: 2021-06-16 12:00+0000\n"
 "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
 "Language-Team: French <https://translate.edugit.org/projects/aleksis/aleksis/fr/>\n"
@@ -40,10 +40,10 @@ msgstr "Détails de contact"
 msgid "Home and mobile phone"
 msgstr ""
 
-#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:265
-#: aleksis/core/models.py:462 aleksis/core/templates/core/group/list.html:8
+#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:185
+#: aleksis/core/models.py:487 aleksis/core/templates/core/group/list.html:8
 #: aleksis/core/templates/core/group/list.html:9
-#: aleksis/core/templates/core/person/full.html:250
+#: aleksis/core/templates/core/person/full.html:246
 #, fuzzy
 #| msgid "Group"
 msgid "Groups"
@@ -70,8 +70,8 @@ msgstr ""
 msgid "The DashboardWidget was reported broken automatically."
 msgstr ""
 
-#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:105
-#: aleksis/core/templates/core/base.html:106
+#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:139
+#: aleksis/core/templates/core/base.html:140
 #: aleksis/core/templates/core/group/list.html:20
 #: aleksis/core/templates/core/person/list.html:24
 #: aleksis/core/templates/search/search.html:7
@@ -97,11 +97,11 @@ msgstr ""
 msgid "Content type"
 msgstr ""
 
-#: aleksis/core/filters.py:113 aleksis/core/models.py:688
+#: aleksis/core/filters.py:113 aleksis/core/models.py:713
 msgid "User"
 msgstr ""
 
-#: aleksis/core/filters.py:135 aleksis/core/models.py:461
+#: aleksis/core/filters.py:135 aleksis/core/models.py:486
 msgid "Group"
 msgstr "groupe"
 
@@ -141,7 +141,7 @@ msgstr ""
 msgid "This username is already in use."
 msgstr "Cet nom est deja en utilisation."
 
-#: aleksis/core/forms.py:153 aleksis/core/models.py:130
+#: aleksis/core/forms.py:153 aleksis/core/models.py:134
 msgid "School term"
 msgstr ""
 
@@ -152,7 +152,7 @@ msgid "Common data"
 msgstr "Détails de contact"
 
 #: aleksis/core/forms.py:155 aleksis/core/forms.py:207
-#: aleksis/core/menus.py:254 aleksis/core/models.py:153
+#: aleksis/core/menus.py:174 aleksis/core/models.py:157
 #: aleksis/core/templates/core/person/list.html:8
 #: aleksis/core/templates/core/person/list.html:9
 #, fuzzy
@@ -166,18 +166,18 @@ msgstr "Personne"
 msgid "Additional data"
 msgstr "Détails de contact"
 
-#: aleksis/core/forms.py:157 aleksis/core/models.py:206
-#: aleksis/core/models.py:514
+#: aleksis/core/forms.py:157 aleksis/core/models.py:210
+#: aleksis/core/models.py:539
 msgid "Photo"
 msgstr ""
 
 #: aleksis/core/forms.py:199 aleksis/core/forms.py:202
-#: aleksis/core/models.py:73
+#: aleksis/core/models.py:77
 msgid "Date"
 msgstr "Date"
 
 #: aleksis/core/forms.py:200 aleksis/core/forms.py:203
-#: aleksis/core/models.py:81
+#: aleksis/core/models.py:85
 msgid "Time"
 msgstr ""
 
@@ -213,11 +213,11 @@ msgstr ""
 msgid "Please enter your invitation code."
 msgstr ""
 
-#: aleksis/core/forms.py:418 aleksis/core/models.py:181
+#: aleksis/core/forms.py:418 aleksis/core/models.py:185
 msgid "First name"
 msgstr "Prénom"
 
-#: aleksis/core/forms.py:419 aleksis/core/models.py:182
+#: aleksis/core/forms.py:419 aleksis/core/models.py:186
 msgid "Last name"
 msgstr "Nom de famille"
 
@@ -267,7 +267,15 @@ msgstr ""
 msgid "Password (again)"
 msgstr ""
 
-#: aleksis/core/forms.py:775
+#: aleksis/core/forms.py:728
+msgid "The selected action does not exist."
+msgstr ""
+
+#: aleksis/core/forms.py:739
+msgid "You do not have permission to run {} on all selected objects."
+msgstr ""
+
+#: aleksis/core/forms.py:795
 msgid "No valid selection."
 msgstr ""
 
@@ -310,123 +318,76 @@ msgstr ""
 msgid "Dashboard"
 msgstr ""
 
-#: aleksis/core/menus.py:41 aleksis/core/models.py:734
-#: aleksis/core/preferences.py:29
+#: aleksis/core/menus.py:41 aleksis/core/models.py:765
+#: aleksis/core/preferences.py:29 aleksis/core/templates/core/base.html:81
 #: aleksis/core/templates/core/notifications.html:4
 #: aleksis/core/templates/core/notifications.html:5
 msgid "Notifications"
 msgstr ""
 
 #: aleksis/core/menus.py:53
-msgid "Account"
-msgstr ""
-
-#: aleksis/core/menus.py:60
-msgid "Stop impersonation"
-msgstr ""
-
-#: aleksis/core/menus.py:69 aleksis/core/templates/core/base.html:80
-msgid "Logout"
-msgstr ""
-
-#: aleksis/core/menus.py:75
-msgid "2FA"
-msgstr ""
-
-#: aleksis/core/menus.py:83
-#: aleksis/core/templates/account/password_change.html:5
-#: aleksis/core/templates/account/password_change.html:6
-#: aleksis/core/templates/account/password_change.html:19
-#: aleksis/core/templates/account/password_reset_from_key.html:5
-#: aleksis/core/templates/account/password_reset_from_key.html:42
-#: aleksis/core/templates/account/password_reset_from_key.html:46
-#: aleksis/core/templates/account/password_reset_from_key_done.html:5
-#: aleksis/core/templates/account/password_reset_from_key_done.html:6
-msgid "Change password"
-msgstr ""
-
-#: aleksis/core/menus.py:95
-msgid "Me"
-msgstr ""
-
-#: aleksis/core/menus.py:104
-#: aleksis/core/templates/dynamic_preferences/form.html:5
-msgid "Preferences"
-msgstr ""
-
-#: aleksis/core/menus.py:113
-msgid "Third-party accounts"
-msgstr ""
-
-#: aleksis/core/menus.py:122
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
-msgid "Authorized applications"
-msgstr ""
-
-#: aleksis/core/menus.py:133
 msgid "Admin"
 msgstr ""
 
-#: aleksis/core/menus.py:141 aleksis/core/models.py:834
+#: aleksis/core/menus.py:61 aleksis/core/models.py:865
 #: aleksis/core/templates/core/announcement/list.html:7
 #: aleksis/core/templates/core/announcement/list.html:8
 msgid "Announcements"
 msgstr ""
 
-#: aleksis/core/menus.py:152 aleksis/core/models.py:131
+#: aleksis/core/menus.py:72 aleksis/core/models.py:135
 #: aleksis/core/templates/core/school_term/list.html:8
 #: aleksis/core/templates/core/school_term/list.html:9
 msgid "School terms"
 msgstr ""
 
-#: aleksis/core/menus.py:163
+#: aleksis/core/menus.py:83
 #: aleksis/core/templates/core/dashboard_widget/list.html:8
 #: aleksis/core/templates/core/dashboard_widget/list.html:9
 msgid "Dashboard widgets"
 msgstr ""
 
-#: aleksis/core/menus.py:174
+#: aleksis/core/menus.py:94
 #: aleksis/core/templates/core/management/data_management.html:6
 #: aleksis/core/templates/core/management/data_management.html:7
 msgid "Data management"
 msgstr ""
 
-#: aleksis/core/menus.py:185
+#: aleksis/core/menus.py:105
 #: aleksis/core/templates/core/pages/system_status.html:5
 #: aleksis/core/templates/core/pages/system_status.html:7
 msgid "System status"
 msgstr ""
 
-#: aleksis/core/menus.py:196
+#: aleksis/core/menus.py:116
 msgid "Configuration"
 msgstr ""
 
-#: aleksis/core/menus.py:207 aleksis/core/templates/core/data_check/list.html:9
+#: aleksis/core/menus.py:127 aleksis/core/templates/core/data_check/list.html:9
 #: aleksis/core/templates/core/data_check/list.html:10
 msgid "Data checks"
 msgstr ""
 
-#: aleksis/core/menus.py:213 aleksis/core/templates/core/perms/list.html:13
+#: aleksis/core/menus.py:133 aleksis/core/templates/core/perms/list.html:13
 #: aleksis/core/templates/core/perms/list.html:14
 msgid "Manage permissions"
 msgstr ""
 
-#: aleksis/core/menus.py:224
+#: aleksis/core/menus.py:144
 msgid "Backend Admin"
 msgstr ""
 
-#: aleksis/core/menus.py:232
+#: aleksis/core/menus.py:152
 #: aleksis/core/templates/oauth2_provider/application/list.html:5
 #: aleksis/core/templates/oauth2_provider/application/list.html:6
 msgid "OAuth2 Applications"
 msgstr ""
 
-#: aleksis/core/menus.py:245
+#: aleksis/core/menus.py:165
 msgid "People"
 msgstr ""
 
-#: aleksis/core/menus.py:276 aleksis/core/models.py:1055
+#: aleksis/core/menus.py:196 aleksis/core/models.py:1099
 #: aleksis/core/templates/core/group_type/list.html:8
 #: aleksis/core/templates/core/group_type/list.html:9
 #, fuzzy
@@ -434,594 +395,663 @@ msgstr ""
 msgid "Group types"
 msgstr "Groupe"
 
-#: aleksis/core/menus.py:287
+#: aleksis/core/menus.py:207
 msgid "Groups and child groups"
 msgstr ""
 
-#: aleksis/core/menus.py:298 aleksis/core/models.py:510
+#: aleksis/core/menus.py:218 aleksis/core/models.py:535
 #: aleksis/core/templates/core/additional_field/list.html:8
 #: aleksis/core/templates/core/additional_field/list.html:9
 msgid "Additional fields"
 msgstr ""
 
-#: aleksis/core/menus.py:309
+#: aleksis/core/menus.py:229
 #, fuzzy
 #| msgid "Contact details"
 msgid "Invite person"
 msgstr "Détails de contact"
 
-#: aleksis/core/menus.py:322
+#: aleksis/core/menus.py:242
 #: aleksis/core/templates/core/group/child_groups.html:7
 #: aleksis/core/templates/core/group/child_groups.html:9
 msgid "Assign child groups to groups"
 msgstr ""
 
+#: aleksis/core/menus.py:254
+msgid "Stop impersonation"
+msgstr ""
+
+#: aleksis/core/menus.py:263
+msgid "Account"
+msgstr ""
+
+#: aleksis/core/menus.py:272
+#: aleksis/core/templates/dynamic_preferences/form.html:5
+msgid "Preferences"
+msgstr ""
+
+#: aleksis/core/menus.py:281
+msgid "2FA"
+msgstr ""
+
+#: aleksis/core/menus.py:289
+#: aleksis/core/templates/account/password_change.html:5
+#: aleksis/core/templates/account/password_change.html:6
+#: aleksis/core/templates/account/password_change.html:19
+#: aleksis/core/templates/account/password_reset_from_key.html:5
+#: aleksis/core/templates/account/password_reset_from_key.html:42
+#: aleksis/core/templates/account/password_reset_from_key.html:46
+#: aleksis/core/templates/account/password_reset_from_key_done.html:5
+#: aleksis/core/templates/account/password_reset_from_key_done.html:6
+msgid "Change password"
+msgstr ""
+
+#: aleksis/core/menus.py:301
+msgid "Third-party accounts"
+msgstr ""
+
+#: aleksis/core/menus.py:310
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
+msgid "Authorized applications"
+msgstr ""
+
+#: aleksis/core/menus.py:320
+msgid "Logout"
+msgstr ""
+
 #: aleksis/core/mixins.py:511
 msgid "Linked school term"
 msgstr ""
 
-#: aleksis/core/models.py:71
+#: aleksis/core/models.py:75
 msgid "Boolean (Yes/No)"
 msgstr ""
 
-#: aleksis/core/models.py:72
+#: aleksis/core/models.py:76
 msgid "Text (one line)"
 msgstr ""
 
-#: aleksis/core/models.py:74
+#: aleksis/core/models.py:78
 msgid "Date and time"
 msgstr ""
 
-#: aleksis/core/models.py:75
+#: aleksis/core/models.py:79
 msgid "Decimal number"
 msgstr ""
 
-#: aleksis/core/models.py:76 aleksis/core/models.py:199
+#: aleksis/core/models.py:80 aleksis/core/models.py:203
 msgid "E-mail address"
 msgstr ""
 
-#: aleksis/core/models.py:77
+#: aleksis/core/models.py:81
 msgid "Integer"
 msgstr ""
 
-#: aleksis/core/models.py:78
+#: aleksis/core/models.py:82
 msgid "IP address"
 msgstr ""
 
-#: aleksis/core/models.py:79
+#: aleksis/core/models.py:83
 msgid "Boolean or empty (Yes/No/Neither)"
 msgstr ""
 
-#: aleksis/core/models.py:80
+#: aleksis/core/models.py:84
 msgid "Text (multi-line)"
 msgstr ""
 
-#: aleksis/core/models.py:82
+#: aleksis/core/models.py:86
 msgid "URL / Link"
 msgstr ""
 
-#: aleksis/core/models.py:94 aleksis/core/models.py:1024
+#: aleksis/core/models.py:98 aleksis/core/models.py:1068
 msgid "Name"
 msgstr ""
 
-#: aleksis/core/models.py:96
+#: aleksis/core/models.py:100
 #, fuzzy
 #| msgid "Contact details"
 msgid "Start date"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:97
+#: aleksis/core/models.py:101
 msgid "End date"
 msgstr ""
 
-#: aleksis/core/models.py:116
+#: aleksis/core/models.py:120
 msgid "The start date must be earlier than the end date."
 msgstr ""
 
-#: aleksis/core/models.py:123
+#: aleksis/core/models.py:127
 msgid "There is already a school term for this time or a part of this time."
 msgstr ""
 
-#: aleksis/core/models.py:152 aleksis/core/models.py:973
+#: aleksis/core/models.py:156 aleksis/core/models.py:1017
 msgid "Person"
 msgstr "Personne"
 
-#: aleksis/core/models.py:155
+#: aleksis/core/models.py:159
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can view address"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:156
+#: aleksis/core/models.py:160
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can view contact details"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:157
+#: aleksis/core/models.py:161
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can view photo"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:158
+#: aleksis/core/models.py:162
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can view avatar image"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:159
+#: aleksis/core/models.py:163
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can view persons groups"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:160
+#: aleksis/core/models.py:164
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can view personal details"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "female"
 msgstr ""
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "male"
 msgstr ""
 
-#: aleksis/core/models.py:178 aleksis/core/models.py:1227
+#: aleksis/core/models.py:182 aleksis/core/models.py:1271
 msgid "Linked user"
 msgstr ""
 
-#: aleksis/core/models.py:184
+#: aleksis/core/models.py:188
 msgid "Additional name(s)"
 msgstr ""
 
-#: aleksis/core/models.py:188 aleksis/core/models.py:479
+#: aleksis/core/models.py:192 aleksis/core/models.py:504
 #, fuzzy
 #| msgid "First name"
 msgid "Short name"
 msgstr "Prénom"
 
-#: aleksis/core/models.py:191
+#: aleksis/core/models.py:195
 msgid "Street"
 msgstr ""
 
-#: aleksis/core/models.py:192
+#: aleksis/core/models.py:196
 msgid "Street number"
 msgstr ""
 
-#: aleksis/core/models.py:193
+#: aleksis/core/models.py:197
 msgid "Postal code"
 msgstr ""
 
-#: aleksis/core/models.py:194
+#: aleksis/core/models.py:198
 msgid "Place"
 msgstr ""
 
-#: aleksis/core/models.py:196 aleksis/core/templates/core/person/full.html:172
+#: aleksis/core/models.py:200 aleksis/core/templates/core/person/full.html:160
 msgid "Home phone"
 msgstr ""
 
-#: aleksis/core/models.py:197 aleksis/core/templates/core/person/full.html:182
+#: aleksis/core/models.py:201 aleksis/core/templates/core/person/full.html:170
 msgid "Mobile phone"
 msgstr ""
 
-#: aleksis/core/models.py:201
+#: aleksis/core/models.py:205
 msgid "Date of birth"
 msgstr "Date d'anniversaire"
 
-#: aleksis/core/models.py:202
+#: aleksis/core/models.py:206
 #, fuzzy
 #| msgid "Date of birth"
 msgid "Place of birth"
 msgstr "Date d'anniversaire"
 
-#: aleksis/core/models.py:203
+#: aleksis/core/models.py:207
 msgid "Sex"
 msgstr "Sexe"
 
-#: aleksis/core/models.py:210 aleksis/core/models.py:518
+#: aleksis/core/models.py:214 aleksis/core/models.py:543
 msgid "This is an official photo, used for official documents and for internal use cases."
 msgstr ""
 
-#: aleksis/core/models.py:215 aleksis/core/models.py:522
+#: aleksis/core/models.py:219 aleksis/core/models.py:547
 msgid "Display picture / Avatar"
 msgstr ""
 
-#: aleksis/core/models.py:218 aleksis/core/models.py:525
+#: aleksis/core/models.py:222 aleksis/core/models.py:550
 msgid "This is a picture or an avatar for public display."
 msgstr ""
 
-#: aleksis/core/models.py:223 aleksis/core/templates/core/person/full.html:239
+#: aleksis/core/models.py:227 aleksis/core/templates/core/person/full.html:235
 msgid "Guardians / Parents"
 msgstr ""
 
-#: aleksis/core/models.py:230
+#: aleksis/core/models.py:234
 msgid "Primary group"
 msgstr ""
 
-#: aleksis/core/models.py:233 aleksis/core/models.py:692
-#: aleksis/core/models.py:716 aleksis/core/models.py:801
-#: aleksis/core/models.py:1048
+#: aleksis/core/models.py:237 aleksis/core/models.py:717
+#: aleksis/core/models.py:741 aleksis/core/models.py:832
+#: aleksis/core/models.py:1092
 msgid "Description"
 msgstr "Description"
 
-#: aleksis/core/models.py:434
+#: aleksis/core/models.py:457
 msgid "Title of field"
 msgstr ""
 
-#: aleksis/core/models.py:436
+#: aleksis/core/models.py:459
 msgid "Type of field"
 msgstr ""
 
-#: aleksis/core/models.py:443
+#: aleksis/core/models.py:461
+msgid "Required"
+msgstr ""
+
+#: aleksis/core/models.py:462
+#, fuzzy
+#| msgid "Description"
+msgid "Help text / description"
+msgstr "Description"
+
+#: aleksis/core/models.py:468
 msgid "Addtitional field for groups"
 msgstr ""
 
-#: aleksis/core/models.py:444
+#: aleksis/core/models.py:469
 msgid "Addtitional fields for groups"
 msgstr ""
 
-#: aleksis/core/models.py:464
+#: aleksis/core/models.py:489
 msgid "Can assign child groups to groups"
 msgstr ""
 
-#: aleksis/core/models.py:465
+#: aleksis/core/models.py:490
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can view statistics about group."
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:477
+#: aleksis/core/models.py:502
 #, fuzzy
 #| msgid "Last name"
 msgid "Long name"
 msgstr "Nom de famille"
 
-#: aleksis/core/models.py:487 aleksis/core/templates/core/group/full.html:85
+#: aleksis/core/models.py:512 aleksis/core/templates/core/group/full.html:85
 msgid "Members"
 msgstr ""
 
-#: aleksis/core/models.py:490 aleksis/core/templates/core/group/full.html:82
+#: aleksis/core/models.py:515 aleksis/core/templates/core/group/full.html:82
 msgid "Owners"
 msgstr "Propriétaires"
 
-#: aleksis/core/models.py:497 aleksis/core/templates/core/group/full.html:55
+#: aleksis/core/models.py:522 aleksis/core/templates/core/group/full.html:55
 msgid "Parent groups"
 msgstr ""
 
-#: aleksis/core/models.py:505
+#: aleksis/core/models.py:530
 msgid "Type of group"
 msgstr ""
 
-#: aleksis/core/models.py:691 aleksis/core/models.py:715
-#: aleksis/core/models.py:800
+#: aleksis/core/models.py:716 aleksis/core/models.py:740
+#: aleksis/core/models.py:831
 #: aleksis/core/templates/core/announcement/list.html:18
 msgid "Title"
 msgstr ""
 
-#: aleksis/core/models.py:694
+#: aleksis/core/models.py:719
 msgid "Application"
 msgstr ""
 
-#: aleksis/core/models.py:700
+#: aleksis/core/models.py:725
 msgid "Activity"
 msgstr ""
 
-#: aleksis/core/models.py:701
+#: aleksis/core/models.py:726
 msgid "Activities"
 msgstr ""
 
-#: aleksis/core/models.py:707
+#: aleksis/core/models.py:732
 msgid "Sender"
 msgstr ""
 
-#: aleksis/core/models.py:712
+#: aleksis/core/models.py:737
 msgid "Recipient"
 msgstr ""
 
-#: aleksis/core/models.py:717 aleksis/core/models.py:1025
+#: aleksis/core/models.py:742 aleksis/core/models.py:1069
 msgid "Link"
 msgstr ""
 
-#: aleksis/core/models.py:719
+#: aleksis/core/models.py:744
+msgid "Send notification at"
+msgstr ""
+
+#: aleksis/core/models.py:746
 msgid "Read"
 msgstr ""
 
-#: aleksis/core/models.py:720
+#: aleksis/core/models.py:747
 msgid "Sent"
 msgstr ""
 
-#: aleksis/core/models.py:733
+#: aleksis/core/models.py:764
 msgid "Notification"
 msgstr ""
 
-#: aleksis/core/models.py:802
+#: aleksis/core/models.py:833
 msgid "Link to detailed view"
 msgstr ""
 
-#: aleksis/core/models.py:805
+#: aleksis/core/models.py:836
 msgid "Date and time from when to show"
 msgstr ""
 
-#: aleksis/core/models.py:808
+#: aleksis/core/models.py:839
 msgid "Date and time until when to show"
 msgstr ""
 
-#: aleksis/core/models.py:833
+#: aleksis/core/models.py:864
 msgid "Announcement"
 msgstr ""
 
-#: aleksis/core/models.py:871
+#: aleksis/core/models.py:902
 msgid "Announcement recipient"
 msgstr ""
 
-#: aleksis/core/models.py:872
+#: aleksis/core/models.py:903
 msgid "Announcement recipients"
 msgstr ""
 
-#: aleksis/core/models.py:894
+#: aleksis/core/models.py:925
 msgid "Widget Title"
 msgstr ""
 
-#: aleksis/core/models.py:895
+#: aleksis/core/models.py:926
 msgid "Activate Widget"
 msgstr ""
 
-#: aleksis/core/models.py:896
+#: aleksis/core/models.py:927
 msgid "Widget is broken"
 msgstr ""
 
-#: aleksis/core/models.py:899
+#: aleksis/core/models.py:930
 msgid "Size on mobile devices"
 msgstr ""
 
-#: aleksis/core/models.py:900
+#: aleksis/core/models.py:931
 msgid "<= 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:905
+#: aleksis/core/models.py:936
 msgid "Size on tablet devices"
 msgstr ""
 
-#: aleksis/core/models.py:906
+#: aleksis/core/models.py:937
 msgid "> 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:911
+#: aleksis/core/models.py:942
 msgid "Size on desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:912
+#: aleksis/core/models.py:943
 msgid "> 992 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:917
+#: aleksis/core/models.py:948
 msgid "Size on large desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:918
+#: aleksis/core/models.py:949
 msgid "> 1200 px>, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:949
+#: aleksis/core/models.py:980
 msgid "Can edit default dashboard"
 msgstr ""
 
-#: aleksis/core/models.py:950
+#: aleksis/core/models.py:981
 msgid "Dashboard Widget"
 msgstr ""
 
-#: aleksis/core/models.py:951
+#: aleksis/core/models.py:982
 msgid "Dashboard Widgets"
 msgstr ""
 
-#: aleksis/core/models.py:957
+#: aleksis/core/models.py:988
 msgid "URL"
 msgstr ""
 
-#: aleksis/core/models.py:958
+#: aleksis/core/models.py:989
 msgid "Icon URL"
 msgstr ""
 
-#: aleksis/core/models.py:964
+#: aleksis/core/models.py:995
 msgid "External link widget"
 msgstr ""
 
-#: aleksis/core/models.py:965
+#: aleksis/core/models.py:996
 msgid "External link widgets"
 msgstr ""
 
-#: aleksis/core/models.py:970
+#: aleksis/core/models.py:1002
+msgid "Content"
+msgstr ""
+
+#: aleksis/core/models.py:1008
+msgid "Static content widget"
+msgstr ""
+
+#: aleksis/core/models.py:1009
+msgid "Static content widgets"
+msgstr ""
+
+#: aleksis/core/models.py:1014
 msgid "Dashboard widget"
 msgstr ""
 
-#: aleksis/core/models.py:975
+#: aleksis/core/models.py:1019
 msgid "Order"
 msgstr ""
 
-#: aleksis/core/models.py:976
+#: aleksis/core/models.py:1020
 msgid "Part of the default dashboard"
 msgstr ""
 
-#: aleksis/core/models.py:991
+#: aleksis/core/models.py:1035
 msgid "Dashboard widget order"
 msgstr ""
 
-#: aleksis/core/models.py:992
+#: aleksis/core/models.py:1036
 msgid "Dashboard widget orders"
 msgstr ""
 
-#: aleksis/core/models.py:998
+#: aleksis/core/models.py:1042
 msgid "Menu ID"
 msgstr ""
 
-#: aleksis/core/models.py:1011
+#: aleksis/core/models.py:1055
 msgid "Custom menu"
 msgstr ""
 
-#: aleksis/core/models.py:1012
+#: aleksis/core/models.py:1056
 msgid "Custom menus"
 msgstr ""
 
-#: aleksis/core/models.py:1022
+#: aleksis/core/models.py:1066
 msgid "Menu"
 msgstr ""
 
-#: aleksis/core/models.py:1026 aleksis/core/models.py:1274
+#: aleksis/core/models.py:1070 aleksis/core/models.py:1318
 #: aleksis/core/templates/oauth2_provider/application/detail.html:26
 msgid "Icon"
 msgstr ""
 
-#: aleksis/core/models.py:1032
+#: aleksis/core/models.py:1076
 msgid "Custom menu item"
 msgstr ""
 
-#: aleksis/core/models.py:1033
+#: aleksis/core/models.py:1077
 msgid "Custom menu items"
 msgstr ""
 
-#: aleksis/core/models.py:1047
+#: aleksis/core/models.py:1091
 msgid "Title of type"
 msgstr ""
 
-#: aleksis/core/models.py:1054 aleksis/core/templates/core/group/full.html:47
+#: aleksis/core/models.py:1098 aleksis/core/templates/core/group/full.html:47
 #, fuzzy
 #| msgid "Group"
 msgid "Group type"
 msgstr "Groupe"
 
-#: aleksis/core/models.py:1068
+#: aleksis/core/models.py:1112
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can view system status"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:1069
+#: aleksis/core/models.py:1113
 msgid "Can manage data"
 msgstr ""
 
-#: aleksis/core/models.py:1070
+#: aleksis/core/models.py:1114
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can impersonate"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:1071
+#: aleksis/core/models.py:1115
 msgid "Can use search"
 msgstr ""
 
-#: aleksis/core/models.py:1072
+#: aleksis/core/models.py:1116
 msgid "Can change site preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1073
+#: aleksis/core/models.py:1117
 msgid "Can change person preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1074
+#: aleksis/core/models.py:1118
 msgid "Can change group preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1075
+#: aleksis/core/models.py:1119
 msgid "Can test PDF generation"
 msgstr ""
 
-#: aleksis/core/models.py:1076
+#: aleksis/core/models.py:1120
 #, fuzzy
 #| msgid "Contact details"
 msgid "Can invite persons"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:1112
+#: aleksis/core/models.py:1156
 msgid "Related data check task"
 msgstr ""
 
-#: aleksis/core/models.py:1120
+#: aleksis/core/models.py:1164
 msgid "Issue solved"
 msgstr ""
 
-#: aleksis/core/models.py:1121
+#: aleksis/core/models.py:1165
 msgid "Notification sent"
 msgstr ""
 
-#: aleksis/core/models.py:1134
+#: aleksis/core/models.py:1178
 msgid "Data check result"
 msgstr ""
 
-#: aleksis/core/models.py:1135
+#: aleksis/core/models.py:1179
 msgid "Data check results"
 msgstr ""
 
-#: aleksis/core/models.py:1137
+#: aleksis/core/models.py:1181
 msgid "Can run data checks"
 msgstr ""
 
-#: aleksis/core/models.py:1138
+#: aleksis/core/models.py:1182
 msgid "Can solve data check problems"
 msgstr ""
 
-#: aleksis/core/models.py:1145
+#: aleksis/core/models.py:1189
 #, fuzzy
 #| msgid "Contact details"
 msgid "E-Mail address"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:1177
+#: aleksis/core/models.py:1221
 #, fuzzy
 #| msgid "Owners"
 msgid "Owner"
 msgstr "Propriétaires"
 
-#: aleksis/core/models.py:1181
+#: aleksis/core/models.py:1225
 msgid "File expires at"
 msgstr ""
 
-#: aleksis/core/models.py:1183
+#: aleksis/core/models.py:1227
 msgid "Generated HTML file"
 msgstr ""
 
-#: aleksis/core/models.py:1185
+#: aleksis/core/models.py:1229
 msgid "Generated PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1192
+#: aleksis/core/models.py:1236
 msgid "PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1193
+#: aleksis/core/models.py:1237
 msgid "PDF files"
 msgstr ""
 
-#: aleksis/core/models.py:1198
+#: aleksis/core/models.py:1242
 msgid "Task result"
 msgstr ""
 
-#: aleksis/core/models.py:1201
+#: aleksis/core/models.py:1245
 msgid "Task user"
 msgstr ""
 
-#: aleksis/core/models.py:1213
+#: aleksis/core/models.py:1257
 msgid "Task user assignment"
 msgstr ""
 
-#: aleksis/core/models.py:1214
+#: aleksis/core/models.py:1258
 msgid "Task user assignments"
 msgstr ""
 
-#: aleksis/core/models.py:1230
+#: aleksis/core/models.py:1274
 #, fuzzy
 #| msgid "Contact details"
 msgid "Additional attributes"
 msgstr "Détails de contact"
 
-#: aleksis/core/models.py:1268
+#: aleksis/core/models.py:1312
 msgid "Allowed scopes that clients can request"
 msgstr ""
 
-#: aleksis/core/models.py:1278
+#: aleksis/core/models.py:1322
 msgid "This image will be shown as icon in the authorization flow. It should be squared."
 msgstr ""
 
@@ -1148,88 +1178,100 @@ msgid "Allow users to change their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:279
-msgid "Enable signup"
+msgid "Allow users to reset their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:287
-msgid "Enable invitations"
+msgid "Enable signup"
 msgstr ""
 
 #: aleksis/core/preferences.py:295
-msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgid "Regular expression for allowed usernames"
 msgstr ""
 
 #: aleksis/core/preferences.py:303
+msgid "Enable invitations"
+msgstr ""
+
+#: aleksis/core/preferences.py:311
+msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgstr ""
+
+#: aleksis/core/preferences.py:319
 msgid "Size of packets. (Default 5: abcde)"
 msgstr ""
 
-#: aleksis/core/preferences.py:314
+#: aleksis/core/preferences.py:330
 #, fuzzy
 #| msgid "Contact details"
 msgid "Allowed Grant Flows for OAuth applications"
 msgstr "Détails de contact"
 
-#: aleksis/core/preferences.py:328
+#: aleksis/core/preferences.py:344
 msgid "Available languages"
 msgstr ""
 
-#: aleksis/core/preferences.py:341
+#: aleksis/core/preferences.py:357
 msgid "Send emails if data checks detect problems"
 msgstr ""
 
-#: aleksis/core/preferences.py:352
+#: aleksis/core/preferences.py:368
 msgid "Email recipients for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:363
+#: aleksis/core/preferences.py:379
 msgid "Email recipient groups for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:372
+#: aleksis/core/preferences.py:388
 msgid "Show dashboard to users without login"
 msgstr ""
 
-#: aleksis/core/preferences.py:381
+#: aleksis/core/preferences.py:397
 msgid "Allow users to edit their dashboard"
 msgstr ""
 
-#: aleksis/core/preferences.py:392
+#: aleksis/core/preferences.py:408
 msgid "Fields on person model which are editable by themselves."
 msgstr ""
 
-#: aleksis/core/preferences.py:407
+#: aleksis/core/preferences.py:423
 msgid "Editable fields on person model which should trigger a notification on change"
 msgstr ""
 
-#: aleksis/core/preferences.py:421
+#: aleksis/core/preferences.py:437
 msgid "Contact for notification if a person changes their data"
 msgstr ""
 
-#: aleksis/core/preferences.py:432
+#: aleksis/core/preferences.py:448
+msgid "Prefer personal photos over avatars"
+msgstr ""
+
+#: aleksis/core/preferences.py:458
 msgid "PDF file expiration duration"
 msgstr ""
 
-#: aleksis/core/preferences.py:433
+#: aleksis/core/preferences.py:459
 msgid "in minutes"
 msgstr ""
 
-#: aleksis/core/preferences.py:443
+#: aleksis/core/preferences.py:469
 msgid "Automatically update the dashboard and its widgets"
 msgstr ""
 
-#: aleksis/core/preferences.py:453
+#: aleksis/core/preferences.py:479
 msgid "Automatically update the dashboard and its widgets sitewide"
 msgstr ""
 
-#: aleksis/core/preferences.py:463
+#: aleksis/core/preferences.py:489
 msgid "Country for phone number parsing"
 msgstr ""
 
-#: aleksis/core/settings.py:529
+#: aleksis/core/settings.py:540
 msgid "English"
 msgstr ""
 
-#: aleksis/core/settings.py:530
+#: aleksis/core/settings.py:541
 msgid "German"
 msgstr ""
 
@@ -1237,7 +1279,7 @@ msgstr ""
 #: aleksis/core/templates/core/announcement/list.html:36
 #: aleksis/core/templates/core/group/full.html:24
 #: aleksis/core/templates/core/person/full.html:26
-#: aleksis/core/templates/core/person/full.html:98
+#: aleksis/core/templates/core/person/full.html:86
 #: aleksis/core/templates/oauth2_provider/application/detail.html:17
 msgid "Edit"
 msgstr ""
@@ -1255,7 +1297,7 @@ msgstr ""
 #: aleksis/core/templates/core/group/full.html:31
 #: aleksis/core/templates/core/pages/delete.html:22
 #: aleksis/core/templates/core/person/full.html:33
-#: aleksis/core/templates/core/person/full.html:105
+#: aleksis/core/templates/core/person/full.html:93
 #: aleksis/core/templates/oauth2_provider/application/detail.html:21
 msgid "Delete"
 msgstr ""
@@ -1302,6 +1344,10 @@ msgid ""
 "          "
 msgstr ""
 
+#: aleksis/core/templates/500.html:21
+msgid "Retry"
+msgstr ""
+
 #: aleksis/core/templates/503.html:10
 msgid ""
 "The maintenance mode is currently enabled. Please try again\n"
@@ -1560,19 +1606,15 @@ msgstr ""
 msgid "There are no announcements."
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:78
-msgid "Logged in as"
-msgstr ""
-
-#: aleksis/core/templates/core/base.html:179
+#: aleksis/core/templates/core/base.html:213
 msgid "About AlekSIS® — The Free School Information System"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:187
+#: aleksis/core/templates/core/base.html:221
 msgid "Imprint"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:195
+#: aleksis/core/templates/core/base.html:229
 msgid "Privacy Policy"
 msgstr ""
 
@@ -1799,7 +1841,7 @@ msgstr ""
 
 #: aleksis/core/templates/core/group/full.html:38
 #: aleksis/core/templates/core/person/full.html:40
-#: aleksis/core/templates/core/person/full.html:112
+#: aleksis/core/templates/core/person/full.html:100
 msgid "Change preferences"
 msgstr ""
 
@@ -1993,83 +2035,83 @@ msgstr ""
 msgid "System checks"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:21
+#: aleksis/core/templates/core/pages/system_status.html:22
 msgid "Maintenance mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:23
+#: aleksis/core/templates/core/pages/system_status.html:24
 msgid ""
 "\n"
 "                Only admin and visitors from internal IPs can access thesite.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:34
+#: aleksis/core/templates/core/pages/system_status.html:36
 msgid "Maintenance mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:35
+#: aleksis/core/templates/core/pages/system_status.html:37
 msgid "Everyone can access the site."
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:45
+#: aleksis/core/templates/core/pages/system_status.html:47
 msgid "Debug mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:47
+#: aleksis/core/templates/core/pages/system_status.html:49
 msgid ""
 "\n"
 "                The web server throws back debug information on errors. Do not use in production!\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:54
+#: aleksis/core/templates/core/pages/system_status.html:56
 msgid "Debug mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:56
+#: aleksis/core/templates/core/pages/system_status.html:58
 msgid ""
 "\n"
 "                Debug mode is disabled. Default error pages are displayed on errors.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:69
+#: aleksis/core/templates/core/pages/system_status.html:71
 msgid "System health checks"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:75
+#: aleksis/core/templates/core/pages/system_status.html:77
 msgid "Service"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:76
-#: aleksis/core/templates/core/pages/system_status.html:115
+#: aleksis/core/templates/core/pages/system_status.html:78
+#: aleksis/core/templates/core/pages/system_status.html:119
 msgid "Status"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:77
+#: aleksis/core/templates/core/pages/system_status.html:79
 msgid "Time taken"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:96
+#: aleksis/core/templates/core/pages/system_status.html:100
 msgid "seconds"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:107
+#: aleksis/core/templates/core/pages/system_status.html:111
 msgid "Celery task results"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:112
+#: aleksis/core/templates/core/pages/system_status.html:116
 #: aleksis/core/templates/templated_email/celery_failure.email:9
 #: aleksis/core/templates/templated_email/celery_failure.email:28
 msgid "Task"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:113
+#: aleksis/core/templates/core/pages/system_status.html:117
 msgid "ID"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:114
+#: aleksis/core/templates/core/pages/system_status.html:118
 #, fuzzy
 #| msgid "Date"
 msgid "Date done"
@@ -2112,6 +2154,18 @@ msgid ""
 "            "
 msgstr ""
 
+#: aleksis/core/templates/core/partials/avatar_content.html:14
+#: aleksis/core/templates/core/partials/avatar_content.html:15
+#: aleksis/core/templates/core/person/full.html:213
+#: aleksis/core/templates/core/person/full.html:214
+msgid "Avatar"
+msgstr ""
+
+#: aleksis/core/templates/core/partials/avatar_content.html:19
+#: aleksis/core/templates/core/partials/avatar_content.html:20
+msgid "Identicon"
+msgstr ""
+
 #: aleksis/core/templates/core/partials/crud_events.html:15
 msgid "Changed by"
 msgstr ""
@@ -2206,26 +2260,26 @@ msgid "Edit person"
 msgstr ""
 
 #: aleksis/core/templates/core/person/full.html:47
-#: aleksis/core/templates/core/person/full.html:119
+#: aleksis/core/templates/core/person/full.html:107
 #, fuzzy
 #| msgid "Contact details"
 msgid "Impersonate"
 msgstr "Détails de contact"
 
 #: aleksis/core/templates/core/person/full.html:54
-#: aleksis/core/templates/core/person/full.html:126
+#: aleksis/core/templates/core/person/full.html:114
 msgid "Invite user"
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:133
+#: aleksis/core/templates/core/person/full.html:121
 msgid "Contact details"
 msgstr "Détails de contact"
 
-#: aleksis/core/templates/core/person/full.html:224
+#: aleksis/core/templates/core/person/full.html:220
 msgid "This person didn't upload a personal photo."
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:232
+#: aleksis/core/templates/core/person/full.html:228
 msgid "Children"
 msgstr ""
 
@@ -2441,13 +2495,11 @@ msgstr "Détails de contact"
 msgid "Network error"
 msgstr ""
 
-#: aleksis/core/templates/offline.html:8
-msgid ""
-"No internet\n"
-"    connection."
+#: aleksis/core/templates/offline.html:10
+msgid "No internet connection."
 msgstr ""
 
-#: aleksis/core/templates/offline.html:12
+#: aleksis/core/templates/offline.html:14
 msgid ""
 "\n"
 "      There was an error accessing this page. You probably don't have an internet connection. Check to see if your WiFi\n"
@@ -2697,7 +2749,7 @@ msgstr ""
 #: aleksis/core/templates/two_factor/_base_focus.html:6
 #: aleksis/core/templates/two_factor/core/otp_required.html:22
 #: aleksis/core/templates/two_factor/core/setup.html:5
-#: aleksis/core/templates/two_factor/profile/profile.html:87
+#: aleksis/core/templates/two_factor/profile/profile.html:88
 msgid "Enable Two-Factor Authentication"
 msgstr ""
 
@@ -2802,15 +2854,15 @@ msgstr ""
 msgid "Or, alternatively, use one of your backup phones:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:121
+#: aleksis/core/templates/two_factor/core/login.html:122
 msgid "As a last resort, you can use a backup token:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:124
+#: aleksis/core/templates/two_factor/core/login.html:125
 msgid "Use Backup Token"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:135
+#: aleksis/core/templates/two_factor/core/login.html:136
 msgid "Use alternative login options"
 msgstr ""
 
@@ -3047,11 +3099,11 @@ msgid ""
 "      "
 msgstr ""
 
-#: aleksis/core/util/notifications.py:63
+#: aleksis/core/util/notifications.py:64
 msgid "E-Mail"
 msgstr ""
 
-#: aleksis/core/util/notifications.py:64
+#: aleksis/core/util/notifications.py:65
 msgid "SMS"
 msgstr ""
 
@@ -3075,157 +3127,157 @@ msgstr ""
 msgid "Download PDF"
 msgstr ""
 
-#: aleksis/core/views.py:285
+#: aleksis/core/views.py:289
 msgid "The school term has been created."
 msgstr ""
 
-#: aleksis/core/views.py:297
+#: aleksis/core/views.py:301
 msgid "The school term has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:417
+#: aleksis/core/views.py:421
 msgid "The child groups were successfully saved."
 msgstr ""
 
-#: aleksis/core/views.py:436 aleksis/core/views.py:446
+#: aleksis/core/views.py:440 aleksis/core/views.py:450
 msgid "The person has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:496
+#: aleksis/core/views.py:500
 msgid "The group has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:593
+#: aleksis/core/views.py:597
 msgid "The announcement has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:609
+#: aleksis/core/views.py:613
 msgid "The announcement has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:677
+#: aleksis/core/views.py:681
 msgid "The requested preference registry does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:696
+#: aleksis/core/views.py:700
 msgid "The preferences have been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:720
+#: aleksis/core/views.py:724
 msgid "The person has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:734
+#: aleksis/core/views.py:738
 msgid "The group has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:766
+#: aleksis/core/views.py:770
 msgid "The additional_field has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:800
+#: aleksis/core/views.py:804
 msgid "The additional field has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:825
+#: aleksis/core/views.py:829
 msgid "The group type has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:855
+#: aleksis/core/views.py:859
 msgid "The group type has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:888
+#: aleksis/core/views.py:892
 msgid "Progress: Run data checks"
 msgstr ""
 
-#: aleksis/core/views.py:889
+#: aleksis/core/views.py:893
 msgid "Run data checks …"
 msgstr ""
 
-#: aleksis/core/views.py:890
+#: aleksis/core/views.py:894
 msgid "The data checks were run successfully."
 msgstr ""
 
-#: aleksis/core/views.py:891
+#: aleksis/core/views.py:895
 msgid "There was a problem while running data checks."
 msgstr ""
 
-#: aleksis/core/views.py:907
+#: aleksis/core/views.py:911
 #, python-brace-format
 msgid "The solve option '{solve_option_obj.verbose_name}' "
 msgstr ""
 
-#: aleksis/core/views.py:917
+#: aleksis/core/views.py:921
 msgid "The requested solve option does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:949
+#: aleksis/core/views.py:953
 msgid "The dashboard widget has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:979
+#: aleksis/core/views.py:983
 msgid "The dashboard widget has been created."
 msgstr ""
 
-#: aleksis/core/views.py:989
+#: aleksis/core/views.py:993
 msgid "The dashboard widget has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1060
+#: aleksis/core/views.py:1064
 msgid "Your dashboard configuration has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1062
+#: aleksis/core/views.py:1066
 msgid "The configuration of the default dashboard has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1138
+#: aleksis/core/views.py:1142
 #, python-brace-format
 msgid "The invitation was successfully created. The invitation code is {code}"
 msgstr ""
 
-#: aleksis/core/views.py:1229
+#: aleksis/core/views.py:1233
 msgid "We have successfully assigned the permissions."
 msgstr ""
 
-#: aleksis/core/views.py:1239
+#: aleksis/core/views.py:1243
 msgid "The global user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1249
+#: aleksis/core/views.py:1253
 msgid "The global group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1259
+#: aleksis/core/views.py:1263
 msgid "The object user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1269
+#: aleksis/core/views.py:1273
 msgid "The object group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1337
+#: aleksis/core/views.py:1341
 msgid "The requested PDF file does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:1346 aleksis/core/views.py:1350
+#: aleksis/core/views.py:1350 aleksis/core/views.py:1354
 msgid "The requested task does not exist or is not accessible"
 msgstr ""
 
-#: aleksis/core/views.py:1388
+#: aleksis/core/views.py:1406
 msgid "The third-party account could not be disconnected because it is the only login method available."
 msgstr ""
 
-#: aleksis/core/views.py:1395
+#: aleksis/core/views.py:1413
 msgid "The third-party account has been successfully disconnected."
 msgstr ""
 
-#: aleksis/core/views.py:1466
+#: aleksis/core/views.py:1484
 msgid "Person was invited successfully and an email with further instructions has been send to them."
 msgstr ""
 
-#: aleksis/core/views.py:1477
+#: aleksis/core/views.py:1495
 #, fuzzy
 #| msgid "This username is already in use."
 msgid "Person was already invited."
diff --git a/aleksis/core/locale/fr/LC_MESSAGES/djangojs.po b/aleksis/core/locale/fr/LC_MESSAGES/djangojs.po
index 9f07be2903046e41680c481bea659aa95b695df7..0f31d087d69a8fca9b3573dfa6d977c8459eaf99 100644
--- a/aleksis/core/locale/fr/LC_MESSAGES/djangojs.po
+++ b/aleksis/core/locale/fr/LC_MESSAGES/djangojs.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:20+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -30,6 +30,6 @@ msgstr ""
 msgid "OK"
 msgstr ""
 
-#: aleksis/core/static/js/main.js:191
+#: aleksis/core/static/js/main.js:195
 msgid "This page may contain outdated information since there is no internet connection."
 msgstr ""
diff --git a/aleksis/core/locale/la/LC_MESSAGES/django.po b/aleksis/core/locale/la/LC_MESSAGES/django.po
index 4b2afb73bbbc2fd8a426789859152fa2d1895395..fd7a3aa487f88448c539f5347f5700188a72d270 100644
--- a/aleksis/core/locale/la/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/la/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:19+0100\n"
 "PO-Revision-Date: 2020-12-19 12:57+0000\n"
 "Last-Translator: Julian <leuckerj@gmail.com>\n"
 "Language-Team: Latin <https://translate.edugit.org/projects/aleksis/aleksis/la/>\n"
@@ -44,10 +44,10 @@ msgstr "Inscriptio electronica"
 msgid "Home and mobile phone"
 msgstr "Numerus telephoni mobilis"
 
-#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:265
-#: aleksis/core/models.py:462 aleksis/core/templates/core/group/list.html:8
+#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:185
+#: aleksis/core/models.py:487 aleksis/core/templates/core/group/list.html:8
 #: aleksis/core/templates/core/group/list.html:9
-#: aleksis/core/templates/core/person/full.html:250
+#: aleksis/core/templates/core/person/full.html:246
 msgid "Groups"
 msgstr "Greges"
 
@@ -74,8 +74,8 @@ msgstr ""
 msgid "The DashboardWidget was reported broken automatically."
 msgstr ""
 
-#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:105
-#: aleksis/core/templates/core/base.html:106
+#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:139
+#: aleksis/core/templates/core/base.html:140
 #: aleksis/core/templates/core/group/list.html:20
 #: aleksis/core/templates/core/person/list.html:24
 #: aleksis/core/templates/search/search.html:7
@@ -101,11 +101,11 @@ msgstr ""
 msgid "Content type"
 msgstr ""
 
-#: aleksis/core/filters.py:113 aleksis/core/models.py:688
+#: aleksis/core/filters.py:113 aleksis/core/models.py:713
 msgid "User"
 msgstr ""
 
-#: aleksis/core/filters.py:135 aleksis/core/models.py:461
+#: aleksis/core/filters.py:135 aleksis/core/models.py:486
 msgid "Group"
 msgstr "Grex"
 
@@ -145,7 +145,7 @@ msgstr ""
 msgid "This username is already in use."
 msgstr ""
 
-#: aleksis/core/forms.py:153 aleksis/core/models.py:130
+#: aleksis/core/forms.py:153 aleksis/core/models.py:134
 msgid "School term"
 msgstr "Anus scolae"
 
@@ -156,7 +156,7 @@ msgid "Common data"
 msgstr "Adminstratio datarum"
 
 #: aleksis/core/forms.py:155 aleksis/core/forms.py:207
-#: aleksis/core/menus.py:254 aleksis/core/models.py:153
+#: aleksis/core/menus.py:174 aleksis/core/models.py:157
 #: aleksis/core/templates/core/person/list.html:8
 #: aleksis/core/templates/core/person/list.html:9
 msgid "Persons"
@@ -168,18 +168,18 @@ msgstr "personae"
 msgid "Additional data"
 msgstr "addita nomines"
 
-#: aleksis/core/forms.py:157 aleksis/core/models.py:206
-#: aleksis/core/models.py:514
+#: aleksis/core/forms.py:157 aleksis/core/models.py:210
+#: aleksis/core/models.py:539
 msgid "Photo"
 msgstr "Photographia"
 
 #: aleksis/core/forms.py:199 aleksis/core/forms.py:202
-#: aleksis/core/models.py:73
+#: aleksis/core/models.py:77
 msgid "Date"
 msgstr "dies"
 
 #: aleksis/core/forms.py:200 aleksis/core/forms.py:203
-#: aleksis/core/models.py:81
+#: aleksis/core/models.py:85
 msgid "Time"
 msgstr "tempus"
 
@@ -215,11 +215,11 @@ msgstr ""
 msgid "Please enter your invitation code."
 msgstr ""
 
-#: aleksis/core/forms.py:418 aleksis/core/models.py:181
+#: aleksis/core/forms.py:418 aleksis/core/models.py:185
 msgid "First name"
 msgstr "Primus nomen"
 
-#: aleksis/core/forms.py:419 aleksis/core/models.py:182
+#: aleksis/core/forms.py:419 aleksis/core/models.py:186
 msgid "Last name"
 msgstr "Secondus nomen"
 
@@ -275,7 +275,15 @@ msgstr ""
 msgid "Password (again)"
 msgstr ""
 
-#: aleksis/core/forms.py:775
+#: aleksis/core/forms.py:728
+msgid "The selected action does not exist."
+msgstr ""
+
+#: aleksis/core/forms.py:739
+msgid "You do not have permission to run {} on all selected objects."
+msgstr ""
+
+#: aleksis/core/forms.py:795
 msgid "No valid selection."
 msgstr ""
 
@@ -322,81 +330,30 @@ msgstr "Muta informationes scolae"
 msgid "Dashboard"
 msgstr "Forum"
 
-#: aleksis/core/menus.py:41 aleksis/core/models.py:734
-#: aleksis/core/preferences.py:29
+#: aleksis/core/menus.py:41 aleksis/core/models.py:765
+#: aleksis/core/preferences.py:29 aleksis/core/templates/core/base.html:81
 #: aleksis/core/templates/core/notifications.html:4
 #: aleksis/core/templates/core/notifications.html:5
 msgid "Notifications"
 msgstr "Nuntii"
 
 #: aleksis/core/menus.py:53
-msgid "Account"
-msgstr ""
-
-#: aleksis/core/menus.py:60
-msgid "Stop impersonation"
-msgstr "Simulandum aliquem finire"
-
-#: aleksis/core/menus.py:69 aleksis/core/templates/core/base.html:80
-msgid "Logout"
-msgstr "nomen retractare"
-
-#: aleksis/core/menus.py:75
-msgid "2FA"
-msgstr ""
-
-#: aleksis/core/menus.py:83
-#: aleksis/core/templates/account/password_change.html:5
-#: aleksis/core/templates/account/password_change.html:6
-#: aleksis/core/templates/account/password_change.html:19
-#: aleksis/core/templates/account/password_reset_from_key.html:5
-#: aleksis/core/templates/account/password_reset_from_key.html:42
-#: aleksis/core/templates/account/password_reset_from_key.html:46
-#: aleksis/core/templates/account/password_reset_from_key_done.html:5
-#: aleksis/core/templates/account/password_reset_from_key_done.html:6
-msgid "Change password"
-msgstr ""
-
-#: aleksis/core/menus.py:95
-msgid "Me"
-msgstr ""
-
-#: aleksis/core/menus.py:104
-#: aleksis/core/templates/dynamic_preferences/form.html:5
-msgid "Preferences"
-msgstr ""
-
-#: aleksis/core/menus.py:113
-#, fuzzy
-#| msgid "Persons and accounts"
-msgid "Third-party accounts"
-msgstr "Personae et computi"
-
-#: aleksis/core/menus.py:122
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
-#, fuzzy
-#| msgid "Notifications"
-msgid "Authorized applications"
-msgstr "Nuntii"
-
-#: aleksis/core/menus.py:133
 msgid "Admin"
 msgstr "Administratio"
 
-#: aleksis/core/menus.py:141 aleksis/core/models.py:834
+#: aleksis/core/menus.py:61 aleksis/core/models.py:865
 #: aleksis/core/templates/core/announcement/list.html:7
 #: aleksis/core/templates/core/announcement/list.html:8
 msgid "Announcements"
 msgstr "Nuntii"
 
-#: aleksis/core/menus.py:152 aleksis/core/models.py:131
+#: aleksis/core/menus.py:72 aleksis/core/models.py:135
 #: aleksis/core/templates/core/school_term/list.html:8
 #: aleksis/core/templates/core/school_term/list.html:9
 msgid "School terms"
 msgstr "ani scolae"
 
-#: aleksis/core/menus.py:163
+#: aleksis/core/menus.py:83
 #: aleksis/core/templates/core/dashboard_widget/list.html:8
 #: aleksis/core/templates/core/dashboard_widget/list.html:9
 #, fuzzy
@@ -404,41 +361,41 @@ msgstr "ani scolae"
 msgid "Dashboard widgets"
 msgstr "Forum"
 
-#: aleksis/core/menus.py:174
+#: aleksis/core/menus.py:94
 #: aleksis/core/templates/core/management/data_management.html:6
 #: aleksis/core/templates/core/management/data_management.html:7
 msgid "Data management"
 msgstr "Adminstratio datarum"
 
-#: aleksis/core/menus.py:185
+#: aleksis/core/menus.py:105
 #: aleksis/core/templates/core/pages/system_status.html:5
 #: aleksis/core/templates/core/pages/system_status.html:7
 msgid "System status"
 msgstr "Status systemae"
 
-#: aleksis/core/menus.py:196
+#: aleksis/core/menus.py:116
 #, fuzzy
 #| msgid "Notification"
 msgid "Configuration"
 msgstr "Nuntius"
 
-#: aleksis/core/menus.py:207 aleksis/core/templates/core/data_check/list.html:9
+#: aleksis/core/menus.py:127 aleksis/core/templates/core/data_check/list.html:9
 #: aleksis/core/templates/core/data_check/list.html:10
 #, fuzzy
 #| msgid "System status"
 msgid "Data checks"
 msgstr "Status systemae"
 
-#: aleksis/core/menus.py:213 aleksis/core/templates/core/perms/list.html:13
+#: aleksis/core/menus.py:133 aleksis/core/templates/core/perms/list.html:13
 #: aleksis/core/templates/core/perms/list.html:14
 msgid "Manage permissions"
 msgstr ""
 
-#: aleksis/core/menus.py:224
+#: aleksis/core/menus.py:144
 msgid "Backend Admin"
 msgstr ""
 
-#: aleksis/core/menus.py:232
+#: aleksis/core/menus.py:152
 #: aleksis/core/templates/oauth2_provider/application/list.html:5
 #: aleksis/core/templates/oauth2_provider/application/list.html:6
 #, fuzzy
@@ -446,11 +403,11 @@ msgstr ""
 msgid "OAuth2 Applications"
 msgstr "Nuntii"
 
-#: aleksis/core/menus.py:245
+#: aleksis/core/menus.py:165
 msgid "People"
 msgstr "Personae"
 
-#: aleksis/core/menus.py:276 aleksis/core/models.py:1055
+#: aleksis/core/menus.py:196 aleksis/core/models.py:1099
 #: aleksis/core/templates/core/group_type/list.html:8
 #: aleksis/core/templates/core/group_type/list.html:9
 #, fuzzy
@@ -458,11 +415,11 @@ msgstr "Personae"
 msgid "Group types"
 msgstr "Greges"
 
-#: aleksis/core/menus.py:287
+#: aleksis/core/menus.py:207
 msgid "Groups and child groups"
 msgstr ""
 
-#: aleksis/core/menus.py:298 aleksis/core/models.py:510
+#: aleksis/core/menus.py:218 aleksis/core/models.py:535
 #: aleksis/core/templates/core/additional_field/list.html:8
 #: aleksis/core/templates/core/additional_field/list.html:9
 #, fuzzy
@@ -470,616 +427,691 @@ msgstr ""
 msgid "Additional fields"
 msgstr "addita nomines"
 
-#: aleksis/core/menus.py:309
+#: aleksis/core/menus.py:229
 #, fuzzy
 #| msgid "Stop impersonation"
 msgid "Invite person"
 msgstr "Simulandum aliquem finire"
 
-#: aleksis/core/menus.py:322
+#: aleksis/core/menus.py:242
 #: aleksis/core/templates/core/group/child_groups.html:7
 #: aleksis/core/templates/core/group/child_groups.html:9
 msgid "Assign child groups to groups"
 msgstr ""
 
+#: aleksis/core/menus.py:254
+msgid "Stop impersonation"
+msgstr "Simulandum aliquem finire"
+
+#: aleksis/core/menus.py:263
+msgid "Account"
+msgstr ""
+
+#: aleksis/core/menus.py:272
+#: aleksis/core/templates/dynamic_preferences/form.html:5
+msgid "Preferences"
+msgstr ""
+
+#: aleksis/core/menus.py:281
+msgid "2FA"
+msgstr ""
+
+#: aleksis/core/menus.py:289
+#: aleksis/core/templates/account/password_change.html:5
+#: aleksis/core/templates/account/password_change.html:6
+#: aleksis/core/templates/account/password_change.html:19
+#: aleksis/core/templates/account/password_reset_from_key.html:5
+#: aleksis/core/templates/account/password_reset_from_key.html:42
+#: aleksis/core/templates/account/password_reset_from_key.html:46
+#: aleksis/core/templates/account/password_reset_from_key_done.html:5
+#: aleksis/core/templates/account/password_reset_from_key_done.html:6
+msgid "Change password"
+msgstr ""
+
+#: aleksis/core/menus.py:301
+#, fuzzy
+#| msgid "Persons and accounts"
+msgid "Third-party accounts"
+msgstr "Personae et computi"
+
+#: aleksis/core/menus.py:310
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
+#, fuzzy
+#| msgid "Notifications"
+msgid "Authorized applications"
+msgstr "Nuntii"
+
+#: aleksis/core/menus.py:320
+msgid "Logout"
+msgstr "nomen retractare"
+
 #: aleksis/core/mixins.py:511
 #, fuzzy
 #| msgid "Edit school term"
 msgid "Linked school term"
 msgstr "Muta anum scolae"
 
-#: aleksis/core/models.py:71
+#: aleksis/core/models.py:75
 msgid "Boolean (Yes/No)"
 msgstr ""
 
-#: aleksis/core/models.py:72
+#: aleksis/core/models.py:76
 msgid "Text (one line)"
 msgstr ""
 
-#: aleksis/core/models.py:74
+#: aleksis/core/models.py:78
 msgid "Date and time"
 msgstr "Dies et hora"
 
-#: aleksis/core/models.py:75
+#: aleksis/core/models.py:79
 msgid "Decimal number"
 msgstr ""
 
-#: aleksis/core/models.py:76 aleksis/core/models.py:199
+#: aleksis/core/models.py:80 aleksis/core/models.py:203
 msgid "E-mail address"
 msgstr "Inscriptio electronica"
 
-#: aleksis/core/models.py:77
+#: aleksis/core/models.py:81
 msgid "Integer"
 msgstr ""
 
-#: aleksis/core/models.py:78
+#: aleksis/core/models.py:82
 #, fuzzy
 #| msgid "E-mail address"
 msgid "IP address"
 msgstr "Inscriptio electronica"
 
-#: aleksis/core/models.py:79
+#: aleksis/core/models.py:83
 msgid "Boolean or empty (Yes/No/Neither)"
 msgstr ""
 
-#: aleksis/core/models.py:80
+#: aleksis/core/models.py:84
 msgid "Text (multi-line)"
 msgstr ""
 
-#: aleksis/core/models.py:82
+#: aleksis/core/models.py:86
 msgid "URL / Link"
 msgstr ""
 
-#: aleksis/core/models.py:94 aleksis/core/models.py:1024
+#: aleksis/core/models.py:98 aleksis/core/models.py:1068
 msgid "Name"
 msgstr "Nomen"
 
-#: aleksis/core/models.py:96
+#: aleksis/core/models.py:100
 msgid "Start date"
 msgstr ""
 
-#: aleksis/core/models.py:97
+#: aleksis/core/models.py:101
 msgid "End date"
 msgstr ""
 
-#: aleksis/core/models.py:116
+#: aleksis/core/models.py:120
 msgid "The start date must be earlier than the end date."
 msgstr ""
 
-#: aleksis/core/models.py:123
+#: aleksis/core/models.py:127
 msgid "There is already a school term for this time or a part of this time."
 msgstr ""
 
-#: aleksis/core/models.py:152 aleksis/core/models.py:973
+#: aleksis/core/models.py:156 aleksis/core/models.py:1017
 msgid "Person"
 msgstr "Persona"
 
-#: aleksis/core/models.py:155
+#: aleksis/core/models.py:159
 #, fuzzy
 #| msgid "E-mail address"
 msgid "Can view address"
 msgstr "Inscriptio electronica"
 
-#: aleksis/core/models.py:156
+#: aleksis/core/models.py:160
 #, fuzzy
 #| msgid "E-mail address"
 msgid "Can view contact details"
 msgstr "Inscriptio electronica"
 
-#: aleksis/core/models.py:157
+#: aleksis/core/models.py:161
 #, fuzzy
 #| msgid "E-mail address"
 msgid "Can view photo"
 msgstr "Inscriptio electronica"
 
-#: aleksis/core/models.py:158
+#: aleksis/core/models.py:162
 #, fuzzy
 #| msgid "E-mail address"
 msgid "Can view avatar image"
 msgstr "Inscriptio electronica"
 
-#: aleksis/core/models.py:159
+#: aleksis/core/models.py:163
 #, fuzzy
 #| msgid "Persons and accounts"
 msgid "Can view persons groups"
 msgstr "Personae et computi"
 
-#: aleksis/core/models.py:160
+#: aleksis/core/models.py:164
 #, fuzzy
 #| msgid "Stop impersonation"
 msgid "Can view personal details"
 msgstr "Simulandum aliquem finire"
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "female"
 msgstr "femininum"
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "male"
 msgstr "maskulinum"
 
-#: aleksis/core/models.py:178 aleksis/core/models.py:1227
+#: aleksis/core/models.py:182 aleksis/core/models.py:1271
 msgid "Linked user"
 msgstr ""
 
-#: aleksis/core/models.py:184
+#: aleksis/core/models.py:188
 msgid "Additional name(s)"
 msgstr "addita nomines"
 
-#: aleksis/core/models.py:188 aleksis/core/models.py:479
+#: aleksis/core/models.py:192 aleksis/core/models.py:504
 msgid "Short name"
 msgstr "Breve nomen"
 
-#: aleksis/core/models.py:191
+#: aleksis/core/models.py:195
 msgid "Street"
 msgstr "Via"
 
-#: aleksis/core/models.py:192
+#: aleksis/core/models.py:196
 msgid "Street number"
 msgstr "Numerus domini"
 
-#: aleksis/core/models.py:193
+#: aleksis/core/models.py:197
 msgid "Postal code"
 msgstr "Numerus directorius"
 
-#: aleksis/core/models.py:194
+#: aleksis/core/models.py:198
 msgid "Place"
 msgstr "Urbs"
 
-#: aleksis/core/models.py:196 aleksis/core/templates/core/person/full.html:172
+#: aleksis/core/models.py:200 aleksis/core/templates/core/person/full.html:160
 msgid "Home phone"
 msgstr "Numerus telephoni domi"
 
-#: aleksis/core/models.py:197 aleksis/core/templates/core/person/full.html:182
+#: aleksis/core/models.py:201 aleksis/core/templates/core/person/full.html:170
 msgid "Mobile phone"
 msgstr "Numerus telephoni mobilis"
 
-#: aleksis/core/models.py:201
+#: aleksis/core/models.py:205
 msgid "Date of birth"
 msgstr "Dies natalis"
 
-#: aleksis/core/models.py:202
+#: aleksis/core/models.py:206
 #, fuzzy
 #| msgid "Date of birth"
 msgid "Place of birth"
 msgstr "Dies natalis"
 
-#: aleksis/core/models.py:203
+#: aleksis/core/models.py:207
 msgid "Sex"
 msgstr "Genus"
 
-#: aleksis/core/models.py:210 aleksis/core/models.py:518
+#: aleksis/core/models.py:214 aleksis/core/models.py:543
 msgid "This is an official photo, used for official documents and for internal use cases."
 msgstr ""
 
-#: aleksis/core/models.py:215 aleksis/core/models.py:522
+#: aleksis/core/models.py:219 aleksis/core/models.py:547
 msgid "Display picture / Avatar"
 msgstr ""
 
-#: aleksis/core/models.py:218 aleksis/core/models.py:525
+#: aleksis/core/models.py:222 aleksis/core/models.py:550
 msgid "This is a picture or an avatar for public display."
 msgstr ""
 
-#: aleksis/core/models.py:223 aleksis/core/templates/core/person/full.html:239
+#: aleksis/core/models.py:227 aleksis/core/templates/core/person/full.html:235
 msgid "Guardians / Parents"
 msgstr "Parentes"
 
-#: aleksis/core/models.py:230
+#: aleksis/core/models.py:234
 msgid "Primary group"
 msgstr ""
 
-#: aleksis/core/models.py:233 aleksis/core/models.py:692
-#: aleksis/core/models.py:716 aleksis/core/models.py:801
-#: aleksis/core/models.py:1048
+#: aleksis/core/models.py:237 aleksis/core/models.py:717
+#: aleksis/core/models.py:741 aleksis/core/models.py:832
+#: aleksis/core/models.py:1092
 msgid "Description"
 msgstr "Descriptio"
 
-#: aleksis/core/models.py:434
+#: aleksis/core/models.py:457
 msgid "Title of field"
 msgstr ""
 
-#: aleksis/core/models.py:436
+#: aleksis/core/models.py:459
 msgid "Type of field"
 msgstr ""
 
-#: aleksis/core/models.py:443
+#: aleksis/core/models.py:461
+msgid "Required"
+msgstr ""
+
+#: aleksis/core/models.py:462
+#, fuzzy
+#| msgid "Site description"
+msgid "Help text / description"
+msgstr "Descriptio paginae"
+
+#: aleksis/core/models.py:468
 #, fuzzy
 #| msgid "Additional name(s)"
 msgid "Addtitional field for groups"
 msgstr "addita nomines"
 
-#: aleksis/core/models.py:444
+#: aleksis/core/models.py:469
 #, fuzzy
 #| msgid "Additional name(s)"
 msgid "Addtitional fields for groups"
 msgstr "addita nomines"
 
-#: aleksis/core/models.py:464
+#: aleksis/core/models.py:489
 msgid "Can assign child groups to groups"
 msgstr ""
 
-#: aleksis/core/models.py:465
+#: aleksis/core/models.py:490
 #, fuzzy
 #| msgid "Persons and accounts"
 msgid "Can view statistics about group."
 msgstr "Personae et computi"
 
-#: aleksis/core/models.py:477
+#: aleksis/core/models.py:502
 #, fuzzy
 #| msgid "Last name"
 msgid "Long name"
 msgstr "Secondus nomen"
 
-#: aleksis/core/models.py:487 aleksis/core/templates/core/group/full.html:85
+#: aleksis/core/models.py:512 aleksis/core/templates/core/group/full.html:85
 msgid "Members"
 msgstr ""
 
-#: aleksis/core/models.py:490 aleksis/core/templates/core/group/full.html:82
+#: aleksis/core/models.py:515 aleksis/core/templates/core/group/full.html:82
 msgid "Owners"
 msgstr ""
 
-#: aleksis/core/models.py:497 aleksis/core/templates/core/group/full.html:55
+#: aleksis/core/models.py:522 aleksis/core/templates/core/group/full.html:55
 msgid "Parent groups"
 msgstr ""
 
-#: aleksis/core/models.py:505
+#: aleksis/core/models.py:530
 msgid "Type of group"
 msgstr ""
 
-#: aleksis/core/models.py:691 aleksis/core/models.py:715
-#: aleksis/core/models.py:800
+#: aleksis/core/models.py:716 aleksis/core/models.py:740
+#: aleksis/core/models.py:831
 #: aleksis/core/templates/core/announcement/list.html:18
 msgid "Title"
 msgstr "Titulus"
 
-#: aleksis/core/models.py:694
+#: aleksis/core/models.py:719
 msgid "Application"
 msgstr ""
 
-#: aleksis/core/models.py:700
+#: aleksis/core/models.py:725
 msgid "Activity"
 msgstr ""
 
-#: aleksis/core/models.py:701
+#: aleksis/core/models.py:726
 msgid "Activities"
 msgstr ""
 
-#: aleksis/core/models.py:707
+#: aleksis/core/models.py:732
 msgid "Sender"
 msgstr "Mittens"
 
-#: aleksis/core/models.py:712
+#: aleksis/core/models.py:737
 msgid "Recipient"
 msgstr ""
 
-#: aleksis/core/models.py:717 aleksis/core/models.py:1025
+#: aleksis/core/models.py:742 aleksis/core/models.py:1069
 msgid "Link"
 msgstr ""
 
-#: aleksis/core/models.py:719
+#: aleksis/core/models.py:744
+#, fuzzy
+#| msgid "Notification"
+msgid "Send notification at"
+msgstr "Nuntius"
+
+#: aleksis/core/models.py:746
 msgid "Read"
 msgstr ""
 
-#: aleksis/core/models.py:720
+#: aleksis/core/models.py:747
 msgid "Sent"
 msgstr ""
 
-#: aleksis/core/models.py:733
+#: aleksis/core/models.py:764
 #, fuzzy
 #| msgid "Notifications"
 msgid "Notification"
 msgstr "Nuntii"
 
-#: aleksis/core/models.py:802
+#: aleksis/core/models.py:833
 msgid "Link to detailed view"
 msgstr ""
 
-#: aleksis/core/models.py:805
+#: aleksis/core/models.py:836
 msgid "Date and time from when to show"
 msgstr ""
 
-#: aleksis/core/models.py:808
+#: aleksis/core/models.py:839
 msgid "Date and time until when to show"
 msgstr ""
 
-#: aleksis/core/models.py:833
+#: aleksis/core/models.py:864
 #, fuzzy
 #| msgid "Announcements"
 msgid "Announcement"
 msgstr "Nuntii"
 
-#: aleksis/core/models.py:871
+#: aleksis/core/models.py:902
 #, fuzzy
 #| msgid "Announcements"
 msgid "Announcement recipient"
 msgstr "Nuntii"
 
-#: aleksis/core/models.py:872
+#: aleksis/core/models.py:903
 #, fuzzy
 #| msgid "Announcements"
 msgid "Announcement recipients"
 msgstr "Nuntii"
 
-#: aleksis/core/models.py:894
+#: aleksis/core/models.py:925
 #, fuzzy
 #| msgid "Site title"
 msgid "Widget Title"
 msgstr "Titulus paginae"
 
-#: aleksis/core/models.py:895
+#: aleksis/core/models.py:926
 msgid "Activate Widget"
 msgstr ""
 
-#: aleksis/core/models.py:896
+#: aleksis/core/models.py:927
 #, fuzzy
 #| msgid "Site title"
 msgid "Widget is broken"
 msgstr "Titulus paginae"
 
-#: aleksis/core/models.py:899
+#: aleksis/core/models.py:930
 msgid "Size on mobile devices"
 msgstr ""
 
-#: aleksis/core/models.py:900
+#: aleksis/core/models.py:931
 msgid "<= 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:905
+#: aleksis/core/models.py:936
 msgid "Size on tablet devices"
 msgstr ""
 
-#: aleksis/core/models.py:906
+#: aleksis/core/models.py:937
 msgid "> 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:911
+#: aleksis/core/models.py:942
 msgid "Size on desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:912
+#: aleksis/core/models.py:943
 msgid "> 992 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:917
+#: aleksis/core/models.py:948
 msgid "Size on large desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:918
+#: aleksis/core/models.py:949
 msgid "> 1200 px>, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:949
+#: aleksis/core/models.py:980
 #, fuzzy
 #| msgid "Dashboard"
 msgid "Can edit default dashboard"
 msgstr "Forum"
 
-#: aleksis/core/models.py:950
+#: aleksis/core/models.py:981
 #, fuzzy
 #| msgid "Dashboard"
 msgid "Dashboard Widget"
 msgstr "Forum"
 
-#: aleksis/core/models.py:951
+#: aleksis/core/models.py:982
 #, fuzzy
 #| msgid "Dashboard"
 msgid "Dashboard Widgets"
 msgstr "Forum"
 
-#: aleksis/core/models.py:957
+#: aleksis/core/models.py:988
 msgid "URL"
 msgstr ""
 
-#: aleksis/core/models.py:958
+#: aleksis/core/models.py:989
 #, fuzzy
 #| msgid "Icon"
 msgid "Icon URL"
 msgstr "Nota"
 
-#: aleksis/core/models.py:964
+#: aleksis/core/models.py:995
 msgid "External link widget"
 msgstr ""
 
-#: aleksis/core/models.py:965
+#: aleksis/core/models.py:996
 msgid "External link widgets"
 msgstr ""
 
-#: aleksis/core/models.py:970
+#: aleksis/core/models.py:1002
+msgid "Content"
+msgstr ""
+
+#: aleksis/core/models.py:1008
+msgid "Static content widget"
+msgstr ""
+
+#: aleksis/core/models.py:1009
+msgid "Static content widgets"
+msgstr ""
+
+#: aleksis/core/models.py:1014
 #, fuzzy
 #| msgid "Dashboard"
 msgid "Dashboard widget"
 msgstr "Forum"
 
-#: aleksis/core/models.py:975
+#: aleksis/core/models.py:1019
 msgid "Order"
 msgstr ""
 
-#: aleksis/core/models.py:976
+#: aleksis/core/models.py:1020
 msgid "Part of the default dashboard"
 msgstr ""
 
-#: aleksis/core/models.py:991
+#: aleksis/core/models.py:1035
 #, fuzzy
 #| msgid "Dashboard"
 msgid "Dashboard widget order"
 msgstr "Forum"
 
-#: aleksis/core/models.py:992
+#: aleksis/core/models.py:1036
 #, fuzzy
 #| msgid "Dashboard"
 msgid "Dashboard widget orders"
 msgstr "Forum"
 
-#: aleksis/core/models.py:998
+#: aleksis/core/models.py:1042
 msgid "Menu ID"
 msgstr ""
 
-#: aleksis/core/models.py:1011
+#: aleksis/core/models.py:1055
 msgid "Custom menu"
 msgstr ""
 
-#: aleksis/core/models.py:1012
+#: aleksis/core/models.py:1056
 msgid "Custom menus"
 msgstr ""
 
-#: aleksis/core/models.py:1022
+#: aleksis/core/models.py:1066
 msgid "Menu"
 msgstr ""
 
-#: aleksis/core/models.py:1026 aleksis/core/models.py:1274
+#: aleksis/core/models.py:1070 aleksis/core/models.py:1318
 #: aleksis/core/templates/oauth2_provider/application/detail.html:26
 msgid "Icon"
 msgstr "Nota"
 
-#: aleksis/core/models.py:1032
+#: aleksis/core/models.py:1076
 msgid "Custom menu item"
 msgstr ""
 
-#: aleksis/core/models.py:1033
+#: aleksis/core/models.py:1077
 msgid "Custom menu items"
 msgstr ""
 
-#: aleksis/core/models.py:1047
+#: aleksis/core/models.py:1091
 msgid "Title of type"
 msgstr ""
 
-#: aleksis/core/models.py:1054 aleksis/core/templates/core/group/full.html:47
+#: aleksis/core/models.py:1098 aleksis/core/templates/core/group/full.html:47
 #, fuzzy
 #| msgid "Group"
 msgid "Group type"
 msgstr "Grex"
 
-#: aleksis/core/models.py:1068
+#: aleksis/core/models.py:1112
 #, fuzzy
 #| msgid "System status"
 msgid "Can view system status"
 msgstr "Status systemae"
 
-#: aleksis/core/models.py:1069
+#: aleksis/core/models.py:1113
 #, fuzzy
 #| msgid "Data management"
 msgid "Can manage data"
 msgstr "Adminstratio datarum"
 
-#: aleksis/core/models.py:1070
+#: aleksis/core/models.py:1114
 #, fuzzy
 #| msgid "Stop impersonation"
 msgid "Can impersonate"
 msgstr "Simulandum aliquem finire"
 
-#: aleksis/core/models.py:1071
+#: aleksis/core/models.py:1115
 msgid "Can use search"
 msgstr ""
 
-#: aleksis/core/models.py:1072
+#: aleksis/core/models.py:1116
 msgid "Can change site preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1073
+#: aleksis/core/models.py:1117
 msgid "Can change person preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1074
+#: aleksis/core/models.py:1118
 msgid "Can change group preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1075
+#: aleksis/core/models.py:1119
 msgid "Can test PDF generation"
 msgstr ""
 
-#: aleksis/core/models.py:1076
+#: aleksis/core/models.py:1120
 #, fuzzy
 #| msgid "Stop impersonation"
 msgid "Can invite persons"
 msgstr "Simulandum aliquem finire"
 
-#: aleksis/core/models.py:1112
+#: aleksis/core/models.py:1156
 msgid "Related data check task"
 msgstr ""
 
-#: aleksis/core/models.py:1120
+#: aleksis/core/models.py:1164
 msgid "Issue solved"
 msgstr ""
 
-#: aleksis/core/models.py:1121
+#: aleksis/core/models.py:1165
 #, fuzzy
 #| msgid "Notifications"
 msgid "Notification sent"
 msgstr "Nuntii"
 
-#: aleksis/core/models.py:1134
+#: aleksis/core/models.py:1178
 msgid "Data check result"
 msgstr ""
 
-#: aleksis/core/models.py:1135
+#: aleksis/core/models.py:1179
 msgid "Data check results"
 msgstr ""
 
-#: aleksis/core/models.py:1137
+#: aleksis/core/models.py:1181
 msgid "Can run data checks"
 msgstr ""
 
-#: aleksis/core/models.py:1138
+#: aleksis/core/models.py:1182
 msgid "Can solve data check problems"
 msgstr ""
 
-#: aleksis/core/models.py:1145
+#: aleksis/core/models.py:1189
 #, fuzzy
 #| msgid "E-mail address"
 msgid "E-Mail address"
 msgstr "Inscriptio electronica"
 
-#: aleksis/core/models.py:1177
+#: aleksis/core/models.py:1221
 msgid "Owner"
 msgstr ""
 
-#: aleksis/core/models.py:1181
+#: aleksis/core/models.py:1225
 msgid "File expires at"
 msgstr ""
 
-#: aleksis/core/models.py:1183
+#: aleksis/core/models.py:1227
 msgid "Generated HTML file"
 msgstr ""
 
-#: aleksis/core/models.py:1185
+#: aleksis/core/models.py:1229
 msgid "Generated PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1192
+#: aleksis/core/models.py:1236
 msgid "PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1193
+#: aleksis/core/models.py:1237
 msgid "PDF files"
 msgstr ""
 
-#: aleksis/core/models.py:1198
+#: aleksis/core/models.py:1242
 msgid "Task result"
 msgstr ""
 
-#: aleksis/core/models.py:1201
+#: aleksis/core/models.py:1245
 msgid "Task user"
 msgstr ""
 
-#: aleksis/core/models.py:1213
+#: aleksis/core/models.py:1257
 msgid "Task user assignment"
 msgstr ""
 
-#: aleksis/core/models.py:1214
+#: aleksis/core/models.py:1258
 msgid "Task user assignments"
 msgstr ""
 
-#: aleksis/core/models.py:1230
+#: aleksis/core/models.py:1274
 #, fuzzy
 #| msgid "Additional name(s)"
 msgid "Additional attributes"
 msgstr "addita nomines"
 
-#: aleksis/core/models.py:1268
+#: aleksis/core/models.py:1312
 msgid "Allowed scopes that clients can request"
 msgstr ""
 
-#: aleksis/core/models.py:1278
+#: aleksis/core/models.py:1322
 msgid "This image will be shown as icon in the authorization flow. It should be squared."
 msgstr ""
 
@@ -1214,88 +1246,100 @@ msgid "Allow users to change their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:279
-msgid "Enable signup"
+msgid "Allow users to reset their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:287
-msgid "Enable invitations"
+msgid "Enable signup"
 msgstr ""
 
 #: aleksis/core/preferences.py:295
-msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgid "Regular expression for allowed usernames"
 msgstr ""
 
 #: aleksis/core/preferences.py:303
+msgid "Enable invitations"
+msgstr ""
+
+#: aleksis/core/preferences.py:311
+msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgstr ""
+
+#: aleksis/core/preferences.py:319
 msgid "Size of packets. (Default 5: abcde)"
 msgstr ""
 
-#: aleksis/core/preferences.py:314
+#: aleksis/core/preferences.py:330
 #, fuzzy
 #| msgid "E-mail address"
 msgid "Allowed Grant Flows for OAuth applications"
 msgstr "Inscriptio electronica"
 
-#: aleksis/core/preferences.py:328
+#: aleksis/core/preferences.py:344
 msgid "Available languages"
 msgstr ""
 
-#: aleksis/core/preferences.py:341
+#: aleksis/core/preferences.py:357
 msgid "Send emails if data checks detect problems"
 msgstr ""
 
-#: aleksis/core/preferences.py:352
+#: aleksis/core/preferences.py:368
 msgid "Email recipients for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:363
+#: aleksis/core/preferences.py:379
 msgid "Email recipient groups for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:372
+#: aleksis/core/preferences.py:388
 msgid "Show dashboard to users without login"
 msgstr ""
 
-#: aleksis/core/preferences.py:381
+#: aleksis/core/preferences.py:397
 msgid "Allow users to edit their dashboard"
 msgstr ""
 
-#: aleksis/core/preferences.py:392
+#: aleksis/core/preferences.py:408
 msgid "Fields on person model which are editable by themselves."
 msgstr ""
 
-#: aleksis/core/preferences.py:407
+#: aleksis/core/preferences.py:423
 msgid "Editable fields on person model which should trigger a notification on change"
 msgstr ""
 
-#: aleksis/core/preferences.py:421
+#: aleksis/core/preferences.py:437
 msgid "Contact for notification if a person changes their data"
 msgstr ""
 
-#: aleksis/core/preferences.py:432
+#: aleksis/core/preferences.py:448
+msgid "Prefer personal photos over avatars"
+msgstr ""
+
+#: aleksis/core/preferences.py:458
 msgid "PDF file expiration duration"
 msgstr ""
 
-#: aleksis/core/preferences.py:433
+#: aleksis/core/preferences.py:459
 msgid "in minutes"
 msgstr ""
 
-#: aleksis/core/preferences.py:443
+#: aleksis/core/preferences.py:469
 msgid "Automatically update the dashboard and its widgets"
 msgstr ""
 
-#: aleksis/core/preferences.py:453
+#: aleksis/core/preferences.py:479
 msgid "Automatically update the dashboard and its widgets sitewide"
 msgstr ""
 
-#: aleksis/core/preferences.py:463
+#: aleksis/core/preferences.py:489
 msgid "Country for phone number parsing"
 msgstr ""
 
-#: aleksis/core/settings.py:529
+#: aleksis/core/settings.py:540
 msgid "English"
 msgstr "Britannicus"
 
-#: aleksis/core/settings.py:530
+#: aleksis/core/settings.py:541
 msgid "German"
 msgstr "Germanus"
 
@@ -1303,7 +1347,7 @@ msgstr "Germanus"
 #: aleksis/core/templates/core/announcement/list.html:36
 #: aleksis/core/templates/core/group/full.html:24
 #: aleksis/core/templates/core/person/full.html:26
-#: aleksis/core/templates/core/person/full.html:98
+#: aleksis/core/templates/core/person/full.html:86
 #: aleksis/core/templates/oauth2_provider/application/detail.html:17
 msgid "Edit"
 msgstr ""
@@ -1323,7 +1367,7 @@ msgstr "Nuntii"
 #: aleksis/core/templates/core/group/full.html:31
 #: aleksis/core/templates/core/pages/delete.html:22
 #: aleksis/core/templates/core/person/full.html:33
-#: aleksis/core/templates/core/person/full.html:105
+#: aleksis/core/templates/core/person/full.html:93
 #: aleksis/core/templates/oauth2_provider/application/detail.html:21
 msgid "Delete"
 msgstr ""
@@ -1370,6 +1414,10 @@ msgid ""
 "          "
 msgstr ""
 
+#: aleksis/core/templates/500.html:21
+msgid "Retry"
+msgstr ""
+
 #: aleksis/core/templates/503.html:10
 msgid ""
 "The maintenance mode is currently enabled. Please try again\n"
@@ -1646,19 +1694,15 @@ msgstr ""
 msgid "There are no announcements."
 msgstr "Scribe nuntium:"
 
-#: aleksis/core/templates/core/base.html:78
-msgid "Logged in as"
-msgstr ""
-
-#: aleksis/core/templates/core/base.html:179
+#: aleksis/core/templates/core/base.html:213
 msgid "About AlekSIS® — The Free School Information System"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:187
+#: aleksis/core/templates/core/base.html:221
 msgid "Imprint"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:195
+#: aleksis/core/templates/core/base.html:229
 msgid "Privacy Policy"
 msgstr ""
 
@@ -1893,7 +1937,7 @@ msgstr ""
 
 #: aleksis/core/templates/core/group/full.html:38
 #: aleksis/core/templates/core/person/full.html:40
-#: aleksis/core/templates/core/person/full.html:112
+#: aleksis/core/templates/core/person/full.html:100
 msgid "Change preferences"
 msgstr ""
 
@@ -2099,87 +2143,87 @@ msgstr ""
 msgid "System checks"
 msgstr "Status systemae"
 
-#: aleksis/core/templates/core/pages/system_status.html:21
+#: aleksis/core/templates/core/pages/system_status.html:22
 msgid "Maintenance mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:23
+#: aleksis/core/templates/core/pages/system_status.html:24
 msgid ""
 "\n"
 "                Only admin and visitors from internal IPs can access thesite.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:34
+#: aleksis/core/templates/core/pages/system_status.html:36
 msgid "Maintenance mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:35
+#: aleksis/core/templates/core/pages/system_status.html:37
 msgid "Everyone can access the site."
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:45
+#: aleksis/core/templates/core/pages/system_status.html:47
 msgid "Debug mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:47
+#: aleksis/core/templates/core/pages/system_status.html:49
 msgid ""
 "\n"
 "                The web server throws back debug information on errors. Do not use in production!\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:54
+#: aleksis/core/templates/core/pages/system_status.html:56
 msgid "Debug mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:56
+#: aleksis/core/templates/core/pages/system_status.html:58
 msgid ""
 "\n"
 "                Debug mode is disabled. Default error pages are displayed on errors.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:69
+#: aleksis/core/templates/core/pages/system_status.html:71
 #, fuzzy
 #| msgid "System status"
 msgid "System health checks"
 msgstr "Status systemae"
 
-#: aleksis/core/templates/core/pages/system_status.html:75
+#: aleksis/core/templates/core/pages/system_status.html:77
 msgid "Service"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:76
-#: aleksis/core/templates/core/pages/system_status.html:115
+#: aleksis/core/templates/core/pages/system_status.html:78
+#: aleksis/core/templates/core/pages/system_status.html:119
 #, fuzzy
 #| msgid "System status"
 msgid "Status"
 msgstr "Status systemae"
 
-#: aleksis/core/templates/core/pages/system_status.html:77
+#: aleksis/core/templates/core/pages/system_status.html:79
 msgid "Time taken"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:96
+#: aleksis/core/templates/core/pages/system_status.html:100
 msgid "seconds"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:107
+#: aleksis/core/templates/core/pages/system_status.html:111
 msgid "Celery task results"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:112
+#: aleksis/core/templates/core/pages/system_status.html:116
 #: aleksis/core/templates/templated_email/celery_failure.email:9
 #: aleksis/core/templates/templated_email/celery_failure.email:28
 msgid "Task"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:113
+#: aleksis/core/templates/core/pages/system_status.html:117
 msgid "ID"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:114
+#: aleksis/core/templates/core/pages/system_status.html:118
 #, fuzzy
 #| msgid "Date"
 msgid "Date done"
@@ -2222,6 +2266,20 @@ msgid ""
 "            "
 msgstr ""
 
+#: aleksis/core/templates/core/partials/avatar_content.html:14
+#: aleksis/core/templates/core/partials/avatar_content.html:15
+#: aleksis/core/templates/core/person/full.html:213
+#: aleksis/core/templates/core/person/full.html:214
+msgid "Avatar"
+msgstr ""
+
+#: aleksis/core/templates/core/partials/avatar_content.html:19
+#: aleksis/core/templates/core/partials/avatar_content.html:20
+#, fuzzy
+#| msgid "Notifications"
+msgid "Identicon"
+msgstr "Nuntii"
+
 #: aleksis/core/templates/core/partials/crud_events.html:15
 msgid "Changed by"
 msgstr ""
@@ -2316,28 +2374,28 @@ msgid "Edit person"
 msgstr ""
 
 #: aleksis/core/templates/core/person/full.html:47
-#: aleksis/core/templates/core/person/full.html:119
+#: aleksis/core/templates/core/person/full.html:107
 #, fuzzy
 #| msgid "Impersonation"
 msgid "Impersonate"
 msgstr "Simulare aliquem"
 
 #: aleksis/core/templates/core/person/full.html:54
-#: aleksis/core/templates/core/person/full.html:126
+#: aleksis/core/templates/core/person/full.html:114
 #, fuzzy
 #| msgid "Impersonation"
 msgid "Invite user"
 msgstr "Simulare aliquem"
 
-#: aleksis/core/templates/core/person/full.html:133
+#: aleksis/core/templates/core/person/full.html:121
 msgid "Contact details"
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:224
+#: aleksis/core/templates/core/person/full.html:220
 msgid "This person didn't upload a personal photo."
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:232
+#: aleksis/core/templates/core/person/full.html:228
 msgid "Children"
 msgstr ""
 
@@ -2559,13 +2617,11 @@ msgstr "Nuntii"
 msgid "Network error"
 msgstr ""
 
-#: aleksis/core/templates/offline.html:8
-msgid ""
-"No internet\n"
-"    connection."
+#: aleksis/core/templates/offline.html:10
+msgid "No internet connection."
 msgstr ""
 
-#: aleksis/core/templates/offline.html:12
+#: aleksis/core/templates/offline.html:14
 msgid ""
 "\n"
 "      There was an error accessing this page. You probably don't have an internet connection. Check to see if your WiFi\n"
@@ -2824,7 +2880,7 @@ msgstr ""
 #: aleksis/core/templates/two_factor/_base_focus.html:6
 #: aleksis/core/templates/two_factor/core/otp_required.html:22
 #: aleksis/core/templates/two_factor/core/setup.html:5
-#: aleksis/core/templates/two_factor/profile/profile.html:87
+#: aleksis/core/templates/two_factor/profile/profile.html:88
 msgid "Enable Two-Factor Authentication"
 msgstr ""
 
@@ -2929,15 +2985,15 @@ msgstr ""
 msgid "Or, alternatively, use one of your backup phones:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:121
+#: aleksis/core/templates/two_factor/core/login.html:122
 msgid "As a last resort, you can use a backup token:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:124
+#: aleksis/core/templates/two_factor/core/login.html:125
 msgid "Use Backup Token"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:135
+#: aleksis/core/templates/two_factor/core/login.html:136
 msgid "Use alternative login options"
 msgstr ""
 
@@ -3174,11 +3230,11 @@ msgid ""
 "      "
 msgstr ""
 
-#: aleksis/core/util/notifications.py:63
+#: aleksis/core/util/notifications.py:64
 msgid "E-Mail"
 msgstr ""
 
-#: aleksis/core/util/notifications.py:64
+#: aleksis/core/util/notifications.py:65
 msgid "SMS"
 msgstr ""
 
@@ -3202,159 +3258,159 @@ msgstr ""
 msgid "Download PDF"
 msgstr ""
 
-#: aleksis/core/views.py:285
+#: aleksis/core/views.py:289
 msgid "The school term has been created."
 msgstr ""
 
-#: aleksis/core/views.py:297
+#: aleksis/core/views.py:301
 msgid "The school term has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:417
+#: aleksis/core/views.py:421
 msgid "The child groups were successfully saved."
 msgstr ""
 
-#: aleksis/core/views.py:436 aleksis/core/views.py:446
+#: aleksis/core/views.py:440 aleksis/core/views.py:450
 msgid "The person has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:496
+#: aleksis/core/views.py:500
 msgid "The group has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:593
+#: aleksis/core/views.py:597
 msgid "The announcement has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:609
+#: aleksis/core/views.py:613
 msgid "The announcement has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:677
+#: aleksis/core/views.py:681
 msgid "The requested preference registry does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:696
+#: aleksis/core/views.py:700
 msgid "The preferences have been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:720
+#: aleksis/core/views.py:724
 msgid "The person has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:734
+#: aleksis/core/views.py:738
 msgid "The group has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:766
+#: aleksis/core/views.py:770
 msgid "The additional_field has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:800
+#: aleksis/core/views.py:804
 msgid "The additional field has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:825
+#: aleksis/core/views.py:829
 msgid "The group type has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:855
+#: aleksis/core/views.py:859
 msgid "The group type has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:888
+#: aleksis/core/views.py:892
 msgid "Progress: Run data checks"
 msgstr ""
 
-#: aleksis/core/views.py:889
+#: aleksis/core/views.py:893
 #, fuzzy
 #| msgid "System status"
 msgid "Run data checks …"
 msgstr "Status systemae"
 
-#: aleksis/core/views.py:890
+#: aleksis/core/views.py:894
 msgid "The data checks were run successfully."
 msgstr ""
 
-#: aleksis/core/views.py:891
+#: aleksis/core/views.py:895
 msgid "There was a problem while running data checks."
 msgstr ""
 
-#: aleksis/core/views.py:907
+#: aleksis/core/views.py:911
 #, python-brace-format
 msgid "The solve option '{solve_option_obj.verbose_name}' "
 msgstr ""
 
-#: aleksis/core/views.py:917
+#: aleksis/core/views.py:921
 msgid "The requested solve option does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:949
+#: aleksis/core/views.py:953
 msgid "The dashboard widget has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:979
+#: aleksis/core/views.py:983
 msgid "The dashboard widget has been created."
 msgstr ""
 
-#: aleksis/core/views.py:989
+#: aleksis/core/views.py:993
 msgid "The dashboard widget has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1060
+#: aleksis/core/views.py:1064
 msgid "Your dashboard configuration has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1062
+#: aleksis/core/views.py:1066
 msgid "The configuration of the default dashboard has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1138
+#: aleksis/core/views.py:1142
 #, python-brace-format
 msgid "The invitation was successfully created. The invitation code is {code}"
 msgstr ""
 
-#: aleksis/core/views.py:1229
+#: aleksis/core/views.py:1233
 msgid "We have successfully assigned the permissions."
 msgstr ""
 
-#: aleksis/core/views.py:1239
+#: aleksis/core/views.py:1243
 msgid "The global user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1249
+#: aleksis/core/views.py:1253
 msgid "The global group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1259
+#: aleksis/core/views.py:1263
 msgid "The object user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1269
+#: aleksis/core/views.py:1273
 msgid "The object group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1337
+#: aleksis/core/views.py:1341
 msgid "The requested PDF file does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:1346 aleksis/core/views.py:1350
+#: aleksis/core/views.py:1350 aleksis/core/views.py:1354
 msgid "The requested task does not exist or is not accessible"
 msgstr ""
 
-#: aleksis/core/views.py:1388
+#: aleksis/core/views.py:1406
 msgid "The third-party account could not be disconnected because it is the only login method available."
 msgstr ""
 
-#: aleksis/core/views.py:1395
+#: aleksis/core/views.py:1413
 msgid "The third-party account has been successfully disconnected."
 msgstr ""
 
-#: aleksis/core/views.py:1466
+#: aleksis/core/views.py:1484
 msgid "Person was invited successfully and an email with further instructions has been send to them."
 msgstr ""
 
-#: aleksis/core/views.py:1477
+#: aleksis/core/views.py:1495
 msgid "Person was already invited."
 msgstr ""
 
diff --git a/aleksis/core/locale/la/LC_MESSAGES/djangojs.po b/aleksis/core/locale/la/LC_MESSAGES/djangojs.po
index dfec73c5c894d3d686092593be9c21a811cd0535..f3df4c3fc6d2848a739ddd1faac44cb9048c97b2 100644
--- a/aleksis/core/locale/la/LC_MESSAGES/djangojs.po
+++ b/aleksis/core/locale/la/LC_MESSAGES/djangojs.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:20+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,6 +29,6 @@ msgstr ""
 msgid "OK"
 msgstr ""
 
-#: aleksis/core/static/js/main.js:191
+#: aleksis/core/static/js/main.js:195
 msgid "This page may contain outdated information since there is no internet connection."
 msgstr ""
diff --git a/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po b/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po
index ab1f6b974dc48a296d3eead2faacea0232b1461d..71dc8727c985e991974f2700347f6fb37e4f7b47 100644
--- a/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: AlekSIS (School Information System) 0.1\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:19+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -37,10 +37,10 @@ msgstr ""
 msgid "Home and mobile phone"
 msgstr ""
 
-#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:265
-#: aleksis/core/models.py:462 aleksis/core/templates/core/group/list.html:8
+#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:185
+#: aleksis/core/models.py:487 aleksis/core/templates/core/group/list.html:8
 #: aleksis/core/templates/core/group/list.html:9
-#: aleksis/core/templates/core/person/full.html:250
+#: aleksis/core/templates/core/person/full.html:246
 msgid "Groups"
 msgstr ""
 
@@ -65,8 +65,8 @@ msgstr ""
 msgid "The DashboardWidget was reported broken automatically."
 msgstr ""
 
-#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:105
-#: aleksis/core/templates/core/base.html:106
+#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:139
+#: aleksis/core/templates/core/base.html:140
 #: aleksis/core/templates/core/group/list.html:20
 #: aleksis/core/templates/core/person/list.html:24
 #: aleksis/core/templates/search/search.html:7
@@ -90,11 +90,11 @@ msgstr ""
 msgid "Content type"
 msgstr ""
 
-#: aleksis/core/filters.py:113 aleksis/core/models.py:688
+#: aleksis/core/filters.py:113 aleksis/core/models.py:713
 msgid "User"
 msgstr ""
 
-#: aleksis/core/filters.py:135 aleksis/core/models.py:461
+#: aleksis/core/filters.py:135 aleksis/core/models.py:486
 msgid "Group"
 msgstr ""
 
@@ -130,7 +130,7 @@ msgstr ""
 msgid "This username is already in use."
 msgstr ""
 
-#: aleksis/core/forms.py:153 aleksis/core/models.py:130
+#: aleksis/core/forms.py:153 aleksis/core/models.py:134
 msgid "School term"
 msgstr ""
 
@@ -139,7 +139,7 @@ msgid "Common data"
 msgstr ""
 
 #: aleksis/core/forms.py:155 aleksis/core/forms.py:207
-#: aleksis/core/menus.py:254 aleksis/core/models.py:153
+#: aleksis/core/menus.py:174 aleksis/core/models.py:157
 #: aleksis/core/templates/core/person/list.html:8
 #: aleksis/core/templates/core/person/list.html:9
 msgid "Persons"
@@ -149,18 +149,18 @@ msgstr ""
 msgid "Additional data"
 msgstr ""
 
-#: aleksis/core/forms.py:157 aleksis/core/models.py:206
-#: aleksis/core/models.py:514
+#: aleksis/core/forms.py:157 aleksis/core/models.py:210
+#: aleksis/core/models.py:539
 msgid "Photo"
 msgstr ""
 
 #: aleksis/core/forms.py:199 aleksis/core/forms.py:202
-#: aleksis/core/models.py:73
+#: aleksis/core/models.py:77
 msgid "Date"
 msgstr ""
 
 #: aleksis/core/forms.py:200 aleksis/core/forms.py:203
-#: aleksis/core/models.py:81
+#: aleksis/core/models.py:85
 msgid "Time"
 msgstr ""
 
@@ -196,11 +196,11 @@ msgstr ""
 msgid "Please enter your invitation code."
 msgstr ""
 
-#: aleksis/core/forms.py:418 aleksis/core/models.py:181
+#: aleksis/core/forms.py:418 aleksis/core/models.py:185
 msgid "First name"
 msgstr ""
 
-#: aleksis/core/forms.py:419 aleksis/core/models.py:182
+#: aleksis/core/forms.py:419 aleksis/core/models.py:186
 msgid "Last name"
 msgstr ""
 
@@ -248,7 +248,15 @@ msgstr ""
 msgid "Password (again)"
 msgstr ""
 
-#: aleksis/core/forms.py:775
+#: aleksis/core/forms.py:728
+msgid "The selected action does not exist."
+msgstr ""
+
+#: aleksis/core/forms.py:739
+msgid "You do not have permission to run {} on all selected objects."
+msgstr ""
+
+#: aleksis/core/forms.py:795
 msgid "No valid selection."
 msgstr ""
 
@@ -291,678 +299,698 @@ msgstr ""
 msgid "Dashboard"
 msgstr ""
 
-#: aleksis/core/menus.py:41 aleksis/core/models.py:734
-#: aleksis/core/preferences.py:29
+#: aleksis/core/menus.py:41 aleksis/core/models.py:765
+#: aleksis/core/preferences.py:29 aleksis/core/templates/core/base.html:81
 #: aleksis/core/templates/core/notifications.html:4
 #: aleksis/core/templates/core/notifications.html:5
 msgid "Notifications"
 msgstr ""
 
 #: aleksis/core/menus.py:53
-msgid "Account"
-msgstr ""
-
-#: aleksis/core/menus.py:60
-msgid "Stop impersonation"
-msgstr ""
-
-#: aleksis/core/menus.py:69 aleksis/core/templates/core/base.html:80
-msgid "Logout"
-msgstr ""
-
-#: aleksis/core/menus.py:75
-msgid "2FA"
-msgstr ""
-
-#: aleksis/core/menus.py:83
-#: aleksis/core/templates/account/password_change.html:5
-#: aleksis/core/templates/account/password_change.html:6
-#: aleksis/core/templates/account/password_change.html:19
-#: aleksis/core/templates/account/password_reset_from_key.html:5
-#: aleksis/core/templates/account/password_reset_from_key.html:42
-#: aleksis/core/templates/account/password_reset_from_key.html:46
-#: aleksis/core/templates/account/password_reset_from_key_done.html:5
-#: aleksis/core/templates/account/password_reset_from_key_done.html:6
-msgid "Change password"
-msgstr ""
-
-#: aleksis/core/menus.py:95
-msgid "Me"
-msgstr ""
-
-#: aleksis/core/menus.py:104
-#: aleksis/core/templates/dynamic_preferences/form.html:5
-msgid "Preferences"
-msgstr ""
-
-#: aleksis/core/menus.py:113
-msgid "Third-party accounts"
-msgstr ""
-
-#: aleksis/core/menus.py:122
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
-msgid "Authorized applications"
-msgstr ""
-
-#: aleksis/core/menus.py:133
 msgid "Admin"
 msgstr ""
 
-#: aleksis/core/menus.py:141 aleksis/core/models.py:834
+#: aleksis/core/menus.py:61 aleksis/core/models.py:865
 #: aleksis/core/templates/core/announcement/list.html:7
 #: aleksis/core/templates/core/announcement/list.html:8
 msgid "Announcements"
 msgstr ""
 
-#: aleksis/core/menus.py:152 aleksis/core/models.py:131
+#: aleksis/core/menus.py:72 aleksis/core/models.py:135
 #: aleksis/core/templates/core/school_term/list.html:8
 #: aleksis/core/templates/core/school_term/list.html:9
 msgid "School terms"
 msgstr ""
 
-#: aleksis/core/menus.py:163
+#: aleksis/core/menus.py:83
 #: aleksis/core/templates/core/dashboard_widget/list.html:8
 #: aleksis/core/templates/core/dashboard_widget/list.html:9
 msgid "Dashboard widgets"
 msgstr ""
 
-#: aleksis/core/menus.py:174
+#: aleksis/core/menus.py:94
 #: aleksis/core/templates/core/management/data_management.html:6
 #: aleksis/core/templates/core/management/data_management.html:7
 msgid "Data management"
 msgstr ""
 
-#: aleksis/core/menus.py:185
+#: aleksis/core/menus.py:105
 #: aleksis/core/templates/core/pages/system_status.html:5
 #: aleksis/core/templates/core/pages/system_status.html:7
 msgid "System status"
 msgstr ""
 
-#: aleksis/core/menus.py:196
+#: aleksis/core/menus.py:116
 msgid "Configuration"
 msgstr ""
 
-#: aleksis/core/menus.py:207 aleksis/core/templates/core/data_check/list.html:9
+#: aleksis/core/menus.py:127 aleksis/core/templates/core/data_check/list.html:9
 #: aleksis/core/templates/core/data_check/list.html:10
 msgid "Data checks"
 msgstr ""
 
-#: aleksis/core/menus.py:213 aleksis/core/templates/core/perms/list.html:13
+#: aleksis/core/menus.py:133 aleksis/core/templates/core/perms/list.html:13
 #: aleksis/core/templates/core/perms/list.html:14
 msgid "Manage permissions"
 msgstr ""
 
-#: aleksis/core/menus.py:224
+#: aleksis/core/menus.py:144
 msgid "Backend Admin"
 msgstr ""
 
-#: aleksis/core/menus.py:232
+#: aleksis/core/menus.py:152
 #: aleksis/core/templates/oauth2_provider/application/list.html:5
 #: aleksis/core/templates/oauth2_provider/application/list.html:6
 msgid "OAuth2 Applications"
 msgstr ""
 
-#: aleksis/core/menus.py:245
+#: aleksis/core/menus.py:165
 msgid "People"
 msgstr ""
 
-#: aleksis/core/menus.py:276 aleksis/core/models.py:1055
+#: aleksis/core/menus.py:196 aleksis/core/models.py:1099
 #: aleksis/core/templates/core/group_type/list.html:8
 #: aleksis/core/templates/core/group_type/list.html:9
 msgid "Group types"
 msgstr ""
 
-#: aleksis/core/menus.py:287
+#: aleksis/core/menus.py:207
 msgid "Groups and child groups"
 msgstr ""
 
-#: aleksis/core/menus.py:298 aleksis/core/models.py:510
+#: aleksis/core/menus.py:218 aleksis/core/models.py:535
 #: aleksis/core/templates/core/additional_field/list.html:8
 #: aleksis/core/templates/core/additional_field/list.html:9
 msgid "Additional fields"
 msgstr ""
 
-#: aleksis/core/menus.py:309
+#: aleksis/core/menus.py:229
 msgid "Invite person"
 msgstr ""
 
-#: aleksis/core/menus.py:322
+#: aleksis/core/menus.py:242
 #: aleksis/core/templates/core/group/child_groups.html:7
 #: aleksis/core/templates/core/group/child_groups.html:9
 msgid "Assign child groups to groups"
 msgstr ""
 
+#: aleksis/core/menus.py:254
+msgid "Stop impersonation"
+msgstr ""
+
+#: aleksis/core/menus.py:263
+msgid "Account"
+msgstr ""
+
+#: aleksis/core/menus.py:272
+#: aleksis/core/templates/dynamic_preferences/form.html:5
+msgid "Preferences"
+msgstr ""
+
+#: aleksis/core/menus.py:281
+msgid "2FA"
+msgstr ""
+
+#: aleksis/core/menus.py:289
+#: aleksis/core/templates/account/password_change.html:5
+#: aleksis/core/templates/account/password_change.html:6
+#: aleksis/core/templates/account/password_change.html:19
+#: aleksis/core/templates/account/password_reset_from_key.html:5
+#: aleksis/core/templates/account/password_reset_from_key.html:42
+#: aleksis/core/templates/account/password_reset_from_key.html:46
+#: aleksis/core/templates/account/password_reset_from_key_done.html:5
+#: aleksis/core/templates/account/password_reset_from_key_done.html:6
+msgid "Change password"
+msgstr ""
+
+#: aleksis/core/menus.py:301
+msgid "Third-party accounts"
+msgstr ""
+
+#: aleksis/core/menus.py:310
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
+msgid "Authorized applications"
+msgstr ""
+
+#: aleksis/core/menus.py:320
+msgid "Logout"
+msgstr ""
+
 #: aleksis/core/mixins.py:511
 msgid "Linked school term"
 msgstr ""
 
-#: aleksis/core/models.py:71
+#: aleksis/core/models.py:75
 msgid "Boolean (Yes/No)"
 msgstr ""
 
-#: aleksis/core/models.py:72
+#: aleksis/core/models.py:76
 msgid "Text (one line)"
 msgstr ""
 
-#: aleksis/core/models.py:74
+#: aleksis/core/models.py:78
 msgid "Date and time"
 msgstr ""
 
-#: aleksis/core/models.py:75
+#: aleksis/core/models.py:79
 msgid "Decimal number"
 msgstr ""
 
-#: aleksis/core/models.py:76 aleksis/core/models.py:199
+#: aleksis/core/models.py:80 aleksis/core/models.py:203
 msgid "E-mail address"
 msgstr ""
 
-#: aleksis/core/models.py:77
+#: aleksis/core/models.py:81
 msgid "Integer"
 msgstr ""
 
-#: aleksis/core/models.py:78
+#: aleksis/core/models.py:82
 msgid "IP address"
 msgstr ""
 
-#: aleksis/core/models.py:79
+#: aleksis/core/models.py:83
 msgid "Boolean or empty (Yes/No/Neither)"
 msgstr ""
 
-#: aleksis/core/models.py:80
+#: aleksis/core/models.py:84
 msgid "Text (multi-line)"
 msgstr ""
 
-#: aleksis/core/models.py:82
+#: aleksis/core/models.py:86
 msgid "URL / Link"
 msgstr ""
 
-#: aleksis/core/models.py:94 aleksis/core/models.py:1024
+#: aleksis/core/models.py:98 aleksis/core/models.py:1068
 msgid "Name"
 msgstr ""
 
-#: aleksis/core/models.py:96
+#: aleksis/core/models.py:100
 msgid "Start date"
 msgstr ""
 
-#: aleksis/core/models.py:97
+#: aleksis/core/models.py:101
 msgid "End date"
 msgstr ""
 
-#: aleksis/core/models.py:116
+#: aleksis/core/models.py:120
 msgid "The start date must be earlier than the end date."
 msgstr ""
 
-#: aleksis/core/models.py:123
+#: aleksis/core/models.py:127
 msgid "There is already a school term for this time or a part of this time."
 msgstr ""
 
-#: aleksis/core/models.py:152 aleksis/core/models.py:973
+#: aleksis/core/models.py:156 aleksis/core/models.py:1017
 msgid "Person"
 msgstr ""
 
-#: aleksis/core/models.py:155
+#: aleksis/core/models.py:159
 msgid "Can view address"
 msgstr ""
 
-#: aleksis/core/models.py:156
+#: aleksis/core/models.py:160
 msgid "Can view contact details"
 msgstr ""
 
-#: aleksis/core/models.py:157
+#: aleksis/core/models.py:161
 msgid "Can view photo"
 msgstr ""
 
-#: aleksis/core/models.py:158
+#: aleksis/core/models.py:162
 msgid "Can view avatar image"
 msgstr ""
 
-#: aleksis/core/models.py:159
+#: aleksis/core/models.py:163
 msgid "Can view persons groups"
 msgstr ""
 
-#: aleksis/core/models.py:160
+#: aleksis/core/models.py:164
 msgid "Can view personal details"
 msgstr ""
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "female"
 msgstr ""
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "male"
 msgstr ""
 
-#: aleksis/core/models.py:178 aleksis/core/models.py:1227
+#: aleksis/core/models.py:182 aleksis/core/models.py:1271
 msgid "Linked user"
 msgstr ""
 
-#: aleksis/core/models.py:184
+#: aleksis/core/models.py:188
 msgid "Additional name(s)"
 msgstr ""
 
-#: aleksis/core/models.py:188 aleksis/core/models.py:479
+#: aleksis/core/models.py:192 aleksis/core/models.py:504
 msgid "Short name"
 msgstr ""
 
-#: aleksis/core/models.py:191
+#: aleksis/core/models.py:195
 msgid "Street"
 msgstr ""
 
-#: aleksis/core/models.py:192
+#: aleksis/core/models.py:196
 msgid "Street number"
 msgstr ""
 
-#: aleksis/core/models.py:193
+#: aleksis/core/models.py:197
 msgid "Postal code"
 msgstr ""
 
-#: aleksis/core/models.py:194
+#: aleksis/core/models.py:198
 msgid "Place"
 msgstr ""
 
-#: aleksis/core/models.py:196 aleksis/core/templates/core/person/full.html:172
+#: aleksis/core/models.py:200 aleksis/core/templates/core/person/full.html:160
 msgid "Home phone"
 msgstr ""
 
-#: aleksis/core/models.py:197 aleksis/core/templates/core/person/full.html:182
+#: aleksis/core/models.py:201 aleksis/core/templates/core/person/full.html:170
 msgid "Mobile phone"
 msgstr ""
 
-#: aleksis/core/models.py:201
+#: aleksis/core/models.py:205
 msgid "Date of birth"
 msgstr ""
 
-#: aleksis/core/models.py:202
+#: aleksis/core/models.py:206
 msgid "Place of birth"
 msgstr ""
 
-#: aleksis/core/models.py:203
+#: aleksis/core/models.py:207
 msgid "Sex"
 msgstr ""
 
-#: aleksis/core/models.py:210 aleksis/core/models.py:518
+#: aleksis/core/models.py:214 aleksis/core/models.py:543
 msgid "This is an official photo, used for official documents and for internal use cases."
 msgstr ""
 
-#: aleksis/core/models.py:215 aleksis/core/models.py:522
+#: aleksis/core/models.py:219 aleksis/core/models.py:547
 msgid "Display picture / Avatar"
 msgstr ""
 
-#: aleksis/core/models.py:218 aleksis/core/models.py:525
+#: aleksis/core/models.py:222 aleksis/core/models.py:550
 msgid "This is a picture or an avatar for public display."
 msgstr ""
 
-#: aleksis/core/models.py:223 aleksis/core/templates/core/person/full.html:239
+#: aleksis/core/models.py:227 aleksis/core/templates/core/person/full.html:235
 msgid "Guardians / Parents"
 msgstr ""
 
-#: aleksis/core/models.py:230
+#: aleksis/core/models.py:234
 msgid "Primary group"
 msgstr ""
 
-#: aleksis/core/models.py:233 aleksis/core/models.py:692
-#: aleksis/core/models.py:716 aleksis/core/models.py:801
-#: aleksis/core/models.py:1048
+#: aleksis/core/models.py:237 aleksis/core/models.py:717
+#: aleksis/core/models.py:741 aleksis/core/models.py:832
+#: aleksis/core/models.py:1092
 msgid "Description"
 msgstr ""
 
-#: aleksis/core/models.py:434
+#: aleksis/core/models.py:457
 msgid "Title of field"
 msgstr ""
 
-#: aleksis/core/models.py:436
+#: aleksis/core/models.py:459
 msgid "Type of field"
 msgstr ""
 
-#: aleksis/core/models.py:443
+#: aleksis/core/models.py:461
+msgid "Required"
+msgstr ""
+
+#: aleksis/core/models.py:462
+msgid "Help text / description"
+msgstr ""
+
+#: aleksis/core/models.py:468
 msgid "Addtitional field for groups"
 msgstr ""
 
-#: aleksis/core/models.py:444
+#: aleksis/core/models.py:469
 msgid "Addtitional fields for groups"
 msgstr ""
 
-#: aleksis/core/models.py:464
+#: aleksis/core/models.py:489
 msgid "Can assign child groups to groups"
 msgstr ""
 
-#: aleksis/core/models.py:465
+#: aleksis/core/models.py:490
 msgid "Can view statistics about group."
 msgstr ""
 
-#: aleksis/core/models.py:477
+#: aleksis/core/models.py:502
 msgid "Long name"
 msgstr ""
 
-#: aleksis/core/models.py:487 aleksis/core/templates/core/group/full.html:85
+#: aleksis/core/models.py:512 aleksis/core/templates/core/group/full.html:85
 msgid "Members"
 msgstr ""
 
-#: aleksis/core/models.py:490 aleksis/core/templates/core/group/full.html:82
+#: aleksis/core/models.py:515 aleksis/core/templates/core/group/full.html:82
 msgid "Owners"
 msgstr ""
 
-#: aleksis/core/models.py:497 aleksis/core/templates/core/group/full.html:55
+#: aleksis/core/models.py:522 aleksis/core/templates/core/group/full.html:55
 msgid "Parent groups"
 msgstr ""
 
-#: aleksis/core/models.py:505
+#: aleksis/core/models.py:530
 msgid "Type of group"
 msgstr ""
 
-#: aleksis/core/models.py:691 aleksis/core/models.py:715
-#: aleksis/core/models.py:800
+#: aleksis/core/models.py:716 aleksis/core/models.py:740
+#: aleksis/core/models.py:831
 #: aleksis/core/templates/core/announcement/list.html:18
 msgid "Title"
 msgstr ""
 
-#: aleksis/core/models.py:694
+#: aleksis/core/models.py:719
 msgid "Application"
 msgstr ""
 
-#: aleksis/core/models.py:700
+#: aleksis/core/models.py:725
 msgid "Activity"
 msgstr ""
 
-#: aleksis/core/models.py:701
+#: aleksis/core/models.py:726
 msgid "Activities"
 msgstr ""
 
-#: aleksis/core/models.py:707
+#: aleksis/core/models.py:732
 msgid "Sender"
 msgstr ""
 
-#: aleksis/core/models.py:712
+#: aleksis/core/models.py:737
 msgid "Recipient"
 msgstr ""
 
-#: aleksis/core/models.py:717 aleksis/core/models.py:1025
+#: aleksis/core/models.py:742 aleksis/core/models.py:1069
 msgid "Link"
 msgstr ""
 
-#: aleksis/core/models.py:719
+#: aleksis/core/models.py:744
+msgid "Send notification at"
+msgstr ""
+
+#: aleksis/core/models.py:746
 msgid "Read"
 msgstr ""
 
-#: aleksis/core/models.py:720
+#: aleksis/core/models.py:747
 msgid "Sent"
 msgstr ""
 
-#: aleksis/core/models.py:733
+#: aleksis/core/models.py:764
 msgid "Notification"
 msgstr ""
 
-#: aleksis/core/models.py:802
+#: aleksis/core/models.py:833
 msgid "Link to detailed view"
 msgstr ""
 
-#: aleksis/core/models.py:805
+#: aleksis/core/models.py:836
 msgid "Date and time from when to show"
 msgstr ""
 
-#: aleksis/core/models.py:808
+#: aleksis/core/models.py:839
 msgid "Date and time until when to show"
 msgstr ""
 
-#: aleksis/core/models.py:833
+#: aleksis/core/models.py:864
 msgid "Announcement"
 msgstr ""
 
-#: aleksis/core/models.py:871
+#: aleksis/core/models.py:902
 msgid "Announcement recipient"
 msgstr ""
 
-#: aleksis/core/models.py:872
+#: aleksis/core/models.py:903
 msgid "Announcement recipients"
 msgstr ""
 
-#: aleksis/core/models.py:894
+#: aleksis/core/models.py:925
 msgid "Widget Title"
 msgstr ""
 
-#: aleksis/core/models.py:895
+#: aleksis/core/models.py:926
 msgid "Activate Widget"
 msgstr ""
 
-#: aleksis/core/models.py:896
+#: aleksis/core/models.py:927
 msgid "Widget is broken"
 msgstr ""
 
-#: aleksis/core/models.py:899
+#: aleksis/core/models.py:930
 msgid "Size on mobile devices"
 msgstr ""
 
-#: aleksis/core/models.py:900
+#: aleksis/core/models.py:931
 msgid "<= 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:905
+#: aleksis/core/models.py:936
 msgid "Size on tablet devices"
 msgstr ""
 
-#: aleksis/core/models.py:906
+#: aleksis/core/models.py:937
 msgid "> 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:911
+#: aleksis/core/models.py:942
 msgid "Size on desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:912
+#: aleksis/core/models.py:943
 msgid "> 992 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:917
+#: aleksis/core/models.py:948
 msgid "Size on large desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:918
+#: aleksis/core/models.py:949
 msgid "> 1200 px>, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:949
+#: aleksis/core/models.py:980
 msgid "Can edit default dashboard"
 msgstr ""
 
-#: aleksis/core/models.py:950
+#: aleksis/core/models.py:981
 msgid "Dashboard Widget"
 msgstr ""
 
-#: aleksis/core/models.py:951
+#: aleksis/core/models.py:982
 msgid "Dashboard Widgets"
 msgstr ""
 
-#: aleksis/core/models.py:957
+#: aleksis/core/models.py:988
 msgid "URL"
 msgstr ""
 
-#: aleksis/core/models.py:958
+#: aleksis/core/models.py:989
 msgid "Icon URL"
 msgstr ""
 
-#: aleksis/core/models.py:964
+#: aleksis/core/models.py:995
 msgid "External link widget"
 msgstr ""
 
-#: aleksis/core/models.py:965
+#: aleksis/core/models.py:996
 msgid "External link widgets"
 msgstr ""
 
-#: aleksis/core/models.py:970
+#: aleksis/core/models.py:1002
+msgid "Content"
+msgstr ""
+
+#: aleksis/core/models.py:1008
+msgid "Static content widget"
+msgstr ""
+
+#: aleksis/core/models.py:1009
+msgid "Static content widgets"
+msgstr ""
+
+#: aleksis/core/models.py:1014
 msgid "Dashboard widget"
 msgstr ""
 
-#: aleksis/core/models.py:975
+#: aleksis/core/models.py:1019
 msgid "Order"
 msgstr ""
 
-#: aleksis/core/models.py:976
+#: aleksis/core/models.py:1020
 msgid "Part of the default dashboard"
 msgstr ""
 
-#: aleksis/core/models.py:991
+#: aleksis/core/models.py:1035
 msgid "Dashboard widget order"
 msgstr ""
 
-#: aleksis/core/models.py:992
+#: aleksis/core/models.py:1036
 msgid "Dashboard widget orders"
 msgstr ""
 
-#: aleksis/core/models.py:998
+#: aleksis/core/models.py:1042
 msgid "Menu ID"
 msgstr ""
 
-#: aleksis/core/models.py:1011
+#: aleksis/core/models.py:1055
 msgid "Custom menu"
 msgstr ""
 
-#: aleksis/core/models.py:1012
+#: aleksis/core/models.py:1056
 msgid "Custom menus"
 msgstr ""
 
-#: aleksis/core/models.py:1022
+#: aleksis/core/models.py:1066
 msgid "Menu"
 msgstr ""
 
-#: aleksis/core/models.py:1026 aleksis/core/models.py:1274
+#: aleksis/core/models.py:1070 aleksis/core/models.py:1318
 #: aleksis/core/templates/oauth2_provider/application/detail.html:26
 msgid "Icon"
 msgstr ""
 
-#: aleksis/core/models.py:1032
+#: aleksis/core/models.py:1076
 msgid "Custom menu item"
 msgstr ""
 
-#: aleksis/core/models.py:1033
+#: aleksis/core/models.py:1077
 msgid "Custom menu items"
 msgstr ""
 
-#: aleksis/core/models.py:1047
+#: aleksis/core/models.py:1091
 msgid "Title of type"
 msgstr ""
 
-#: aleksis/core/models.py:1054 aleksis/core/templates/core/group/full.html:47
+#: aleksis/core/models.py:1098 aleksis/core/templates/core/group/full.html:47
 msgid "Group type"
 msgstr ""
 
-#: aleksis/core/models.py:1068
+#: aleksis/core/models.py:1112
 msgid "Can view system status"
 msgstr ""
 
-#: aleksis/core/models.py:1069
+#: aleksis/core/models.py:1113
 msgid "Can manage data"
 msgstr ""
 
-#: aleksis/core/models.py:1070
+#: aleksis/core/models.py:1114
 msgid "Can impersonate"
 msgstr ""
 
-#: aleksis/core/models.py:1071
+#: aleksis/core/models.py:1115
 msgid "Can use search"
 msgstr ""
 
-#: aleksis/core/models.py:1072
+#: aleksis/core/models.py:1116
 msgid "Can change site preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1073
+#: aleksis/core/models.py:1117
 msgid "Can change person preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1074
+#: aleksis/core/models.py:1118
 msgid "Can change group preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1075
+#: aleksis/core/models.py:1119
 msgid "Can test PDF generation"
 msgstr ""
 
-#: aleksis/core/models.py:1076
+#: aleksis/core/models.py:1120
 msgid "Can invite persons"
 msgstr ""
 
-#: aleksis/core/models.py:1112
+#: aleksis/core/models.py:1156
 msgid "Related data check task"
 msgstr ""
 
-#: aleksis/core/models.py:1120
+#: aleksis/core/models.py:1164
 msgid "Issue solved"
 msgstr ""
 
-#: aleksis/core/models.py:1121
+#: aleksis/core/models.py:1165
 msgid "Notification sent"
 msgstr ""
 
-#: aleksis/core/models.py:1134
+#: aleksis/core/models.py:1178
 msgid "Data check result"
 msgstr ""
 
-#: aleksis/core/models.py:1135
+#: aleksis/core/models.py:1179
 msgid "Data check results"
 msgstr ""
 
-#: aleksis/core/models.py:1137
+#: aleksis/core/models.py:1181
 msgid "Can run data checks"
 msgstr ""
 
-#: aleksis/core/models.py:1138
+#: aleksis/core/models.py:1182
 msgid "Can solve data check problems"
 msgstr ""
 
-#: aleksis/core/models.py:1145
+#: aleksis/core/models.py:1189
 msgid "E-Mail address"
 msgstr ""
 
-#: aleksis/core/models.py:1177
+#: aleksis/core/models.py:1221
 msgid "Owner"
 msgstr ""
 
-#: aleksis/core/models.py:1181
+#: aleksis/core/models.py:1225
 msgid "File expires at"
 msgstr ""
 
-#: aleksis/core/models.py:1183
+#: aleksis/core/models.py:1227
 msgid "Generated HTML file"
 msgstr ""
 
-#: aleksis/core/models.py:1185
+#: aleksis/core/models.py:1229
 msgid "Generated PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1192
+#: aleksis/core/models.py:1236
 msgid "PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1193
+#: aleksis/core/models.py:1237
 msgid "PDF files"
 msgstr ""
 
-#: aleksis/core/models.py:1198
+#: aleksis/core/models.py:1242
 msgid "Task result"
 msgstr ""
 
-#: aleksis/core/models.py:1201
+#: aleksis/core/models.py:1245
 msgid "Task user"
 msgstr ""
 
-#: aleksis/core/models.py:1213
+#: aleksis/core/models.py:1257
 msgid "Task user assignment"
 msgstr ""
 
-#: aleksis/core/models.py:1214
+#: aleksis/core/models.py:1258
 msgid "Task user assignments"
 msgstr ""
 
-#: aleksis/core/models.py:1230
+#: aleksis/core/models.py:1274
 msgid "Additional attributes"
 msgstr ""
 
-#: aleksis/core/models.py:1268
+#: aleksis/core/models.py:1312
 msgid "Allowed scopes that clients can request"
 msgstr ""
 
-#: aleksis/core/models.py:1278
+#: aleksis/core/models.py:1322
 msgid "This image will be shown as icon in the authorization flow. It should be squared."
 msgstr ""
 
@@ -1083,86 +1111,98 @@ msgid "Allow users to change their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:279
-msgid "Enable signup"
+msgid "Allow users to reset their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:287
-msgid "Enable invitations"
+msgid "Enable signup"
 msgstr ""
 
 #: aleksis/core/preferences.py:295
-msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgid "Regular expression for allowed usernames"
 msgstr ""
 
 #: aleksis/core/preferences.py:303
+msgid "Enable invitations"
+msgstr ""
+
+#: aleksis/core/preferences.py:311
+msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgstr ""
+
+#: aleksis/core/preferences.py:319
 msgid "Size of packets. (Default 5: abcde)"
 msgstr ""
 
-#: aleksis/core/preferences.py:314
+#: aleksis/core/preferences.py:330
 msgid "Allowed Grant Flows for OAuth applications"
 msgstr ""
 
-#: aleksis/core/preferences.py:328
+#: aleksis/core/preferences.py:344
 msgid "Available languages"
 msgstr ""
 
-#: aleksis/core/preferences.py:341
+#: aleksis/core/preferences.py:357
 msgid "Send emails if data checks detect problems"
 msgstr ""
 
-#: aleksis/core/preferences.py:352
+#: aleksis/core/preferences.py:368
 msgid "Email recipients for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:363
+#: aleksis/core/preferences.py:379
 msgid "Email recipient groups for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:372
+#: aleksis/core/preferences.py:388
 msgid "Show dashboard to users without login"
 msgstr ""
 
-#: aleksis/core/preferences.py:381
+#: aleksis/core/preferences.py:397
 msgid "Allow users to edit their dashboard"
 msgstr ""
 
-#: aleksis/core/preferences.py:392
+#: aleksis/core/preferences.py:408
 msgid "Fields on person model which are editable by themselves."
 msgstr ""
 
-#: aleksis/core/preferences.py:407
+#: aleksis/core/preferences.py:423
 msgid "Editable fields on person model which should trigger a notification on change"
 msgstr ""
 
-#: aleksis/core/preferences.py:421
+#: aleksis/core/preferences.py:437
 msgid "Contact for notification if a person changes their data"
 msgstr ""
 
-#: aleksis/core/preferences.py:432
+#: aleksis/core/preferences.py:448
+msgid "Prefer personal photos over avatars"
+msgstr ""
+
+#: aleksis/core/preferences.py:458
 msgid "PDF file expiration duration"
 msgstr ""
 
-#: aleksis/core/preferences.py:433
+#: aleksis/core/preferences.py:459
 msgid "in minutes"
 msgstr ""
 
-#: aleksis/core/preferences.py:443
+#: aleksis/core/preferences.py:469
 msgid "Automatically update the dashboard and its widgets"
 msgstr ""
 
-#: aleksis/core/preferences.py:453
+#: aleksis/core/preferences.py:479
 msgid "Automatically update the dashboard and its widgets sitewide"
 msgstr ""
 
-#: aleksis/core/preferences.py:463
+#: aleksis/core/preferences.py:489
 msgid "Country for phone number parsing"
 msgstr ""
 
-#: aleksis/core/settings.py:529
+#: aleksis/core/settings.py:540
 msgid "English"
 msgstr ""
 
-#: aleksis/core/settings.py:530
+#: aleksis/core/settings.py:541
 msgid "German"
 msgstr ""
 
@@ -1170,7 +1210,7 @@ msgstr ""
 #: aleksis/core/templates/core/announcement/list.html:36
 #: aleksis/core/templates/core/group/full.html:24
 #: aleksis/core/templates/core/person/full.html:26
-#: aleksis/core/templates/core/person/full.html:98
+#: aleksis/core/templates/core/person/full.html:86
 #: aleksis/core/templates/oauth2_provider/application/detail.html:17
 msgid "Edit"
 msgstr ""
@@ -1188,7 +1228,7 @@ msgstr ""
 #: aleksis/core/templates/core/group/full.html:31
 #: aleksis/core/templates/core/pages/delete.html:22
 #: aleksis/core/templates/core/person/full.html:33
-#: aleksis/core/templates/core/person/full.html:105
+#: aleksis/core/templates/core/person/full.html:93
 #: aleksis/core/templates/oauth2_provider/application/detail.html:21
 msgid "Delete"
 msgstr ""
@@ -1235,6 +1275,10 @@ msgid ""
 "          "
 msgstr ""
 
+#: aleksis/core/templates/500.html:21
+msgid "Retry"
+msgstr ""
+
 #: aleksis/core/templates/503.html:10
 msgid ""
 "The maintenance mode is currently enabled. Please try again\n"
@@ -1493,19 +1537,15 @@ msgstr ""
 msgid "There are no announcements."
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:78
-msgid "Logged in as"
-msgstr ""
-
-#: aleksis/core/templates/core/base.html:179
+#: aleksis/core/templates/core/base.html:213
 msgid "About AlekSIS® — The Free School Information System"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:187
+#: aleksis/core/templates/core/base.html:221
 msgid "Imprint"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:195
+#: aleksis/core/templates/core/base.html:229
 msgid "Privacy Policy"
 msgstr ""
 
@@ -1726,7 +1766,7 @@ msgstr ""
 
 #: aleksis/core/templates/core/group/full.html:38
 #: aleksis/core/templates/core/person/full.html:40
-#: aleksis/core/templates/core/person/full.html:112
+#: aleksis/core/templates/core/person/full.html:100
 msgid "Change preferences"
 msgstr ""
 
@@ -1916,83 +1956,83 @@ msgstr ""
 msgid "System checks"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:21
+#: aleksis/core/templates/core/pages/system_status.html:22
 msgid "Maintenance mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:23
+#: aleksis/core/templates/core/pages/system_status.html:24
 msgid ""
 "\n"
 "                Only admin and visitors from internal IPs can access thesite.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:34
+#: aleksis/core/templates/core/pages/system_status.html:36
 msgid "Maintenance mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:35
+#: aleksis/core/templates/core/pages/system_status.html:37
 msgid "Everyone can access the site."
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:45
+#: aleksis/core/templates/core/pages/system_status.html:47
 msgid "Debug mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:47
+#: aleksis/core/templates/core/pages/system_status.html:49
 msgid ""
 "\n"
 "                The web server throws back debug information on errors. Do not use in production!\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:54
+#: aleksis/core/templates/core/pages/system_status.html:56
 msgid "Debug mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:56
+#: aleksis/core/templates/core/pages/system_status.html:58
 msgid ""
 "\n"
 "                Debug mode is disabled. Default error pages are displayed on errors.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:69
+#: aleksis/core/templates/core/pages/system_status.html:71
 msgid "System health checks"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:75
+#: aleksis/core/templates/core/pages/system_status.html:77
 msgid "Service"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:76
-#: aleksis/core/templates/core/pages/system_status.html:115
+#: aleksis/core/templates/core/pages/system_status.html:78
+#: aleksis/core/templates/core/pages/system_status.html:119
 msgid "Status"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:77
+#: aleksis/core/templates/core/pages/system_status.html:79
 msgid "Time taken"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:96
+#: aleksis/core/templates/core/pages/system_status.html:100
 msgid "seconds"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:107
+#: aleksis/core/templates/core/pages/system_status.html:111
 msgid "Celery task results"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:112
+#: aleksis/core/templates/core/pages/system_status.html:116
 #: aleksis/core/templates/templated_email/celery_failure.email:9
 #: aleksis/core/templates/templated_email/celery_failure.email:28
 msgid "Task"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:113
+#: aleksis/core/templates/core/pages/system_status.html:117
 msgid "ID"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:114
+#: aleksis/core/templates/core/pages/system_status.html:118
 msgid "Date done"
 msgstr ""
 
@@ -2033,6 +2073,18 @@ msgid ""
 "            "
 msgstr ""
 
+#: aleksis/core/templates/core/partials/avatar_content.html:14
+#: aleksis/core/templates/core/partials/avatar_content.html:15
+#: aleksis/core/templates/core/person/full.html:213
+#: aleksis/core/templates/core/person/full.html:214
+msgid "Avatar"
+msgstr ""
+
+#: aleksis/core/templates/core/partials/avatar_content.html:19
+#: aleksis/core/templates/core/partials/avatar_content.html:20
+msgid "Identicon"
+msgstr ""
+
 #: aleksis/core/templates/core/partials/crud_events.html:15
 msgid "Changed by"
 msgstr ""
@@ -2123,24 +2175,24 @@ msgid "Edit person"
 msgstr ""
 
 #: aleksis/core/templates/core/person/full.html:47
-#: aleksis/core/templates/core/person/full.html:119
+#: aleksis/core/templates/core/person/full.html:107
 msgid "Impersonate"
 msgstr ""
 
 #: aleksis/core/templates/core/person/full.html:54
-#: aleksis/core/templates/core/person/full.html:126
+#: aleksis/core/templates/core/person/full.html:114
 msgid "Invite user"
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:133
+#: aleksis/core/templates/core/person/full.html:121
 msgid "Contact details"
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:224
+#: aleksis/core/templates/core/person/full.html:220
 msgid "This person didn't upload a personal photo."
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:232
+#: aleksis/core/templates/core/person/full.html:228
 msgid "Children"
 msgstr ""
 
@@ -2345,13 +2397,11 @@ msgstr ""
 msgid "Network error"
 msgstr ""
 
-#: aleksis/core/templates/offline.html:8
-msgid ""
-"No internet\n"
-"    connection."
+#: aleksis/core/templates/offline.html:10
+msgid "No internet connection."
 msgstr ""
 
-#: aleksis/core/templates/offline.html:12
+#: aleksis/core/templates/offline.html:14
 msgid ""
 "\n"
 "      There was an error accessing this page. You probably don't have an internet connection. Check to see if your WiFi\n"
@@ -2595,7 +2645,7 @@ msgstr ""
 #: aleksis/core/templates/two_factor/_base_focus.html:6
 #: aleksis/core/templates/two_factor/core/otp_required.html:22
 #: aleksis/core/templates/two_factor/core/setup.html:5
-#: aleksis/core/templates/two_factor/profile/profile.html:87
+#: aleksis/core/templates/two_factor/profile/profile.html:88
 msgid "Enable Two-Factor Authentication"
 msgstr ""
 
@@ -2699,15 +2749,15 @@ msgstr ""
 msgid "Or, alternatively, use one of your backup phones:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:121
+#: aleksis/core/templates/two_factor/core/login.html:122
 msgid "As a last resort, you can use a backup token:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:124
+#: aleksis/core/templates/two_factor/core/login.html:125
 msgid "Use Backup Token"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:135
+#: aleksis/core/templates/two_factor/core/login.html:136
 msgid "Use alternative login options"
 msgstr ""
 
@@ -2944,11 +2994,11 @@ msgid ""
 "      "
 msgstr ""
 
-#: aleksis/core/util/notifications.py:63
+#: aleksis/core/util/notifications.py:64
 msgid "E-Mail"
 msgstr ""
 
-#: aleksis/core/util/notifications.py:64
+#: aleksis/core/util/notifications.py:65
 msgid "SMS"
 msgstr ""
 
@@ -2972,157 +3022,157 @@ msgstr ""
 msgid "Download PDF"
 msgstr ""
 
-#: aleksis/core/views.py:285
+#: aleksis/core/views.py:289
 msgid "The school term has been created."
 msgstr ""
 
-#: aleksis/core/views.py:297
+#: aleksis/core/views.py:301
 msgid "The school term has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:417
+#: aleksis/core/views.py:421
 msgid "The child groups were successfully saved."
 msgstr ""
 
-#: aleksis/core/views.py:436 aleksis/core/views.py:446
+#: aleksis/core/views.py:440 aleksis/core/views.py:450
 msgid "The person has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:496
+#: aleksis/core/views.py:500
 msgid "The group has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:593
+#: aleksis/core/views.py:597
 msgid "The announcement has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:609
+#: aleksis/core/views.py:613
 msgid "The announcement has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:677
+#: aleksis/core/views.py:681
 msgid "The requested preference registry does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:696
+#: aleksis/core/views.py:700
 msgid "The preferences have been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:720
+#: aleksis/core/views.py:724
 msgid "The person has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:734
+#: aleksis/core/views.py:738
 msgid "The group has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:766
+#: aleksis/core/views.py:770
 msgid "The additional_field has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:800
+#: aleksis/core/views.py:804
 msgid "The additional field has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:825
+#: aleksis/core/views.py:829
 msgid "The group type has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:855
+#: aleksis/core/views.py:859
 msgid "The group type has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:888
+#: aleksis/core/views.py:892
 msgid "Progress: Run data checks"
 msgstr ""
 
-#: aleksis/core/views.py:889
+#: aleksis/core/views.py:893
 msgid "Run data checks …"
 msgstr ""
 
-#: aleksis/core/views.py:890
+#: aleksis/core/views.py:894
 msgid "The data checks were run successfully."
 msgstr ""
 
-#: aleksis/core/views.py:891
+#: aleksis/core/views.py:895
 msgid "There was a problem while running data checks."
 msgstr ""
 
-#: aleksis/core/views.py:907
+#: aleksis/core/views.py:911
 #, python-brace-format
 msgid "The solve option '{solve_option_obj.verbose_name}' "
 msgstr ""
 
-#: aleksis/core/views.py:917
+#: aleksis/core/views.py:921
 msgid "The requested solve option does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:949
+#: aleksis/core/views.py:953
 msgid "The dashboard widget has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:979
+#: aleksis/core/views.py:983
 msgid "The dashboard widget has been created."
 msgstr ""
 
-#: aleksis/core/views.py:989
+#: aleksis/core/views.py:993
 msgid "The dashboard widget has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1060
+#: aleksis/core/views.py:1064
 msgid "Your dashboard configuration has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1062
+#: aleksis/core/views.py:1066
 msgid "The configuration of the default dashboard has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1138
+#: aleksis/core/views.py:1142
 #, python-brace-format
 msgid "The invitation was successfully created. The invitation code is {code}"
 msgstr ""
 
-#: aleksis/core/views.py:1229
+#: aleksis/core/views.py:1233
 msgid "We have successfully assigned the permissions."
 msgstr ""
 
-#: aleksis/core/views.py:1239
+#: aleksis/core/views.py:1243
 msgid "The global user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1249
+#: aleksis/core/views.py:1253
 msgid "The global group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1259
+#: aleksis/core/views.py:1263
 msgid "The object user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1269
+#: aleksis/core/views.py:1273
 msgid "The object group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1337
+#: aleksis/core/views.py:1341
 msgid "The requested PDF file does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:1346 aleksis/core/views.py:1350
+#: aleksis/core/views.py:1350 aleksis/core/views.py:1354
 msgid "The requested task does not exist or is not accessible"
 msgstr ""
 
-#: aleksis/core/views.py:1388
+#: aleksis/core/views.py:1406
 msgid "The third-party account could not be disconnected because it is the only login method available."
 msgstr ""
 
-#: aleksis/core/views.py:1395
+#: aleksis/core/views.py:1413
 msgid "The third-party account has been successfully disconnected."
 msgstr ""
 
-#: aleksis/core/views.py:1466
+#: aleksis/core/views.py:1484
 msgid "Person was invited successfully and an email with further instructions has been send to them."
 msgstr ""
 
-#: aleksis/core/views.py:1477
+#: aleksis/core/views.py:1495
 msgid "Person was already invited."
 msgstr ""
 
diff --git a/aleksis/core/locale/nb_NO/LC_MESSAGES/djangojs.po b/aleksis/core/locale/nb_NO/LC_MESSAGES/djangojs.po
index dfec73c5c894d3d686092593be9c21a811cd0535..f3df4c3fc6d2848a739ddd1faac44cb9048c97b2 100644
--- a/aleksis/core/locale/nb_NO/LC_MESSAGES/djangojs.po
+++ b/aleksis/core/locale/nb_NO/LC_MESSAGES/djangojs.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:20+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,6 +29,6 @@ msgstr ""
 msgid "OK"
 msgstr ""
 
-#: aleksis/core/static/js/main.js:191
+#: aleksis/core/static/js/main.js:195
 msgid "This page may contain outdated information since there is no internet connection."
 msgstr ""
diff --git a/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po b/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po
index 2973720cd0f3f85a06ca9d68c21a2c09a92961a5..d5a2d5f0647e2460b313fb20d6cca3d9059a3d8b 100644
--- a/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: AlekSIS (School Information System) 0.1\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:19+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -37,10 +37,10 @@ msgstr ""
 msgid "Home and mobile phone"
 msgstr ""
 
-#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:265
-#: aleksis/core/models.py:462 aleksis/core/templates/core/group/list.html:8
+#: aleksis/core/apps.py:171 aleksis/core/forms.py:220 aleksis/core/menus.py:185
+#: aleksis/core/models.py:487 aleksis/core/templates/core/group/list.html:8
 #: aleksis/core/templates/core/group/list.html:9
-#: aleksis/core/templates/core/person/full.html:250
+#: aleksis/core/templates/core/person/full.html:246
 msgid "Groups"
 msgstr ""
 
@@ -65,8 +65,8 @@ msgstr ""
 msgid "The DashboardWidget was reported broken automatically."
 msgstr ""
 
-#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:105
-#: aleksis/core/templates/core/base.html:106
+#: aleksis/core/filters.py:42 aleksis/core/templates/core/base.html:139
+#: aleksis/core/templates/core/base.html:140
 #: aleksis/core/templates/core/group/list.html:20
 #: aleksis/core/templates/core/person/list.html:24
 #: aleksis/core/templates/search/search.html:7
@@ -90,11 +90,11 @@ msgstr ""
 msgid "Content type"
 msgstr ""
 
-#: aleksis/core/filters.py:113 aleksis/core/models.py:688
+#: aleksis/core/filters.py:113 aleksis/core/models.py:713
 msgid "User"
 msgstr ""
 
-#: aleksis/core/filters.py:135 aleksis/core/models.py:461
+#: aleksis/core/filters.py:135 aleksis/core/models.py:486
 msgid "Group"
 msgstr ""
 
@@ -130,7 +130,7 @@ msgstr ""
 msgid "This username is already in use."
 msgstr ""
 
-#: aleksis/core/forms.py:153 aleksis/core/models.py:130
+#: aleksis/core/forms.py:153 aleksis/core/models.py:134
 msgid "School term"
 msgstr ""
 
@@ -139,7 +139,7 @@ msgid "Common data"
 msgstr ""
 
 #: aleksis/core/forms.py:155 aleksis/core/forms.py:207
-#: aleksis/core/menus.py:254 aleksis/core/models.py:153
+#: aleksis/core/menus.py:174 aleksis/core/models.py:157
 #: aleksis/core/templates/core/person/list.html:8
 #: aleksis/core/templates/core/person/list.html:9
 msgid "Persons"
@@ -149,18 +149,18 @@ msgstr ""
 msgid "Additional data"
 msgstr ""
 
-#: aleksis/core/forms.py:157 aleksis/core/models.py:206
-#: aleksis/core/models.py:514
+#: aleksis/core/forms.py:157 aleksis/core/models.py:210
+#: aleksis/core/models.py:539
 msgid "Photo"
 msgstr ""
 
 #: aleksis/core/forms.py:199 aleksis/core/forms.py:202
-#: aleksis/core/models.py:73
+#: aleksis/core/models.py:77
 msgid "Date"
 msgstr ""
 
 #: aleksis/core/forms.py:200 aleksis/core/forms.py:203
-#: aleksis/core/models.py:81
+#: aleksis/core/models.py:85
 msgid "Time"
 msgstr ""
 
@@ -196,11 +196,11 @@ msgstr ""
 msgid "Please enter your invitation code."
 msgstr ""
 
-#: aleksis/core/forms.py:418 aleksis/core/models.py:181
+#: aleksis/core/forms.py:418 aleksis/core/models.py:185
 msgid "First name"
 msgstr ""
 
-#: aleksis/core/forms.py:419 aleksis/core/models.py:182
+#: aleksis/core/forms.py:419 aleksis/core/models.py:186
 msgid "Last name"
 msgstr ""
 
@@ -248,7 +248,15 @@ msgstr ""
 msgid "Password (again)"
 msgstr ""
 
-#: aleksis/core/forms.py:775
+#: aleksis/core/forms.py:728
+msgid "The selected action does not exist."
+msgstr ""
+
+#: aleksis/core/forms.py:739
+msgid "You do not have permission to run {} on all selected objects."
+msgstr ""
+
+#: aleksis/core/forms.py:795
 msgid "No valid selection."
 msgstr ""
 
@@ -291,678 +299,698 @@ msgstr ""
 msgid "Dashboard"
 msgstr ""
 
-#: aleksis/core/menus.py:41 aleksis/core/models.py:734
-#: aleksis/core/preferences.py:29
+#: aleksis/core/menus.py:41 aleksis/core/models.py:765
+#: aleksis/core/preferences.py:29 aleksis/core/templates/core/base.html:81
 #: aleksis/core/templates/core/notifications.html:4
 #: aleksis/core/templates/core/notifications.html:5
 msgid "Notifications"
 msgstr ""
 
 #: aleksis/core/menus.py:53
-msgid "Account"
-msgstr ""
-
-#: aleksis/core/menus.py:60
-msgid "Stop impersonation"
-msgstr ""
-
-#: aleksis/core/menus.py:69 aleksis/core/templates/core/base.html:80
-msgid "Logout"
-msgstr ""
-
-#: aleksis/core/menus.py:75
-msgid "2FA"
-msgstr ""
-
-#: aleksis/core/menus.py:83
-#: aleksis/core/templates/account/password_change.html:5
-#: aleksis/core/templates/account/password_change.html:6
-#: aleksis/core/templates/account/password_change.html:19
-#: aleksis/core/templates/account/password_reset_from_key.html:5
-#: aleksis/core/templates/account/password_reset_from_key.html:42
-#: aleksis/core/templates/account/password_reset_from_key.html:46
-#: aleksis/core/templates/account/password_reset_from_key_done.html:5
-#: aleksis/core/templates/account/password_reset_from_key_done.html:6
-msgid "Change password"
-msgstr ""
-
-#: aleksis/core/menus.py:95
-msgid "Me"
-msgstr ""
-
-#: aleksis/core/menus.py:104
-#: aleksis/core/templates/dynamic_preferences/form.html:5
-msgid "Preferences"
-msgstr ""
-
-#: aleksis/core/menus.py:113
-msgid "Third-party accounts"
-msgstr ""
-
-#: aleksis/core/menus.py:122
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
-#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
-msgid "Authorized applications"
-msgstr ""
-
-#: aleksis/core/menus.py:133
 msgid "Admin"
 msgstr ""
 
-#: aleksis/core/menus.py:141 aleksis/core/models.py:834
+#: aleksis/core/menus.py:61 aleksis/core/models.py:865
 #: aleksis/core/templates/core/announcement/list.html:7
 #: aleksis/core/templates/core/announcement/list.html:8
 msgid "Announcements"
 msgstr ""
 
-#: aleksis/core/menus.py:152 aleksis/core/models.py:131
+#: aleksis/core/menus.py:72 aleksis/core/models.py:135
 #: aleksis/core/templates/core/school_term/list.html:8
 #: aleksis/core/templates/core/school_term/list.html:9
 msgid "School terms"
 msgstr ""
 
-#: aleksis/core/menus.py:163
+#: aleksis/core/menus.py:83
 #: aleksis/core/templates/core/dashboard_widget/list.html:8
 #: aleksis/core/templates/core/dashboard_widget/list.html:9
 msgid "Dashboard widgets"
 msgstr ""
 
-#: aleksis/core/menus.py:174
+#: aleksis/core/menus.py:94
 #: aleksis/core/templates/core/management/data_management.html:6
 #: aleksis/core/templates/core/management/data_management.html:7
 msgid "Data management"
 msgstr ""
 
-#: aleksis/core/menus.py:185
+#: aleksis/core/menus.py:105
 #: aleksis/core/templates/core/pages/system_status.html:5
 #: aleksis/core/templates/core/pages/system_status.html:7
 msgid "System status"
 msgstr ""
 
-#: aleksis/core/menus.py:196
+#: aleksis/core/menus.py:116
 msgid "Configuration"
 msgstr ""
 
-#: aleksis/core/menus.py:207 aleksis/core/templates/core/data_check/list.html:9
+#: aleksis/core/menus.py:127 aleksis/core/templates/core/data_check/list.html:9
 #: aleksis/core/templates/core/data_check/list.html:10
 msgid "Data checks"
 msgstr ""
 
-#: aleksis/core/menus.py:213 aleksis/core/templates/core/perms/list.html:13
+#: aleksis/core/menus.py:133 aleksis/core/templates/core/perms/list.html:13
 #: aleksis/core/templates/core/perms/list.html:14
 msgid "Manage permissions"
 msgstr ""
 
-#: aleksis/core/menus.py:224
+#: aleksis/core/menus.py:144
 msgid "Backend Admin"
 msgstr ""
 
-#: aleksis/core/menus.py:232
+#: aleksis/core/menus.py:152
 #: aleksis/core/templates/oauth2_provider/application/list.html:5
 #: aleksis/core/templates/oauth2_provider/application/list.html:6
 msgid "OAuth2 Applications"
 msgstr ""
 
-#: aleksis/core/menus.py:245
+#: aleksis/core/menus.py:165
 msgid "People"
 msgstr ""
 
-#: aleksis/core/menus.py:276 aleksis/core/models.py:1055
+#: aleksis/core/menus.py:196 aleksis/core/models.py:1099
 #: aleksis/core/templates/core/group_type/list.html:8
 #: aleksis/core/templates/core/group_type/list.html:9
 msgid "Group types"
 msgstr ""
 
-#: aleksis/core/menus.py:287
+#: aleksis/core/menus.py:207
 msgid "Groups and child groups"
 msgstr ""
 
-#: aleksis/core/menus.py:298 aleksis/core/models.py:510
+#: aleksis/core/menus.py:218 aleksis/core/models.py:535
 #: aleksis/core/templates/core/additional_field/list.html:8
 #: aleksis/core/templates/core/additional_field/list.html:9
 msgid "Additional fields"
 msgstr ""
 
-#: aleksis/core/menus.py:309
+#: aleksis/core/menus.py:229
 msgid "Invite person"
 msgstr ""
 
-#: aleksis/core/menus.py:322
+#: aleksis/core/menus.py:242
 #: aleksis/core/templates/core/group/child_groups.html:7
 #: aleksis/core/templates/core/group/child_groups.html:9
 msgid "Assign child groups to groups"
 msgstr ""
 
+#: aleksis/core/menus.py:254
+msgid "Stop impersonation"
+msgstr ""
+
+#: aleksis/core/menus.py:263
+msgid "Account"
+msgstr ""
+
+#: aleksis/core/menus.py:272
+#: aleksis/core/templates/dynamic_preferences/form.html:5
+msgid "Preferences"
+msgstr ""
+
+#: aleksis/core/menus.py:281
+msgid "2FA"
+msgstr ""
+
+#: aleksis/core/menus.py:289
+#: aleksis/core/templates/account/password_change.html:5
+#: aleksis/core/templates/account/password_change.html:6
+#: aleksis/core/templates/account/password_change.html:19
+#: aleksis/core/templates/account/password_reset_from_key.html:5
+#: aleksis/core/templates/account/password_reset_from_key.html:42
+#: aleksis/core/templates/account/password_reset_from_key.html:46
+#: aleksis/core/templates/account/password_reset_from_key_done.html:5
+#: aleksis/core/templates/account/password_reset_from_key_done.html:6
+msgid "Change password"
+msgstr ""
+
+#: aleksis/core/menus.py:301
+msgid "Third-party accounts"
+msgstr ""
+
+#: aleksis/core/menus.py:310
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:5
+#: aleksis/core/templates/oauth2_provider/authorized-tokens.html:6
+msgid "Authorized applications"
+msgstr ""
+
+#: aleksis/core/menus.py:320
+msgid "Logout"
+msgstr ""
+
 #: aleksis/core/mixins.py:511
 msgid "Linked school term"
 msgstr ""
 
-#: aleksis/core/models.py:71
+#: aleksis/core/models.py:75
 msgid "Boolean (Yes/No)"
 msgstr ""
 
-#: aleksis/core/models.py:72
+#: aleksis/core/models.py:76
 msgid "Text (one line)"
 msgstr ""
 
-#: aleksis/core/models.py:74
+#: aleksis/core/models.py:78
 msgid "Date and time"
 msgstr ""
 
-#: aleksis/core/models.py:75
+#: aleksis/core/models.py:79
 msgid "Decimal number"
 msgstr ""
 
-#: aleksis/core/models.py:76 aleksis/core/models.py:199
+#: aleksis/core/models.py:80 aleksis/core/models.py:203
 msgid "E-mail address"
 msgstr ""
 
-#: aleksis/core/models.py:77
+#: aleksis/core/models.py:81
 msgid "Integer"
 msgstr ""
 
-#: aleksis/core/models.py:78
+#: aleksis/core/models.py:82
 msgid "IP address"
 msgstr ""
 
-#: aleksis/core/models.py:79
+#: aleksis/core/models.py:83
 msgid "Boolean or empty (Yes/No/Neither)"
 msgstr ""
 
-#: aleksis/core/models.py:80
+#: aleksis/core/models.py:84
 msgid "Text (multi-line)"
 msgstr ""
 
-#: aleksis/core/models.py:82
+#: aleksis/core/models.py:86
 msgid "URL / Link"
 msgstr ""
 
-#: aleksis/core/models.py:94 aleksis/core/models.py:1024
+#: aleksis/core/models.py:98 aleksis/core/models.py:1068
 msgid "Name"
 msgstr ""
 
-#: aleksis/core/models.py:96
+#: aleksis/core/models.py:100
 msgid "Start date"
 msgstr ""
 
-#: aleksis/core/models.py:97
+#: aleksis/core/models.py:101
 msgid "End date"
 msgstr ""
 
-#: aleksis/core/models.py:116
+#: aleksis/core/models.py:120
 msgid "The start date must be earlier than the end date."
 msgstr ""
 
-#: aleksis/core/models.py:123
+#: aleksis/core/models.py:127
 msgid "There is already a school term for this time or a part of this time."
 msgstr ""
 
-#: aleksis/core/models.py:152 aleksis/core/models.py:973
+#: aleksis/core/models.py:156 aleksis/core/models.py:1017
 msgid "Person"
 msgstr ""
 
-#: aleksis/core/models.py:155
+#: aleksis/core/models.py:159
 msgid "Can view address"
 msgstr ""
 
-#: aleksis/core/models.py:156
+#: aleksis/core/models.py:160
 msgid "Can view contact details"
 msgstr ""
 
-#: aleksis/core/models.py:157
+#: aleksis/core/models.py:161
 msgid "Can view photo"
 msgstr ""
 
-#: aleksis/core/models.py:158
+#: aleksis/core/models.py:162
 msgid "Can view avatar image"
 msgstr ""
 
-#: aleksis/core/models.py:159
+#: aleksis/core/models.py:163
 msgid "Can view persons groups"
 msgstr ""
 
-#: aleksis/core/models.py:160
+#: aleksis/core/models.py:164
 msgid "Can view personal details"
 msgstr ""
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "female"
 msgstr ""
 
-#: aleksis/core/models.py:170
+#: aleksis/core/models.py:174
 msgid "male"
 msgstr ""
 
-#: aleksis/core/models.py:178 aleksis/core/models.py:1227
+#: aleksis/core/models.py:182 aleksis/core/models.py:1271
 msgid "Linked user"
 msgstr ""
 
-#: aleksis/core/models.py:184
+#: aleksis/core/models.py:188
 msgid "Additional name(s)"
 msgstr ""
 
-#: aleksis/core/models.py:188 aleksis/core/models.py:479
+#: aleksis/core/models.py:192 aleksis/core/models.py:504
 msgid "Short name"
 msgstr ""
 
-#: aleksis/core/models.py:191
+#: aleksis/core/models.py:195
 msgid "Street"
 msgstr ""
 
-#: aleksis/core/models.py:192
+#: aleksis/core/models.py:196
 msgid "Street number"
 msgstr ""
 
-#: aleksis/core/models.py:193
+#: aleksis/core/models.py:197
 msgid "Postal code"
 msgstr ""
 
-#: aleksis/core/models.py:194
+#: aleksis/core/models.py:198
 msgid "Place"
 msgstr ""
 
-#: aleksis/core/models.py:196 aleksis/core/templates/core/person/full.html:172
+#: aleksis/core/models.py:200 aleksis/core/templates/core/person/full.html:160
 msgid "Home phone"
 msgstr ""
 
-#: aleksis/core/models.py:197 aleksis/core/templates/core/person/full.html:182
+#: aleksis/core/models.py:201 aleksis/core/templates/core/person/full.html:170
 msgid "Mobile phone"
 msgstr ""
 
-#: aleksis/core/models.py:201
+#: aleksis/core/models.py:205
 msgid "Date of birth"
 msgstr ""
 
-#: aleksis/core/models.py:202
+#: aleksis/core/models.py:206
 msgid "Place of birth"
 msgstr ""
 
-#: aleksis/core/models.py:203
+#: aleksis/core/models.py:207
 msgid "Sex"
 msgstr ""
 
-#: aleksis/core/models.py:210 aleksis/core/models.py:518
+#: aleksis/core/models.py:214 aleksis/core/models.py:543
 msgid "This is an official photo, used for official documents and for internal use cases."
 msgstr ""
 
-#: aleksis/core/models.py:215 aleksis/core/models.py:522
+#: aleksis/core/models.py:219 aleksis/core/models.py:547
 msgid "Display picture / Avatar"
 msgstr ""
 
-#: aleksis/core/models.py:218 aleksis/core/models.py:525
+#: aleksis/core/models.py:222 aleksis/core/models.py:550
 msgid "This is a picture or an avatar for public display."
 msgstr ""
 
-#: aleksis/core/models.py:223 aleksis/core/templates/core/person/full.html:239
+#: aleksis/core/models.py:227 aleksis/core/templates/core/person/full.html:235
 msgid "Guardians / Parents"
 msgstr ""
 
-#: aleksis/core/models.py:230
+#: aleksis/core/models.py:234
 msgid "Primary group"
 msgstr ""
 
-#: aleksis/core/models.py:233 aleksis/core/models.py:692
-#: aleksis/core/models.py:716 aleksis/core/models.py:801
-#: aleksis/core/models.py:1048
+#: aleksis/core/models.py:237 aleksis/core/models.py:717
+#: aleksis/core/models.py:741 aleksis/core/models.py:832
+#: aleksis/core/models.py:1092
 msgid "Description"
 msgstr ""
 
-#: aleksis/core/models.py:434
+#: aleksis/core/models.py:457
 msgid "Title of field"
 msgstr ""
 
-#: aleksis/core/models.py:436
+#: aleksis/core/models.py:459
 msgid "Type of field"
 msgstr ""
 
-#: aleksis/core/models.py:443
+#: aleksis/core/models.py:461
+msgid "Required"
+msgstr ""
+
+#: aleksis/core/models.py:462
+msgid "Help text / description"
+msgstr ""
+
+#: aleksis/core/models.py:468
 msgid "Addtitional field for groups"
 msgstr ""
 
-#: aleksis/core/models.py:444
+#: aleksis/core/models.py:469
 msgid "Addtitional fields for groups"
 msgstr ""
 
-#: aleksis/core/models.py:464
+#: aleksis/core/models.py:489
 msgid "Can assign child groups to groups"
 msgstr ""
 
-#: aleksis/core/models.py:465
+#: aleksis/core/models.py:490
 msgid "Can view statistics about group."
 msgstr ""
 
-#: aleksis/core/models.py:477
+#: aleksis/core/models.py:502
 msgid "Long name"
 msgstr ""
 
-#: aleksis/core/models.py:487 aleksis/core/templates/core/group/full.html:85
+#: aleksis/core/models.py:512 aleksis/core/templates/core/group/full.html:85
 msgid "Members"
 msgstr ""
 
-#: aleksis/core/models.py:490 aleksis/core/templates/core/group/full.html:82
+#: aleksis/core/models.py:515 aleksis/core/templates/core/group/full.html:82
 msgid "Owners"
 msgstr ""
 
-#: aleksis/core/models.py:497 aleksis/core/templates/core/group/full.html:55
+#: aleksis/core/models.py:522 aleksis/core/templates/core/group/full.html:55
 msgid "Parent groups"
 msgstr ""
 
-#: aleksis/core/models.py:505
+#: aleksis/core/models.py:530
 msgid "Type of group"
 msgstr ""
 
-#: aleksis/core/models.py:691 aleksis/core/models.py:715
-#: aleksis/core/models.py:800
+#: aleksis/core/models.py:716 aleksis/core/models.py:740
+#: aleksis/core/models.py:831
 #: aleksis/core/templates/core/announcement/list.html:18
 msgid "Title"
 msgstr ""
 
-#: aleksis/core/models.py:694
+#: aleksis/core/models.py:719
 msgid "Application"
 msgstr ""
 
-#: aleksis/core/models.py:700
+#: aleksis/core/models.py:725
 msgid "Activity"
 msgstr ""
 
-#: aleksis/core/models.py:701
+#: aleksis/core/models.py:726
 msgid "Activities"
 msgstr ""
 
-#: aleksis/core/models.py:707
+#: aleksis/core/models.py:732
 msgid "Sender"
 msgstr ""
 
-#: aleksis/core/models.py:712
+#: aleksis/core/models.py:737
 msgid "Recipient"
 msgstr ""
 
-#: aleksis/core/models.py:717 aleksis/core/models.py:1025
+#: aleksis/core/models.py:742 aleksis/core/models.py:1069
 msgid "Link"
 msgstr ""
 
-#: aleksis/core/models.py:719
+#: aleksis/core/models.py:744
+msgid "Send notification at"
+msgstr ""
+
+#: aleksis/core/models.py:746
 msgid "Read"
 msgstr ""
 
-#: aleksis/core/models.py:720
+#: aleksis/core/models.py:747
 msgid "Sent"
 msgstr ""
 
-#: aleksis/core/models.py:733
+#: aleksis/core/models.py:764
 msgid "Notification"
 msgstr ""
 
-#: aleksis/core/models.py:802
+#: aleksis/core/models.py:833
 msgid "Link to detailed view"
 msgstr ""
 
-#: aleksis/core/models.py:805
+#: aleksis/core/models.py:836
 msgid "Date and time from when to show"
 msgstr ""
 
-#: aleksis/core/models.py:808
+#: aleksis/core/models.py:839
 msgid "Date and time until when to show"
 msgstr ""
 
-#: aleksis/core/models.py:833
+#: aleksis/core/models.py:864
 msgid "Announcement"
 msgstr ""
 
-#: aleksis/core/models.py:871
+#: aleksis/core/models.py:902
 msgid "Announcement recipient"
 msgstr ""
 
-#: aleksis/core/models.py:872
+#: aleksis/core/models.py:903
 msgid "Announcement recipients"
 msgstr ""
 
-#: aleksis/core/models.py:894
+#: aleksis/core/models.py:925
 msgid "Widget Title"
 msgstr ""
 
-#: aleksis/core/models.py:895
+#: aleksis/core/models.py:926
 msgid "Activate Widget"
 msgstr ""
 
-#: aleksis/core/models.py:896
+#: aleksis/core/models.py:927
 msgid "Widget is broken"
 msgstr ""
 
-#: aleksis/core/models.py:899
+#: aleksis/core/models.py:930
 msgid "Size on mobile devices"
 msgstr ""
 
-#: aleksis/core/models.py:900
+#: aleksis/core/models.py:931
 msgid "<= 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:905
+#: aleksis/core/models.py:936
 msgid "Size on tablet devices"
 msgstr ""
 
-#: aleksis/core/models.py:906
+#: aleksis/core/models.py:937
 msgid "> 600 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:911
+#: aleksis/core/models.py:942
 msgid "Size on desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:912
+#: aleksis/core/models.py:943
 msgid "> 992 px, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:917
+#: aleksis/core/models.py:948
 msgid "Size on large desktop devices"
 msgstr ""
 
-#: aleksis/core/models.py:918
+#: aleksis/core/models.py:949
 msgid "> 1200 px>, 12 columns"
 msgstr ""
 
-#: aleksis/core/models.py:949
+#: aleksis/core/models.py:980
 msgid "Can edit default dashboard"
 msgstr ""
 
-#: aleksis/core/models.py:950
+#: aleksis/core/models.py:981
 msgid "Dashboard Widget"
 msgstr ""
 
-#: aleksis/core/models.py:951
+#: aleksis/core/models.py:982
 msgid "Dashboard Widgets"
 msgstr ""
 
-#: aleksis/core/models.py:957
+#: aleksis/core/models.py:988
 msgid "URL"
 msgstr ""
 
-#: aleksis/core/models.py:958
+#: aleksis/core/models.py:989
 msgid "Icon URL"
 msgstr ""
 
-#: aleksis/core/models.py:964
+#: aleksis/core/models.py:995
 msgid "External link widget"
 msgstr ""
 
-#: aleksis/core/models.py:965
+#: aleksis/core/models.py:996
 msgid "External link widgets"
 msgstr ""
 
-#: aleksis/core/models.py:970
+#: aleksis/core/models.py:1002
+msgid "Content"
+msgstr ""
+
+#: aleksis/core/models.py:1008
+msgid "Static content widget"
+msgstr ""
+
+#: aleksis/core/models.py:1009
+msgid "Static content widgets"
+msgstr ""
+
+#: aleksis/core/models.py:1014
 msgid "Dashboard widget"
 msgstr ""
 
-#: aleksis/core/models.py:975
+#: aleksis/core/models.py:1019
 msgid "Order"
 msgstr ""
 
-#: aleksis/core/models.py:976
+#: aleksis/core/models.py:1020
 msgid "Part of the default dashboard"
 msgstr ""
 
-#: aleksis/core/models.py:991
+#: aleksis/core/models.py:1035
 msgid "Dashboard widget order"
 msgstr ""
 
-#: aleksis/core/models.py:992
+#: aleksis/core/models.py:1036
 msgid "Dashboard widget orders"
 msgstr ""
 
-#: aleksis/core/models.py:998
+#: aleksis/core/models.py:1042
 msgid "Menu ID"
 msgstr ""
 
-#: aleksis/core/models.py:1011
+#: aleksis/core/models.py:1055
 msgid "Custom menu"
 msgstr ""
 
-#: aleksis/core/models.py:1012
+#: aleksis/core/models.py:1056
 msgid "Custom menus"
 msgstr ""
 
-#: aleksis/core/models.py:1022
+#: aleksis/core/models.py:1066
 msgid "Menu"
 msgstr ""
 
-#: aleksis/core/models.py:1026 aleksis/core/models.py:1274
+#: aleksis/core/models.py:1070 aleksis/core/models.py:1318
 #: aleksis/core/templates/oauth2_provider/application/detail.html:26
 msgid "Icon"
 msgstr ""
 
-#: aleksis/core/models.py:1032
+#: aleksis/core/models.py:1076
 msgid "Custom menu item"
 msgstr ""
 
-#: aleksis/core/models.py:1033
+#: aleksis/core/models.py:1077
 msgid "Custom menu items"
 msgstr ""
 
-#: aleksis/core/models.py:1047
+#: aleksis/core/models.py:1091
 msgid "Title of type"
 msgstr ""
 
-#: aleksis/core/models.py:1054 aleksis/core/templates/core/group/full.html:47
+#: aleksis/core/models.py:1098 aleksis/core/templates/core/group/full.html:47
 msgid "Group type"
 msgstr ""
 
-#: aleksis/core/models.py:1068
+#: aleksis/core/models.py:1112
 msgid "Can view system status"
 msgstr ""
 
-#: aleksis/core/models.py:1069
+#: aleksis/core/models.py:1113
 msgid "Can manage data"
 msgstr ""
 
-#: aleksis/core/models.py:1070
+#: aleksis/core/models.py:1114
 msgid "Can impersonate"
 msgstr ""
 
-#: aleksis/core/models.py:1071
+#: aleksis/core/models.py:1115
 msgid "Can use search"
 msgstr ""
 
-#: aleksis/core/models.py:1072
+#: aleksis/core/models.py:1116
 msgid "Can change site preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1073
+#: aleksis/core/models.py:1117
 msgid "Can change person preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1074
+#: aleksis/core/models.py:1118
 msgid "Can change group preferences"
 msgstr ""
 
-#: aleksis/core/models.py:1075
+#: aleksis/core/models.py:1119
 msgid "Can test PDF generation"
 msgstr ""
 
-#: aleksis/core/models.py:1076
+#: aleksis/core/models.py:1120
 msgid "Can invite persons"
 msgstr ""
 
-#: aleksis/core/models.py:1112
+#: aleksis/core/models.py:1156
 msgid "Related data check task"
 msgstr ""
 
-#: aleksis/core/models.py:1120
+#: aleksis/core/models.py:1164
 msgid "Issue solved"
 msgstr ""
 
-#: aleksis/core/models.py:1121
+#: aleksis/core/models.py:1165
 msgid "Notification sent"
 msgstr ""
 
-#: aleksis/core/models.py:1134
+#: aleksis/core/models.py:1178
 msgid "Data check result"
 msgstr ""
 
-#: aleksis/core/models.py:1135
+#: aleksis/core/models.py:1179
 msgid "Data check results"
 msgstr ""
 
-#: aleksis/core/models.py:1137
+#: aleksis/core/models.py:1181
 msgid "Can run data checks"
 msgstr ""
 
-#: aleksis/core/models.py:1138
+#: aleksis/core/models.py:1182
 msgid "Can solve data check problems"
 msgstr ""
 
-#: aleksis/core/models.py:1145
+#: aleksis/core/models.py:1189
 msgid "E-Mail address"
 msgstr ""
 
-#: aleksis/core/models.py:1177
+#: aleksis/core/models.py:1221
 msgid "Owner"
 msgstr ""
 
-#: aleksis/core/models.py:1181
+#: aleksis/core/models.py:1225
 msgid "File expires at"
 msgstr ""
 
-#: aleksis/core/models.py:1183
+#: aleksis/core/models.py:1227
 msgid "Generated HTML file"
 msgstr ""
 
-#: aleksis/core/models.py:1185
+#: aleksis/core/models.py:1229
 msgid "Generated PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1192
+#: aleksis/core/models.py:1236
 msgid "PDF file"
 msgstr ""
 
-#: aleksis/core/models.py:1193
+#: aleksis/core/models.py:1237
 msgid "PDF files"
 msgstr ""
 
-#: aleksis/core/models.py:1198
+#: aleksis/core/models.py:1242
 msgid "Task result"
 msgstr ""
 
-#: aleksis/core/models.py:1201
+#: aleksis/core/models.py:1245
 msgid "Task user"
 msgstr ""
 
-#: aleksis/core/models.py:1213
+#: aleksis/core/models.py:1257
 msgid "Task user assignment"
 msgstr ""
 
-#: aleksis/core/models.py:1214
+#: aleksis/core/models.py:1258
 msgid "Task user assignments"
 msgstr ""
 
-#: aleksis/core/models.py:1230
+#: aleksis/core/models.py:1274
 msgid "Additional attributes"
 msgstr ""
 
-#: aleksis/core/models.py:1268
+#: aleksis/core/models.py:1312
 msgid "Allowed scopes that clients can request"
 msgstr ""
 
-#: aleksis/core/models.py:1278
+#: aleksis/core/models.py:1322
 msgid "This image will be shown as icon in the authorization flow. It should be squared."
 msgstr ""
 
@@ -1083,86 +1111,98 @@ msgid "Allow users to change their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:279
-msgid "Enable signup"
+msgid "Allow users to reset their passwords"
 msgstr ""
 
 #: aleksis/core/preferences.py:287
-msgid "Enable invitations"
+msgid "Enable signup"
 msgstr ""
 
 #: aleksis/core/preferences.py:295
-msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgid "Regular expression for allowed usernames"
 msgstr ""
 
 #: aleksis/core/preferences.py:303
+msgid "Enable invitations"
+msgstr ""
+
+#: aleksis/core/preferences.py:311
+msgid "Length of invite code. (Default 3: abcde-acbde-abcde)"
+msgstr ""
+
+#: aleksis/core/preferences.py:319
 msgid "Size of packets. (Default 5: abcde)"
 msgstr ""
 
-#: aleksis/core/preferences.py:314
+#: aleksis/core/preferences.py:330
 msgid "Allowed Grant Flows for OAuth applications"
 msgstr ""
 
-#: aleksis/core/preferences.py:328
+#: aleksis/core/preferences.py:344
 msgid "Available languages"
 msgstr ""
 
-#: aleksis/core/preferences.py:341
+#: aleksis/core/preferences.py:357
 msgid "Send emails if data checks detect problems"
 msgstr ""
 
-#: aleksis/core/preferences.py:352
+#: aleksis/core/preferences.py:368
 msgid "Email recipients for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:363
+#: aleksis/core/preferences.py:379
 msgid "Email recipient groups for data checks problem emails"
 msgstr ""
 
-#: aleksis/core/preferences.py:372
+#: aleksis/core/preferences.py:388
 msgid "Show dashboard to users without login"
 msgstr ""
 
-#: aleksis/core/preferences.py:381
+#: aleksis/core/preferences.py:397
 msgid "Allow users to edit their dashboard"
 msgstr ""
 
-#: aleksis/core/preferences.py:392
+#: aleksis/core/preferences.py:408
 msgid "Fields on person model which are editable by themselves."
 msgstr ""
 
-#: aleksis/core/preferences.py:407
+#: aleksis/core/preferences.py:423
 msgid "Editable fields on person model which should trigger a notification on change"
 msgstr ""
 
-#: aleksis/core/preferences.py:421
+#: aleksis/core/preferences.py:437
 msgid "Contact for notification if a person changes their data"
 msgstr ""
 
-#: aleksis/core/preferences.py:432
+#: aleksis/core/preferences.py:448
+msgid "Prefer personal photos over avatars"
+msgstr ""
+
+#: aleksis/core/preferences.py:458
 msgid "PDF file expiration duration"
 msgstr ""
 
-#: aleksis/core/preferences.py:433
+#: aleksis/core/preferences.py:459
 msgid "in minutes"
 msgstr ""
 
-#: aleksis/core/preferences.py:443
+#: aleksis/core/preferences.py:469
 msgid "Automatically update the dashboard and its widgets"
 msgstr ""
 
-#: aleksis/core/preferences.py:453
+#: aleksis/core/preferences.py:479
 msgid "Automatically update the dashboard and its widgets sitewide"
 msgstr ""
 
-#: aleksis/core/preferences.py:463
+#: aleksis/core/preferences.py:489
 msgid "Country for phone number parsing"
 msgstr ""
 
-#: aleksis/core/settings.py:529
+#: aleksis/core/settings.py:540
 msgid "English"
 msgstr ""
 
-#: aleksis/core/settings.py:530
+#: aleksis/core/settings.py:541
 msgid "German"
 msgstr ""
 
@@ -1170,7 +1210,7 @@ msgstr ""
 #: aleksis/core/templates/core/announcement/list.html:36
 #: aleksis/core/templates/core/group/full.html:24
 #: aleksis/core/templates/core/person/full.html:26
-#: aleksis/core/templates/core/person/full.html:98
+#: aleksis/core/templates/core/person/full.html:86
 #: aleksis/core/templates/oauth2_provider/application/detail.html:17
 msgid "Edit"
 msgstr ""
@@ -1188,7 +1228,7 @@ msgstr ""
 #: aleksis/core/templates/core/group/full.html:31
 #: aleksis/core/templates/core/pages/delete.html:22
 #: aleksis/core/templates/core/person/full.html:33
-#: aleksis/core/templates/core/person/full.html:105
+#: aleksis/core/templates/core/person/full.html:93
 #: aleksis/core/templates/oauth2_provider/application/detail.html:21
 msgid "Delete"
 msgstr ""
@@ -1235,6 +1275,10 @@ msgid ""
 "          "
 msgstr ""
 
+#: aleksis/core/templates/500.html:21
+msgid "Retry"
+msgstr ""
+
 #: aleksis/core/templates/503.html:10
 msgid ""
 "The maintenance mode is currently enabled. Please try again\n"
@@ -1493,19 +1537,15 @@ msgstr ""
 msgid "There are no announcements."
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:78
-msgid "Logged in as"
-msgstr ""
-
-#: aleksis/core/templates/core/base.html:179
+#: aleksis/core/templates/core/base.html:213
 msgid "About AlekSIS® — The Free School Information System"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:187
+#: aleksis/core/templates/core/base.html:221
 msgid "Imprint"
 msgstr ""
 
-#: aleksis/core/templates/core/base.html:195
+#: aleksis/core/templates/core/base.html:229
 msgid "Privacy Policy"
 msgstr ""
 
@@ -1726,7 +1766,7 @@ msgstr ""
 
 #: aleksis/core/templates/core/group/full.html:38
 #: aleksis/core/templates/core/person/full.html:40
-#: aleksis/core/templates/core/person/full.html:112
+#: aleksis/core/templates/core/person/full.html:100
 msgid "Change preferences"
 msgstr ""
 
@@ -1916,83 +1956,83 @@ msgstr ""
 msgid "System checks"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:21
+#: aleksis/core/templates/core/pages/system_status.html:22
 msgid "Maintenance mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:23
+#: aleksis/core/templates/core/pages/system_status.html:24
 msgid ""
 "\n"
 "                Only admin and visitors from internal IPs can access thesite.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:34
+#: aleksis/core/templates/core/pages/system_status.html:36
 msgid "Maintenance mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:35
+#: aleksis/core/templates/core/pages/system_status.html:37
 msgid "Everyone can access the site."
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:45
+#: aleksis/core/templates/core/pages/system_status.html:47
 msgid "Debug mode enabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:47
+#: aleksis/core/templates/core/pages/system_status.html:49
 msgid ""
 "\n"
 "                The web server throws back debug information on errors. Do not use in production!\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:54
+#: aleksis/core/templates/core/pages/system_status.html:56
 msgid "Debug mode disabled"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:56
+#: aleksis/core/templates/core/pages/system_status.html:58
 msgid ""
 "\n"
 "                Debug mode is disabled. Default error pages are displayed on errors.\n"
 "              "
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:69
+#: aleksis/core/templates/core/pages/system_status.html:71
 msgid "System health checks"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:75
+#: aleksis/core/templates/core/pages/system_status.html:77
 msgid "Service"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:76
-#: aleksis/core/templates/core/pages/system_status.html:115
+#: aleksis/core/templates/core/pages/system_status.html:78
+#: aleksis/core/templates/core/pages/system_status.html:119
 msgid "Status"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:77
+#: aleksis/core/templates/core/pages/system_status.html:79
 msgid "Time taken"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:96
+#: aleksis/core/templates/core/pages/system_status.html:100
 msgid "seconds"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:107
+#: aleksis/core/templates/core/pages/system_status.html:111
 msgid "Celery task results"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:112
+#: aleksis/core/templates/core/pages/system_status.html:116
 #: aleksis/core/templates/templated_email/celery_failure.email:9
 #: aleksis/core/templates/templated_email/celery_failure.email:28
 msgid "Task"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:113
+#: aleksis/core/templates/core/pages/system_status.html:117
 msgid "ID"
 msgstr ""
 
-#: aleksis/core/templates/core/pages/system_status.html:114
+#: aleksis/core/templates/core/pages/system_status.html:118
 msgid "Date done"
 msgstr ""
 
@@ -2033,6 +2073,18 @@ msgid ""
 "            "
 msgstr ""
 
+#: aleksis/core/templates/core/partials/avatar_content.html:14
+#: aleksis/core/templates/core/partials/avatar_content.html:15
+#: aleksis/core/templates/core/person/full.html:213
+#: aleksis/core/templates/core/person/full.html:214
+msgid "Avatar"
+msgstr ""
+
+#: aleksis/core/templates/core/partials/avatar_content.html:19
+#: aleksis/core/templates/core/partials/avatar_content.html:20
+msgid "Identicon"
+msgstr ""
+
 #: aleksis/core/templates/core/partials/crud_events.html:15
 msgid "Changed by"
 msgstr ""
@@ -2123,24 +2175,24 @@ msgid "Edit person"
 msgstr ""
 
 #: aleksis/core/templates/core/person/full.html:47
-#: aleksis/core/templates/core/person/full.html:119
+#: aleksis/core/templates/core/person/full.html:107
 msgid "Impersonate"
 msgstr ""
 
 #: aleksis/core/templates/core/person/full.html:54
-#: aleksis/core/templates/core/person/full.html:126
+#: aleksis/core/templates/core/person/full.html:114
 msgid "Invite user"
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:133
+#: aleksis/core/templates/core/person/full.html:121
 msgid "Contact details"
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:224
+#: aleksis/core/templates/core/person/full.html:220
 msgid "This person didn't upload a personal photo."
 msgstr ""
 
-#: aleksis/core/templates/core/person/full.html:232
+#: aleksis/core/templates/core/person/full.html:228
 msgid "Children"
 msgstr ""
 
@@ -2345,13 +2397,11 @@ msgstr ""
 msgid "Network error"
 msgstr ""
 
-#: aleksis/core/templates/offline.html:8
-msgid ""
-"No internet\n"
-"    connection."
+#: aleksis/core/templates/offline.html:10
+msgid "No internet connection."
 msgstr ""
 
-#: aleksis/core/templates/offline.html:12
+#: aleksis/core/templates/offline.html:14
 msgid ""
 "\n"
 "      There was an error accessing this page. You probably don't have an internet connection. Check to see if your WiFi\n"
@@ -2595,7 +2645,7 @@ msgstr ""
 #: aleksis/core/templates/two_factor/_base_focus.html:6
 #: aleksis/core/templates/two_factor/core/otp_required.html:22
 #: aleksis/core/templates/two_factor/core/setup.html:5
-#: aleksis/core/templates/two_factor/profile/profile.html:87
+#: aleksis/core/templates/two_factor/profile/profile.html:88
 msgid "Enable Two-Factor Authentication"
 msgstr ""
 
@@ -2699,15 +2749,15 @@ msgstr ""
 msgid "Or, alternatively, use one of your backup phones:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:121
+#: aleksis/core/templates/two_factor/core/login.html:122
 msgid "As a last resort, you can use a backup token:"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:124
+#: aleksis/core/templates/two_factor/core/login.html:125
 msgid "Use Backup Token"
 msgstr ""
 
-#: aleksis/core/templates/two_factor/core/login.html:135
+#: aleksis/core/templates/two_factor/core/login.html:136
 msgid "Use alternative login options"
 msgstr ""
 
@@ -2944,11 +2994,11 @@ msgid ""
 "      "
 msgstr ""
 
-#: aleksis/core/util/notifications.py:63
+#: aleksis/core/util/notifications.py:64
 msgid "E-Mail"
 msgstr ""
 
-#: aleksis/core/util/notifications.py:64
+#: aleksis/core/util/notifications.py:65
 msgid "SMS"
 msgstr ""
 
@@ -2972,156 +3022,156 @@ msgstr ""
 msgid "Download PDF"
 msgstr ""
 
-#: aleksis/core/views.py:285
+#: aleksis/core/views.py:289
 msgid "The school term has been created."
 msgstr ""
 
-#: aleksis/core/views.py:297
+#: aleksis/core/views.py:301
 msgid "The school term has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:417
+#: aleksis/core/views.py:421
 msgid "The child groups were successfully saved."
 msgstr ""
 
-#: aleksis/core/views.py:436 aleksis/core/views.py:446
+#: aleksis/core/views.py:440 aleksis/core/views.py:450
 msgid "The person has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:496
+#: aleksis/core/views.py:500
 msgid "The group has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:593
+#: aleksis/core/views.py:597
 msgid "The announcement has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:609
+#: aleksis/core/views.py:613
 msgid "The announcement has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:677
+#: aleksis/core/views.py:681
 msgid "The requested preference registry does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:696
+#: aleksis/core/views.py:700
 msgid "The preferences have been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:720
+#: aleksis/core/views.py:724
 msgid "The person has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:734
+#: aleksis/core/views.py:738
 msgid "The group has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:766
+#: aleksis/core/views.py:770
 msgid "The additional_field has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:800
+#: aleksis/core/views.py:804
 msgid "The additional field has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:825
+#: aleksis/core/views.py:829
 msgid "The group type has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:855
+#: aleksis/core/views.py:859
 msgid "The group type has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:888
+#: aleksis/core/views.py:892
 msgid "Progress: Run data checks"
 msgstr ""
 
-#: aleksis/core/views.py:889
+#: aleksis/core/views.py:893
 msgid "Run data checks …"
 msgstr ""
 
-#: aleksis/core/views.py:890
+#: aleksis/core/views.py:894
 msgid "The data checks were run successfully."
 msgstr ""
 
-#: aleksis/core/views.py:891
+#: aleksis/core/views.py:895
 msgid "There was a problem while running data checks."
 msgstr ""
 
-#: aleksis/core/views.py:907
+#: aleksis/core/views.py:911
 #, python-brace-format
 msgid "The solve option '{solve_option_obj.verbose_name}' "
 msgstr ""
 
-#: aleksis/core/views.py:917
+#: aleksis/core/views.py:921
 msgid "The requested solve option does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:949
+#: aleksis/core/views.py:953
 msgid "The dashboard widget has been saved."
 msgstr ""
 
-#: aleksis/core/views.py:979
+#: aleksis/core/views.py:983
 msgid "The dashboard widget has been created."
 msgstr ""
 
-#: aleksis/core/views.py:989
+#: aleksis/core/views.py:993
 msgid "The dashboard widget has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1060
+#: aleksis/core/views.py:1064
 msgid "Your dashboard configuration has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1062
+#: aleksis/core/views.py:1066
 msgid "The configuration of the default dashboard has been saved successfully."
 msgstr ""
 
-#: aleksis/core/views.py:1138
+#: aleksis/core/views.py:1142
 #, python-brace-format
 msgid "The invitation was successfully created. The invitation code is {code}"
 msgstr ""
 
-#: aleksis/core/views.py:1229
+#: aleksis/core/views.py:1233
 msgid "We have successfully assigned the permissions."
 msgstr ""
 
-#: aleksis/core/views.py:1239
+#: aleksis/core/views.py:1243
 msgid "The global user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1249
+#: aleksis/core/views.py:1253
 msgid "The global group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1259
+#: aleksis/core/views.py:1263
 msgid "The object user permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1269
+#: aleksis/core/views.py:1273
 msgid "The object group permission has been deleted."
 msgstr ""
 
-#: aleksis/core/views.py:1337
+#: aleksis/core/views.py:1341
 msgid "The requested PDF file does not exist"
 msgstr ""
 
-#: aleksis/core/views.py:1346 aleksis/core/views.py:1350
+#: aleksis/core/views.py:1350 aleksis/core/views.py:1354
 msgid "The requested task does not exist or is not accessible"
 msgstr ""
 
-#: aleksis/core/views.py:1388
+#: aleksis/core/views.py:1406
 msgid "The third-party account could not be disconnected because it is the only login method available."
 msgstr ""
 
-#: aleksis/core/views.py:1395
+#: aleksis/core/views.py:1413
 msgid "The third-party account has been successfully disconnected."
 msgstr ""
 
-#: aleksis/core/views.py:1466
+#: aleksis/core/views.py:1484
 msgid "Person was invited successfully and an email with further instructions has been send to them."
 msgstr ""
 
-#: aleksis/core/views.py:1477
+#: aleksis/core/views.py:1495
 msgid "Person was already invited."
 msgstr ""
diff --git a/aleksis/core/locale/tr_TR/LC_MESSAGES/djangojs.po b/aleksis/core/locale/tr_TR/LC_MESSAGES/djangojs.po
index dfec73c5c894d3d686092593be9c21a811cd0535..f3df4c3fc6d2848a739ddd1faac44cb9048c97b2 100644
--- a/aleksis/core/locale/tr_TR/LC_MESSAGES/djangojs.po
+++ b/aleksis/core/locale/tr_TR/LC_MESSAGES/djangojs.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-08 23:16+0000\n"
+"POT-Creation-Date: 2022-03-23 11:20+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,6 +29,6 @@ msgstr ""
 msgid "OK"
 msgstr ""
 
-#: aleksis/core/static/js/main.js:191
+#: aleksis/core/static/js/main.js:195
 msgid "This page may contain outdated information since there is no internet connection."
 msgstr ""
diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py
index 7938539c53327936d86d6c08334834e8e2a4fb0a..49ddd4af743769252cbdbbae26d7de1d68c55776 100644
--- a/aleksis/core/menus.py
+++ b/aleksis/core/menus.py
@@ -1,20 +1,18 @@
 from django.conf import settings
 from django.utils.translation import gettext_lazy as _
 
-from .util.core_helpers import unread_notifications_badge
-
 MENUS = {
     "NAV_MENU_CORE": [
         {
             "name": _("Login"),
             "url": settings.LOGIN_URL,
-            "icon": "lock_open",
+            "svg_icon": "mdi:login-variant",
             "validators": ["menu_generator.validators.is_anonymous"],
         },
         {
             "name": _("Sign up"),
             "url": "account_signup",
-            "icon": "how_to_reg",
+            "svg_icon": "mdi:account-plus-outline",
             "validators": [
                 "menu_generator.validators.is_anonymous",
                 ("aleksis.core.util.predicates.permission_validator", "core.can_register"),
@@ -23,7 +21,7 @@ MENUS = {
         {
             "name": _("Accept invitation"),
             "url": "enter_invitation_code",
-            "icon": "vpn_key",
+            "svg_icon": "mdi:key-outline",
             "validators": [
                 "menu_generator.validators.is_anonymous",
                 ("aleksis.core.util.predicates.permission_validator", "core.invite_enabled"),
@@ -32,107 +30,15 @@ MENUS = {
         {
             "name": _("Dashboard"),
             "url": "index",
-            "icon": "home",
+            "svg_icon": "mdi:home-outline",
             "validators": [
                 ("aleksis.core.util.predicates.permission_validator", "core.view_dashboard_rule")
             ],
         },
-        {
-            "name": _("Notifications"),
-            "url": "notifications",
-            "icon": "notifications",
-            "badge": unread_notifications_badge,
-            "validators": [
-                (
-                    "aleksis.core.util.predicates.permission_validator",
-                    "core.view_notifications",
-                ),
-            ],
-        },
-        {
-            "name": _("Account"),
-            "url": "#",
-            "icon": "person",
-            "root": True,
-            "validators": ["menu_generator.validators.is_authenticated"],
-            "submenu": [
-                {
-                    "name": _("Stop impersonation"),
-                    "url": "impersonate-stop",
-                    "icon": "stop",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.is_impersonate",
-                    ],
-                },
-                {
-                    "name": _("Logout"),
-                    "url": "logout",
-                    "icon": "exit_to_app",
-                    "validators": ["menu_generator.validators.is_authenticated"],
-                },
-                {
-                    "name": _("2FA"),
-                    "url": "two_factor:profile",
-                    "icon": "phonelink_lock",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                    ],
-                },
-                {
-                    "name": _("Change password"),
-                    "url": "account_change_password",
-                    "icon": "lock",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        (
-                            "aleksis.core.util.predicates.permission_validator",
-                            "core.can_change_password",
-                        ),
-                    ],
-                },
-                {
-                    "name": _("Me"),
-                    "url": "person",
-                    "icon": "insert_emoticon",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.has_person",
-                    ],
-                },
-                {
-                    "name": _("Preferences"),
-                    "url": "preferences_person",
-                    "icon": "settings",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.has_person",
-                    ],
-                },
-                {
-                    "name": _("Third-party accounts"),
-                    "url": "socialaccount_connections",
-                    "icon": "public",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.has_person",
-                    ],
-                },
-                {
-                    "name": _("Authorized applications"),
-                    "url": "oauth2_provider:authorized-token-list",
-                    "icon": "touch_app",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.has_person",
-                    ],
-                },
-            ],
-        },
         {
             "name": _("Admin"),
             "url": "#",
-            "icon": "security",
+            "svg_icon": "mdi:security",
             "validators": [
                 ("aleksis.core.util.predicates.permission_validator", "core.view_admin_menu"),
             ],
@@ -140,7 +46,7 @@ MENUS = {
                 {
                     "name": _("Announcements"),
                     "url": "announcements",
-                    "icon": "announcement",
+                    "svg_icon": "mdi:message-alert-outline",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -151,7 +57,7 @@ MENUS = {
                 {
                     "name": _("School terms"),
                     "url": "school_terms",
-                    "icon": "date_range",
+                    "svg_icon": "mdi:calendar-range-outline",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -162,7 +68,7 @@ MENUS = {
                 {
                     "name": _("Dashboard widgets"),
                     "url": "dashboard_widgets",
-                    "icon": "dashboard",
+                    "svg_icon": "mdi:view-dashboard-outline",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -173,7 +79,7 @@ MENUS = {
                 {
                     "name": _("Data management"),
                     "url": "data_management",
-                    "icon": "view_list",
+                    "svg_icon": "mdi:chart-donut",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -184,7 +90,7 @@ MENUS = {
                 {
                     "name": _("System status"),
                     "url": "system_status",
-                    "icon": "power_settings_new",
+                    "svg_icon": "mdi:power-settings",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -195,7 +101,7 @@ MENUS = {
                 {
                     "name": _("Configuration"),
                     "url": "preferences_site",
-                    "icon": "settings",
+                    "svg_icon": "mdi:tune",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -206,13 +112,13 @@ MENUS = {
                 {
                     "name": _("Data checks"),
                     "url": "check_data",
-                    "icon": "done_all",
+                    "svg_icon": "mdi:list-status",
                     "validators": ["menu_generator.validators.is_superuser"],
                 },
                 {
                     "name": _("Manage permissions"),
                     "url": "manage_user_global_permissions",
-                    "icon": "shield",
+                    "svg_icon": "mdi:shield-outline",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -223,7 +129,7 @@ MENUS = {
                 {
                     "name": _("Backend Admin"),
                     "url": "admin:index",
-                    "icon": "settings",
+                    "svg_icon": "mdi:database-cog-outline",
                     "validators": [
                         "menu_generator.validators.is_superuser",
                     ],
@@ -231,7 +137,7 @@ MENUS = {
                 {
                     "name": _("OAuth2 Applications"),
                     "url": "oauth2_applications",
-                    "icon": "touch_app",
+                    "svg_icon": "mdi:gesture-tap-hold",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -244,7 +150,7 @@ MENUS = {
         {
             "name": _("People"),
             "url": "#",
-            "icon": "people",
+            "svg_icon": "mdi:account-group-outline",
             "root": True,
             "validators": [
                 ("aleksis.core.util.predicates.permission_validator", "core.view_people_menu_rule")
@@ -253,7 +159,7 @@ MENUS = {
                 {
                     "name": _("Persons"),
                     "url": "persons",
-                    "icon": "person",
+                    "svg_icon": "mdi:account-outline",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -264,7 +170,7 @@ MENUS = {
                 {
                     "name": _("Groups"),
                     "url": "groups",
-                    "icon": "group",
+                    "svg_icon": "mdi:account-multiple-outline",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -275,7 +181,7 @@ MENUS = {
                 {
                     "name": _("Group types"),
                     "url": "group_types",
-                    "icon": "category",
+                    "svg_icon": "mdi:shape-outline",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -286,7 +192,7 @@ MENUS = {
                 {
                     "name": _("Groups and child groups"),
                     "url": "groups_child_groups",
-                    "icon": "group_add",
+                    "svg_icon": "mdi:account-multiple-plus-outline",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -297,7 +203,7 @@ MENUS = {
                 {
                     "name": _("Additional fields"),
                     "url": "additional_fields",
-                    "icon": "style",
+                    "svg_icon": "mdi:palette-swatch-outline",
                     "validators": [
                         (
                             "aleksis.core.util.predicates.permission_validator",
@@ -308,7 +214,7 @@ MENUS = {
                 {
                     "name": _("Invite person"),
                     "url": "invite_person",
-                    "icon": "card_giftcard",
+                    "svg_icon": "mdi:account-plus-outline",
                     "validators": [
                         "menu_generator.validators.is_authenticated",
                         ("aleksis.core.util.predicates.permission_validator", "core.can_invite"),
@@ -329,4 +235,90 @@ MENUS = {
             ],
         },
     ],
+    "NAVBAR_ACCOUNT_MENU": [
+        {
+            "name": _("Stop impersonation"),
+            "url": "impersonate-stop",
+            "svg_icon": "mdi:stop",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.is_impersonate",
+            ],
+        },
+        {
+            "name": _("Account"),
+            "url": "person",
+            "svg_icon": "mdi:account-outline",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.has_person",
+            ],
+        },
+        {
+            "name": _("Preferences"),
+            "url": "preferences_person",
+            "svg_icon": "mdi:cog-outline",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.has_person",
+            ],
+        },
+        {
+            "name": _("2FA"),
+            "url": "two_factor:profile",
+            "svg_icon": "mdi:two-factor-authentication",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+            ],
+        },
+        {
+            "name": _("Change password"),
+            "url": "account_change_password",
+            "svg_icon": "mdi:form-textbox-password",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                (
+                    "aleksis.core.util.predicates.permission_validator",
+                    "core.can_change_password",
+                ),
+            ],
+        },
+        {
+            "name": _("Third-party accounts"),
+            "url": "socialaccount_connections",
+            "svg_icon": "mdi:earth",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.has_person",
+            ],
+        },
+        {
+            "name": _("Authorized applications"),
+            "url": "oauth2_provider:authorized-token-list",
+            "svg_icon": "mdi:gesture-tap-hold",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.has_person",
+            ],
+        },
+        {
+            "name": _("Calendar Feeds"),
+            "url": "ical_feed_list",
+            "svg_icon": "mdi:calendar-multiple",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                (
+                    "aleksis.core.util.predicates.permission_validator",
+                    "core.view_ical_rule",
+                ),
+            ],
+        },
+        {
+            "divider": True,
+            "name": _("Logout"),
+            "url": "logout",
+            "svg_icon": "mdi:logout-variant",
+            "validators": ["menu_generator.validators.is_authenticated"],
+        },
+    ],
 }
diff --git a/aleksis/core/migrations/0036_additionalfields_helptext_required.py b/aleksis/core/migrations/0036_additionalfields_helptext_required.py
new file mode 100644
index 0000000000000000000000000000000000000000..9486105645a238f437b0a25eced23e56d7d805c2
--- /dev/null
+++ b/aleksis/core/migrations/0036_additionalfields_helptext_required.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.12 on 2022-02-18 21:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0035_preference_model_unique'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='additionalfield',
+            name='help_text',
+            field=models.TextField(blank=True, verbose_name='Help text / description'),
+        ),
+        migrations.AddField(
+            model_name='additionalfield',
+            name='required',
+            field=models.BooleanField(default=False, verbose_name='Required'),
+        ),
+    ]
diff --git a/aleksis/core/migrations/0037_add_static_content_widget.py b/aleksis/core/migrations/0037_add_static_content_widget.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb4e810a614c1357243bdda4c5ac42b7f844058a
--- /dev/null
+++ b/aleksis/core/migrations/0037_add_static_content_widget.py
@@ -0,0 +1,27 @@
+# Generated by Django 3.2.12 on 2022-02-23 18:03
+
+import ckeditor.fields
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0036_additionalfields_helptext_required'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='StaticContentWidget',
+            fields=[
+                ('dashboardwidget_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.dashboardwidget')),
+                ('content', ckeditor.fields.RichTextField(verbose_name='Content')),
+            ],
+            options={
+                'verbose_name': 'Static content widget',
+                'verbose_name_plural': 'Static content widgets',
+            },
+            bases=('core.dashboardwidget',),
+        ),
+    ]
diff --git a/aleksis/core/migrations/0038_notification_send_at.py b/aleksis/core/migrations/0038_notification_send_at.py
new file mode 100644
index 0000000000000000000000000000000000000000..edb96f1e87f143d60477a1578aea5d90226389a8
--- /dev/null
+++ b/aleksis/core/migrations/0038_notification_send_at.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.12 on 2022-02-23 19:33
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0037_add_static_content_widget'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='notification',
+            name='send_at',
+            field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Send notification at'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/aleksis/core/migrations/0039_personal_ical_url.py b/aleksis/core/migrations/0039_personal_ical_url.py
new file mode 100644
index 0000000000000000000000000000000000000000..02d9cb3a9e3589563c7dd68ce211271264c5245e
--- /dev/null
+++ b/aleksis/core/migrations/0039_personal_ical_url.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.2.12 on 2022-02-20 21:04
+
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+from aleksis.core.feeds import PersonalICalFeedBase
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0038_notification_send_at'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='PersonalICalUrl',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
+                ('name', models.CharField(max_length=255, verbose_name='Name')),
+                ('ical_feed', models.CharField(choices=PersonalICalFeedBase.subclass_choices, max_length=255, verbose_name='Selected ICal feed')),
+                ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='calendar_urls', to='core.person', verbose_name='Person')),
+            ],
+            options={
+                'verbose_name': 'Personal Calendar URL',
+                'verbose_name_plural': 'Personal Calendar URLs',
+            },
+        ),
+    ]
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 812a9d254ca4bff2e9372ed7969d0cb2b811c176..963c60c99ef8e8b1a9b0ca7ebc31c7c8f298a2f5 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -1,5 +1,7 @@
 # flake8: noqa: DJ01
+import base64
 import hmac
+import uuid
 from datetime import date, datetime, timedelta
 from typing import Any, Iterable, List, Optional, Sequence, Union
 from urllib.parse import urlparse
@@ -24,9 +26,12 @@ from django.utils.functional import classproperty
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 
+import customidenticon
 import jsonstore
 from cachalot.api import cachalot_disabled
 from cache_memoize import cache_memoize
+from celery.result import AsyncResult
+from ckeditor.fields import RichTextField
 from django_celery_results.models import TaskResult
 from django_cte import CTEQuerySet, With
 from dynamic_preferences.models import PerInstancePreferenceModel
@@ -48,6 +53,7 @@ from polymorphic.models import PolymorphicModel
 
 from aleksis.core.data_checks import BrokenDashboardWidgetDataCheck, DataCheck, DataCheckRegistry
 
+from .feeds import PersonalICalFeedBase
 from .managers import (
     CurrentSiteManagerWithoutMigrations,
     GroupManager,
@@ -305,7 +311,7 @@ class Person(ExtensibleModel):
     @property
     def unread_notifications(self) -> QuerySet:
         """Get all unread notifications for this person."""
-        return self.notifications.filter(read=False)
+        return self.notifications.filter(read=False, send_at__lte=timezone.now())
 
     @property
     def unread_notifications_count(self) -> int:
@@ -314,7 +320,12 @@ class Person(ExtensibleModel):
 
     @property
     def initials(self):
-        return f"{self.first_name[0]}{self.last_name[0]}".upper()
+        initials = ""
+        if self.first_name:
+            initials += self.first_name[0]
+        if self.last_name:
+            initials += self.last_name[0]
+        return initials.upper() or "?"
 
     user_info_tracker = FieldTracker(fields=("first_name", "last_name", "email", "user_id"))
 
@@ -334,6 +345,20 @@ class Person(ExtensibleModel):
             q = q.union(group.child_groups_recursive)
         return q
 
+    @property
+    @cache_memoize(60 * 60)
+    def identicon_url(self):
+        identicon = customidenticon.create(self.full_name, border=35)
+        base64_data = base64.b64encode(identicon).decode("ascii")
+        return f"data:image/png;base64,{base64_data}"
+
+    @property
+    def avatar_url(self):
+        if self.avatar:
+            return self.avatar.url
+        else:
+            return self.identicon_url
+
     def save(self, *args, **kwargs):
         # Determine all fields that were changed since last load
         changed = self.user_info_tracker.changed()
@@ -435,6 +460,8 @@ class AdditionalField(ExtensibleModel):
     field_type = models.CharField(
         verbose_name=_("Type of field"), choices=FIELD_CHOICES, max_length=50
     )
+    required = models.BooleanField(verbose_name=_("Required"), default=False)
+    help_text = models.TextField(verbose_name=_("Help text / description"), blank=True)
 
     def __str__(self) -> str:
         return self.title
@@ -716,6 +743,8 @@ class Notification(ExtensibleModel, TimeStampedModel):
     description = models.TextField(max_length=500, verbose_name=_("Description"))
     link = models.URLField(blank=True, verbose_name=_("Link"))
 
+    send_at = models.DateTimeField(default=timezone.now, verbose_name=_("Send notification at"))
+
     read = models.BooleanField(default=False, verbose_name=_("Read"))
     sent = models.BooleanField(default=False, verbose_name=_("Sent"))
 
@@ -724,10 +753,14 @@ class Notification(ExtensibleModel, TimeStampedModel):
 
     def save(self, **kwargs):
         super().save(**kwargs)
-        if not self.sent:
-            send_notification(self.pk, resend=True)
-        self.sent = True
-        super().save(**kwargs)
+        if not self.sent and self.send_at <= timezone.now():
+            self.send()
+            super().save(**kwargs)
+
+    def send(self, resend: bool = False) -> Optional[AsyncResult]:
+        """Send the notification to the recipient."""
+        if not self.sent or resend:
+            return send_notification.delay(self.pk, resend=True)
 
     class Meta:
         verbose_name = _("Notification")
@@ -965,6 +998,19 @@ class ExternalLinkWidget(DashboardWidget):
         verbose_name_plural = _("External link widgets")
 
 
+class StaticContentWidget(DashboardWidget):
+    template = "core/dashboard_widget/static_content_widget.html"
+
+    content = RichTextField(verbose_name=_("Content"))
+
+    def get_context(self, request):
+        return {"title": self.title, "content": self.content}
+
+    class Meta:
+        verbose_name = _("Static content widget")
+        verbose_name_plural = _("Static content widgets")
+
+
 class DashboardWidgetOrder(ExtensibleModel):
     widget = models.ForeignKey(
         DashboardWidget, on_delete=models.CASCADE, verbose_name=_("Dashboard widget")
@@ -1307,3 +1353,41 @@ class OAuthRefreshToken(AbstractRefreshToken):
     """Placeholder for customising the RefreshToken model."""
 
     pass
+
+
+class PersonalICalUrl(models.Model):
+    """Calendar URL for a person.
+
+    This is used to connect iCalendar subscriptions to a person. A person can have multiple
+    URLs. The URL is used to generate the iCalendar feed and have personalized results. It
+    is possible to create multiple URLs for the same person and the same iCal feed, e.g. to
+    allow a person to share and unshare their calendar with other people.
+    """
+
+    person = models.ForeignKey(
+        "Person",
+        on_delete=models.CASCADE,
+        related_name="calendar_urls",
+        verbose_name=_("Person"),
+    )
+    uuid = models.UUIDField(default=uuid.uuid4, editable=False, verbose_name=_("UUID"), unique=True)
+    name = models.CharField(max_length=255, verbose_name=_("Name"))
+    ical_feed = models.CharField(
+        max_length=255,
+        verbose_name=_("Selected ICal feed"),
+        choices=PersonalICalFeedBase.subclass_choices,
+    )
+
+    @property
+    def ical_feed_object(self):
+        return PersonalICalFeedBase.subclasses_dict.get(self.ical_feed)
+
+    class Meta:
+        verbose_name = _("Personal Calendar URL")
+        verbose_name_plural = _("Personal Calendar URLs")
+
+    def __str__(self):
+        return self.name
+
+    def get_absolute_url(self):
+        return reverse("ical_feed", kwargs={"slug": self.uuid})
diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py
index f1fb0227eab16a152f29918e91b37d0808471812..78b1874356a3a11eab121a56963c9aaf3595d872 100644
--- a/aleksis/core/preferences.py
+++ b/aleksis/core/preferences.py
@@ -271,6 +271,14 @@ class AllowPasswordChange(BooleanPreference):
     verbose_name = _("Allow users to change their passwords")
 
 
+@site_preferences_registry.register
+class AllowPasswordReset(BooleanPreference):
+    section = auth
+    name = "allow_password_reset"
+    default = True
+    verbose_name = _("Allow users to reset their passwords")
+
+
 @site_preferences_registry.register
 class SignupEnabled(BooleanPreference):
     section = auth
@@ -279,6 +287,14 @@ class SignupEnabled(BooleanPreference):
     verbose_name = _("Enable signup")
 
 
+@site_preferences_registry.register
+class AllowedUsernameRegex(StringPreference):
+    section = auth
+    name = "allowed_username_regex"
+    default = ".+"
+    verbose_name = _("Regular expression for allowed usernames")
+
+
 @site_preferences_registry.register
 class InviteEnabled(BooleanPreference):
     section = auth
@@ -422,6 +438,16 @@ class PersonChangeNotificationContact(StringPreference):
     required = False
 
 
+@site_preferences_registry.register
+class PersonPreferPhoto(BooleanPreference):
+    """Preference, whether personal photos should be displayed instead of avatars."""
+
+    section = account
+    name = "person_prefer_photo"
+    default = False
+    verbose_name = _("Prefer personal photos over avatars")
+
+
 @site_preferences_registry.register
 class PDFFileExpirationDuration(IntegerPreference):
     """PDF file expiration duration."""
diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index 022fa11a00671a3acb0775c2229a378bd3981342..a6b478f26144b9fdea74804dccdb7a1913b01c30 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -7,6 +7,7 @@ from .util.predicates import (
     has_global_perm,
     has_object_perm,
     has_person,
+    is_assigned_to_current_person,
     is_current_person,
     is_group_owner,
     is_notification_recipient,
@@ -357,3 +358,16 @@ rules.add_perm("core.manage_permissions", manage_person_permissions_predicate)
 
 test_pdf_generation_predicate = has_person & has_global_perm("core.test_pdf")
 rules.add_perm("core.test_pdf_rule", test_pdf_generation_predicate)
+
+# Do CRUD on PersonalICalUrls
+view_ical_predicate = has_person
+rules.add_perm("core.view_ical_rule", view_ical_predicate)
+
+create_ical_predicate = view_ical_predicate
+rules.add_perm("core.create_ical_rule", create_ical_predicate)
+
+edit_ical_predicate = view_ical_predicate & is_assigned_to_current_person
+rules.add_perm("core.edit_ical_rule", edit_ical_predicate)
+
+delete_ical_predicate = edit_ical_predicate
+rules.add_perm("core.delete_ical_rule", delete_ical_predicate)
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index ce99ff32c66db9ca4576668308de67fe43aa6aa7..b708e991fb59d8ac978c638dda2ae150c8e66938 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -1,13 +1,20 @@
 import os
 import warnings
+from copy import deepcopy
 from glob import glob
 from socket import getfqdn
 
+from django.utils.log import DEFAULT_LOGGING
 from django.utils.translation import gettext_lazy as _
 
 from dynaconf import LazySettings
 
-from .util.core_helpers import get_app_packages, merge_app_settings, monkey_patch
+from .util.core_helpers import (
+    get_app_packages,
+    get_app_settings_overrides,
+    merge_app_settings,
+    monkey_patch,
+)
 
 monkey_patch()
 
@@ -147,6 +154,7 @@ INSTALLED_APPS = [
     "django_filters",
     "oauth2_provider",
     "rest_framework",
+    "dj_iconify.apps.DjIconifyConfig",
 ]
 
 merge_app_settings("INSTALLED_APPS", INSTALLED_APPS, True)
@@ -362,6 +370,9 @@ ACCOUNT_SIGNUP_EMAIL_ENTER_TWICE = True
 # Enforce uniqueness of email addresses
 ACCOUNT_UNIQUE_EMAIL = _settings.get("auth.login.registration.unique_email", True)
 
+# Configurable username validators
+ACCOUNT_USERNAME_VALIDATORS = "aleksis.core.util.auth_helpers.custom_username_validators"
+
 # Configuration for django-invitations
 
 # Use custom account adapter
@@ -566,6 +577,8 @@ YARN_INSTALLED_APPS = [
     "sortablejs",
     "@sentry/tracing",
     "luxon",
+    "@iconify/iconify",
+    "@iconify/json",
 ]
 
 merge_app_settings("YARN_INSTALLED_APPS", YARN_INSTALLED_APPS, True)
@@ -599,6 +612,7 @@ ANY_JS = {
     "Sentry": {"js_url": JS_URL + "/@sentry/tracing/build/bundle.tracing.js"},
     "cleavejs": {"js_url": JS_URL + "/cleave.js/dist/cleave.min.js"},
     "luxon": {"js_url": JS_URL + "/luxon/build/global/luxon.min.js"},
+    "iconify": {"js_url": JS_URL + "/@iconify/iconify/dist/iconify.min.js"},
 }
 
 merge_app_settings("ANY_JS", ANY_JS, True)
@@ -618,6 +632,8 @@ SASS_PROCESSOR_INCLUDE_DIRS = [
     os.path.join(STATIC_ROOT, "public"),
 ]
 
+ICONIFY_JSON_ROOT = os.path.join(JS_ROOT, "@iconify", "json")
+
 ADMINS = _settings.get(
     "contact.admins", [(AUTH_INITIAL_SUPERUSER["username"], AUTH_INITIAL_SUPERUSER["email"])]
 )
@@ -860,21 +876,22 @@ BLEACH_STRIP_TAGS = True
 # Strip comments, or leave them in.
 BLEACH_STRIP_COMMENTS = True
 
-LOGGING = {
-    "version": 1,
-    "disable_existing_loggers": False,
-    "handlers": {
-        "console": {"class": "logging.StreamHandler", "formatter": "verbose"},
-        "null": {"class": "logging.NullHandler"},
-    },
-    "formatters": {"verbose": {"format": "%(levelname)s %(asctime)s %(module)s: %(message)s"}},
-    "root": {
-        "handlers": ["console"],
-        "level": _settings.get("logging.level", "WARNING"),
-    },
-    "loggers": {},
+LOGGING = deepcopy(DEFAULT_LOGGING)
+# Set root logging level as default
+LOGGING["root"] = {
+    "handlers": ["console"],
+    "level": _settings.get("logging.level", "WARNING"),
 }
-
+# Add null handler for selective silencing
+LOGGING["handlers"]["null"] = {"class": "logging.NullHandler"}
+# Make console logging independent of DEBUG
+LOGGING["handlers"]["console"]["filters"].remove("require_debug_true")
+# Use root log level for console
+del LOGGING["handlers"]["console"]["level"]
+# Disable exception mails if not desired
+if not _settings.get("logging.mail_admins", True):
+    LOGGING["loggers"]["django"]["handlers"].remove("mail_admins")
+# Disable mails on disaalowed host by default
 if not _settings.get("logging.disallowed_host", False):
     LOGGING["loggers"]["django.security.DisallowedHost"] = {
         "handlers": ["null"],
@@ -1002,3 +1019,5 @@ merge_app_settings("SHELL_PLUS_DONT_LOAD", SHELL_PLUS_DONT_LOAD)
 
 # Add django-cleanup after all apps to ensure that it gets all signals as last app
 INSTALLED_APPS.append("django_cleanup.apps.CleanupConfig")
+
+locals().update(get_app_settings_overrides())
diff --git a/aleksis/core/static/js/copy_button.js b/aleksis/core/static/js/copy_button.js
new file mode 100644
index 0000000000000000000000000000000000000000..554f6230e4f9c8b9bb012875e31da25c4f61c2c6
--- /dev/null
+++ b/aleksis/core/static/js/copy_button.js
@@ -0,0 +1,16 @@
+$(".copy-button").click((e) => {
+    const target = $(e.currentTarget);
+    const input = $("#" + target.data("target"));
+    const copy_icon = target.children(".copy-icon-copy").first();
+    const check_icon = target.children(".copy-icon-success").first();
+
+    console.log("Copying to clipboard");
+    navigator.clipboard.writeText(input.val()).then(r => {
+        check_icon.show();
+        copy_icon.hide();
+        setTimeout(() => {
+            check_icon.hide();
+            copy_icon.show();
+        }, 1000);
+    });
+});
diff --git a/aleksis/core/static/js/main.js b/aleksis/core/static/js/main.js
index 9b4c133e1ae7db5906645963cfd7182da313ca01..b735e2bb53d4a3c6a8f5b1edc9a9bb307214e1b4 100644
--- a/aleksis/core/static/js/main.js
+++ b/aleksis/core/static/js/main.js
@@ -121,6 +121,10 @@ $(document).ready(function () {
 
     // Initialize dropdown [MAT]
     $('.dropdown-trigger').dropdown();
+    $('.navbar-dropdown-trigger').dropdown({
+        "coverTrigger": false,
+        "constrainWidth": false,
+    });
 
     // If JS is activated, the language form will be auto-submitted
     $('.language-field select').change(function () {
@@ -188,6 +192,6 @@ $(document).ready(function () {
 const channel = new BroadcastChannel("cache-or-not");
 channel.addEventListener("message", event => {
     if ((event.data) && !($("#cache-alert").length)) {
-        $("main").prepend('<div id="cache-alert" class="alert warning"><p><i class="material-icons left">warning</i>' + gettext("This page may contain outdated information since there is no internet connection.") + '</p> </div>')
+        $("main").prepend('<div id="cache-alert" class="alert warning"><p><i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>' + gettext("This page may contain outdated information since there is no internet connection.") + '</p> </div>')
     }
 });
diff --git a/aleksis/core/static/js/progress.js b/aleksis/core/static/js/progress.js
index 0b2509006029421273159617c9ff2646bb12800b..5fa0213f8466738466158a56ca6e1df50654a811 100644
--- a/aleksis/core/static/js/progress.js
+++ b/aleksis/core/static/js/progress.js
@@ -9,11 +9,11 @@ const STYLE_CLASSES = {
 };
 
 const ICONS = {
-    10: 'info',
-    20: 'info',
-    25: 'check_circle',
-    30: 'warning',
-    40: 'error',
+    10: 'mdi:information',
+    20: 'mdi:information',
+    25: 'mdi:check-circle',
+    30: 'mdi:alert-outline',
+    40: 'mdi:alert-octagon-outline',
 };
 
 function setProgress(progress) {
@@ -21,7 +21,7 @@ function setProgress(progress) {
 }
 
 function renderMessageBox(level, text) {
-    return '<div class="alert ' + STYLE_CLASSES[level] + '"><p><i class="material-icons left">' + ICONS[level] + '</i>' + text + '</p></div>';
+    return '<div class="alert ' + STYLE_CLASSES[level] + '"><p><i class="material-icons iconify left" data-icon="' + ICONS[level] + '"></i>' + text + '</p></div>';
 }
 
 function customProgress(progressBarElement, progressBarMessageElement, progress) {
@@ -44,7 +44,7 @@ function customProgress(progressBarElement, progressBarMessageElement, progress)
 function customSuccess(progressBarElement, progressBarMessageElement) {
     setProgress(100);
     $("#result-alert").addClass("success");
-    $("#result-icon").text("check_circle");
+    $("#result-icon").attr("data-icon", "mdi:check-circle-outline");
     $("#result-text").text(OPTIONS.success);
     $("#result-box").show();
     $("#result-button").show();
@@ -57,7 +57,7 @@ function customSuccess(progressBarElement, progressBarMessageElement) {
 function customError(progressBarElement, progressBarMessageElement) {
     setProgress(100);
     $("#result-alert").addClass("error");
-    $("#result-icon").text("error");
+    $("#result-icon").attr("data-icon", "mdi:alert-octagon-outline");
     $("#result-text").text(OPTIONS.error);
     $("#result-box").show();
 }
diff --git a/aleksis/core/static/public/style.scss b/aleksis/core/static/public/style.scss
index e448c8aa54e55d4667c8dde9826c37e70fed6db0..5a35561c389f5656fd573e8f9b698ae456d42ad5 100644
--- a/aleksis/core/static/public/style.scss
+++ b/aleksis/core/static/public/style.scss
@@ -24,6 +24,10 @@ rect#background {
   background-color: lighten($primary-color, 5%);
 }
 
+.waves-effect.waves-secondary .waves-ripple {
+  background-color: lighten($secondary-color, 5%);
+}
+
 .success {
   @extend .light-green, .lighten-3
 }
@@ -77,6 +81,9 @@ header, main, footer {
 .materialize-circle {
   @extend .circle;
 }
+.collection .collection-item.avatar > .materialize-circle > .materialize-circle {
+  left: 0;
+}
 
 /**********/
 /* HEADER */
@@ -211,25 +218,6 @@ div#search-results {
 }
 
 
-// Sidenav trigger
-
-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;
-}
-
-
 // Footer
 
 .footer-icon {
@@ -445,41 +433,17 @@ th.orderable > a {
   height: inherit;
 }
 
-th.orderable > a::after {
-  @extend .material-icons;
-  font-family: 'Material Icons';
-  font-weight: normal;
-  font-style: normal;
-  font-size: 24px;
-  display: inline-block;
-  line-height: 1;
-  text-transform: none;
-  letter-spacing: normal;
-  word-wrap: normal;
-  white-space: nowrap;
-  direction: ltr;
-  -webkit-font-smoothing: antialiased;
-  text-rendering: optimizeLegibility;
-  -moz-osx-font-smoothing: grayscale;
-  font-feature-settings: 'liga';
-  float: right;
-  content: "unfold_more";
+th.orderable {
+  background: no-repeat right center;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M12,18.17L8.83,15L7.42,16.41L12,21L16.59,16.41L15.17,15M12,5.83L15.17,9L16.58,7.59L12,3L7.41,7.59L8.83,9L12,5.83Z' /%3E%3C/svg%3E");
 }
 
-th.orderable.asc > a {
-  color: inherit;
-
-  &::after {
-    content: "expand_less";
-  }
+th.orderable.asc {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M7.41,15.41L12,10.83L16.59,15.41L18,14L12,8L6,14L7.41,15.41Z' /%3E%3C/svg%3E");
 }
 
-th.orderable.desc > a {
-  color: inherit;
-
-  &::after {
-    content: "expand_more";
-  }
+th.orderable.desc {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z' /%3E%3C/svg%3E");
 }
 
 /*+++++++*/
@@ -828,8 +792,8 @@ $person-logo-size: 20vh;
 
   & img {
     border-radius: 50%;
-    width: 20vh;
-    height: 20vh;
+    width: 100%;
+    height: 100%;
     object-fit: cover;
   }
 }
@@ -844,6 +808,64 @@ $person-logo-size: 20vh;
   user-select: none;
   cursor: default;
   border-radius: 50%;
+  height: unset;
+}
+
+.nav-wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 1rem;
+  > a {
+    position: static!important;
+    transform: none!important;
+  }
+  & .nav-spacer {
+    width: 60px;
+  }
+  & ul.account-nav {
+    display: flex;
+    margin-inline: 7.5px;
+    & > li > a {
+      padding: 0 7.5px;
+    }
+  }
+}
+
+.nav-wrapper .navbar-dropdown-trigger {
+  cursor: pointer;
+  height: 100%;
+  display: grid;
+}
+
+.navbar-dropdown-trigger .clip-circle {
+  margin: auto;
+  width: $navbar-height*0.75;
+  height: $navbar-height*0.75;
+  cursor: pointer;
+
+  &.no-image, &.no-image > i.material-icons {
+    font-size: calc(#{$navbar-height} * 0.75 * 0.5);
+    color: #6f6f6f;
+    background: #f2f2f2;
+    line-height: $navbar-height*0.75;
+    width: $navbar-height*0.75;
+    cursor: pointer;
+  }
+}
+
+a.new-notification {
+  position: relative;
+  &:after {
+    content: "";
+    position: absolute;
+    width: 10px;
+    height: 10px;
+    bottom: 30%;
+    right: 19%;
+    background-color: $secondary-color;
+    border-radius: 50%;
+  }
 }
 
 #hero-bg {
@@ -937,3 +959,35 @@ $person-logo-size: 20vh;
   @extend .application-circle;
   object-fit: cover;
 }
+
+svg.iconify {
+  @extend i;
+}
+
+.btn .iconify.material-icons, .btn-flat .iconify.material-icons{
+  height: $button-height;
+}
+
+// Login Page
+.login-card-action {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+  row-gap: 0.7rem;
+  & *:last-child {
+    grid-column: -2;
+    text-align: center;
+  }
+}
+
+.btn-small-line-height {
+  line-height: $button-small-height;
+}
+
+.btn-smaller-padding {
+  padding: 0 8px;
+}
+
+p.ical-description {
+  margin: 0;
+  font-weight: 300;
+}
diff --git a/aleksis/core/tasks.py b/aleksis/core/tasks.py
index 847fc41281a0266cff16bbe2117e326e13abc0dd..7b7e529b3a1fa21c5a6620170296d9ee1eb777ec 100644
--- a/aleksis/core/tasks.py
+++ b/aleksis/core/tasks.py
@@ -4,6 +4,7 @@ from django.conf import settings
 from django.core import management
 
 from .celery import app
+from .util.notifications import _send_due_notifications as _send_due_notifications
 from .util.notifications import send_notification as _send_notification
 
 
@@ -48,3 +49,9 @@ def clear_oauth_tokens():
     from oauth2_provider.models import clear_expired  # noqa
 
     return clear_expired()
+
+
+@app.task(run_every=timedelta(minutes=5))
+def send_notifications():
+    """Send due notifications to users."""
+    _send_due_notifications()
diff --git a/aleksis/core/templates/403.html b/aleksis/core/templates/403.html
index cbc962a93da9f8e8efd91d1af1b493c254834281..03e600b49e533de68bd25e8f07ea6ed4dbc8fb51 100644
--- a/aleksis/core/templates/403.html
+++ b/aleksis/core/templates/403.html
@@ -6,7 +6,7 @@
   <div class="container">
     <div class="card red">
       <div class="card-content white-text">
-        <i class="material-icons small left">error_outline</i>
+        <i class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></i>
         <span class="card-title">
         {% if exception %}
           {{ exception }}
diff --git a/aleksis/core/templates/404.html b/aleksis/core/templates/404.html
index cf68c12bf63624ecf40107ee6f3994b288548c3c..969166bea0fb127705f19d8462569a60b4ae88ea 100644
--- a/aleksis/core/templates/404.html
+++ b/aleksis/core/templates/404.html
@@ -6,7 +6,7 @@
   <div class="container">
     <div class="card red">
       <div class="card-content white-text">
-        <i class="material-icons small left">error_outline</i>
+        <i class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></i>
         <span class="card-title">{{ exception }}</span>
         <p>
           {% blocktrans %}
diff --git a/aleksis/core/templates/500.html b/aleksis/core/templates/500.html
index bf56e08db5b6fd6d6052585ce216bd50cbea3796..fee566258f556cf5ea2922abd170873d3c212733 100644
--- a/aleksis/core/templates/500.html
+++ b/aleksis/core/templates/500.html
@@ -6,7 +6,7 @@
   <div class="container">
     <div class="card red">
       <div class="card-content white-text">
-        <i class="material-icons small left">error_outline</i>
+        <i class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></i>
         <span class="card-title">{% trans "Error" %} (500): {% blocktrans %}An unexpected error has
           occured.{% endblocktrans %}</span>
         <p>
diff --git a/aleksis/core/templates/503.html b/aleksis/core/templates/503.html
index 97d76caa4fab9e641bf522779f7c282a9ab2d2dd..21578e63238f09081e35081b4ed45cf1e0a47a1c 100644
--- a/aleksis/core/templates/503.html
+++ b/aleksis/core/templates/503.html
@@ -6,7 +6,7 @@
   <div class="container">
     <div class="card red">
       <div class="card-content white-text">
-        <i class="material-icons small left">error_outline</i>
+        <i class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></i>
         <span class="card-title">{% blocktrans %}The maintenance mode is currently enabled. Please try again
           later.{% endblocktrans %}</span>
         <p>
diff --git a/aleksis/core/templates/account/account_inactive.html b/aleksis/core/templates/account/account_inactive.html
index 5c328abcfbca48c6499f71b4b11fac5f1f91ccad..e5fc162e6eea9c1a3418d2a151880dfcdd9003f9 100644
--- a/aleksis/core/templates/account/account_inactive.html
+++ b/aleksis/core/templates/account/account_inactive.html
@@ -10,7 +10,7 @@
     <div class="card red">
       <div class="card-content white-text">
         <div class="card-title">
-          <i class="material-icons small left">error_outline</i>
+          <i class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></i>
           {% blocktrans %}Account inactive.{% endblocktrans %}
         </div>
         <p>
diff --git a/aleksis/core/templates/account/email_confirm.html b/aleksis/core/templates/account/email_confirm.html
index 0f560556b6567f0a2133d0efb5c324730761243c..189df8516b9446550783fd8ecb7d2751e38dd2d3 100644
--- a/aleksis/core/templates/account/email_confirm.html
+++ b/aleksis/core/templates/account/email_confirm.html
@@ -15,13 +15,13 @@
       {% csrf_token %}
       {% form form=form %}{% endform %}
       {% trans "Confirm" as caption %}
-      {% include "core/partials/save_button.html" with caption=caption icon="how_to_reg" %}
+      {% include "core/partials/save_button.html" with caption=caption icon="mdi:account-plus-outline" %}
     </form>
   {% else %}
     {% url "account_email" as email_url %}
     <div class="alert warning">
       <p>
-        <i class="material-icons left">warning</i>
+        <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
         {% blocktrans %}This e-mail confirmation link expired or is invalid. Please <a href="{{ email_url }}">issue a new e-mail confirmation request</a>.{% endblocktrans %}
       </p>
     </div>
diff --git a/aleksis/core/templates/account/password_change.html b/aleksis/core/templates/account/password_change.html
index be331751b31187a792b72cf97a178ddddf4de42b..3bcf728890412e39f69929db3d85c945343a5126 100644
--- a/aleksis/core/templates/account/password_change.html
+++ b/aleksis/core/templates/account/password_change.html
@@ -8,7 +8,7 @@
 {% block content %}
   <div class="alert warning">
     <p>
-      <i class="material-icons left">warning</i>
+      <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
       {% trans "Forgot your current password? Click here to reset it:" %} <a href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>.
     </p>
   </div>
@@ -17,7 +17,7 @@
     {% csrf_token %}
     {% form form=form %}{% endform %}
     {% trans "Change password" as caption %}
-    {% include "core/partials/save_button.html" with caption=caption icon="priority_high" %}
+    {% include "core/partials/save_button.html" with caption=caption icon="mdi:exclamation" %}
   </form>
 
 {% endblock %}
diff --git a/aleksis/core/templates/account/password_change_disabled.html b/aleksis/core/templates/account/password_change_disabled.html
index 131df489102ec8d257f584892ceb94f04d8e93b1..996ecfa9fa6368ab45772be4064dc058ac59567f 100644
--- a/aleksis/core/templates/account/password_change_disabled.html
+++ b/aleksis/core/templates/account/password_change_disabled.html
@@ -9,7 +9,7 @@
   <div class="container">
     <div class="card red">
       <div class="card-content white-text">
-        <div class="material-icons small left">error_outline</div>
+        <div class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></div>
         <span class="card-title">{% blocktrans %}Changing of password disabled.{% endblocktrans %}</span>
         <p>
           {% blocktrans %}
diff --git a/aleksis/core/templates/account/password_reset.html b/aleksis/core/templates/account/password_reset.html
index 4e93262643e0e28919e405c9b2f0465bf2a2bcf8..6253e0df3643b9fb6434bee35944583c40056b15 100644
--- a/aleksis/core/templates/account/password_reset.html
+++ b/aleksis/core/templates/account/password_reset.html
@@ -15,13 +15,13 @@
             <div class="card-title">{% trans "Reset password" %}</div>
               <p class="margin-bottom">
                 {% blocktrans %}Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it.{% endblocktrans %}
-              </p> 
+              </p>
               {% csrf_token %}
               {% form form=form %}{% endform %}
-            </div>  
+            </div>
           <div class="card-action-light">
             {% trans "Reset password" as caption %}
-            {% include "core/partials/save_button.html" with caption=caption icon="priority_high" %}
+            {% include "core/partials/save_button.html" with caption=caption icon="mdi:exclamation" %}
           </div>
         </form>
       </div>
diff --git a/aleksis/core/templates/account/password_reset_done.html b/aleksis/core/templates/account/password_reset_done.html
index ddd8b3871c242e8ee2eb9b22cc472778a3b096b9..f97816fea191b4435d53ea6ea61884186883ccbb 100644
--- a/aleksis/core/templates/account/password_reset_done.html
+++ b/aleksis/core/templates/account/password_reset_done.html
@@ -11,7 +11,7 @@
     <div class="card green">
       <div class="card-content white-text">
         <div class="card-title">
-          <i class="material-icons small left">check_circle</i>
+          <i class="material-icons iconify small left" data-icon="mdi:check-circle-outline"></i>
           {% blocktrans %}Password reset mail sent{% endblocktrans %}
         </div>
         <p>
diff --git a/aleksis/core/templates/account/password_reset_from_key.html b/aleksis/core/templates/account/password_reset_from_key.html
index 4b2b91a28c875e7c1337fd0543646eae881da356..7afae12f4b0141d0a8a9af2ba2ecb33e893c41a0 100644
--- a/aleksis/core/templates/account/password_reset_from_key.html
+++ b/aleksis/core/templates/account/password_reset_from_key.html
@@ -11,7 +11,7 @@
       <div class="card red">
         <div class="card-content white-text">
           <div class="card-title">
-            <i class="material-icons small left">error_outline</i>
+            <i class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></i>
             {% blocktrans %}Bad token{% endblocktrans %}
           </div>
           <p>
@@ -44,7 +44,7 @@
                 {% form form=form %}{% endform %}
               <div class="card-action-light">
                 {% trans "Change password" as caption %}
-                {% include "core/partials/save_button.html" with caption=caption icon="priority_high" %}
+                {% include "core/partials/save_button.html" with caption=caption icon="mdi:exclamation" %}
               </div>
             </div>
           </form>
@@ -52,7 +52,7 @@
       </div>
       <div class="alert success">
         <p>
-          <i class="material-icons left">success</i>
+          <i class="material-icons iconify left" data-icon="mdi:check"></i>
           {% blocktrans %}
             Your password is now changed!
           {% endblocktrans %}
diff --git a/aleksis/core/templates/account/password_reset_from_key_done.html b/aleksis/core/templates/account/password_reset_from_key_done.html
index b32538e53ee5b1b0f2115e645d0f955d235ba958..8fdd9232f065d093549358c485b523a40d83a996 100644
--- a/aleksis/core/templates/account/password_reset_from_key_done.html
+++ b/aleksis/core/templates/account/password_reset_from_key_done.html
@@ -9,7 +9,7 @@
   <div class="container">
     <div class="card green">
       <div class="card-content white-text">
-        <div class="material-icons small left">success</div>
+        <i class="material-icons iconify small left" data-icon="mdi:check"></i>
         <span class="card-title">{% blocktrans %}Password changed!{% endblocktrans %}</span>
         <p>
           {% blocktrans %}
diff --git a/aleksis/core/templates/account/password_set.html b/aleksis/core/templates/account/password_set.html
index 08819c1e0904f53e62f11d86bd2e82ccef7b2469..2c550502354719da4959adb7c9b12739378b1c23 100644
--- a/aleksis/core/templates/account/password_set.html
+++ b/aleksis/core/templates/account/password_set.html
@@ -10,7 +10,7 @@
     {% csrf_token %}
     {% form form=form %}{% endform %}
     {% trans "Set password" as caption %}
-    {% include "core/partials/save_button.html" with caption=caption icon="priority_high" %}
+    {% include "core/partials/save_button.html" with caption=caption icon="mdi:exclamation" %}
   </form>
 
 {% endblock %}
diff --git a/aleksis/core/templates/account/signup.html b/aleksis/core/templates/account/signup.html
index 8885e3902af13c609a69a595df3d125b66dc79ce..eb606114eae4fbbab8cab0543a59ce549e7bb35d 100644
--- a/aleksis/core/templates/account/signup.html
+++ b/aleksis/core/templates/account/signup.html
@@ -8,7 +8,7 @@
 {% block content %}
   <div class="alert warning">
     <p>
-      <i class="material-icons left">warning</i>
+      <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
       {% blocktrans %}Already have an account? Then please <a href="{{ login_url }}">sign in</a>.{% endblocktrans %}
     </p>
   </div>
@@ -20,7 +20,7 @@
       <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
     {% endif %}
     {% trans "Sign up" as caption %}
-    {% include "core/partials/save_button.html" with caption=caption icon="how_to_reg" %}
+    {% include "core/partials/save_button.html" with caption=caption icon="mdi:account-plus-outline" %}
   </form>
 
 {% endblock %}
diff --git a/aleksis/core/templates/account/signup_closed.html b/aleksis/core/templates/account/signup_closed.html
index 0d5cbd51095e9c24fceacbc4ba8f6a510a5705a9..ca0a124ec3922d6a530bec9548863cb981a0b629 100644
--- a/aleksis/core/templates/account/signup_closed.html
+++ b/aleksis/core/templates/account/signup_closed.html
@@ -10,7 +10,7 @@
     <div class="card red">
       <div class="card-content white-text">
         <div class="card-title">
-        <i class="material-icons small left">error_outline</i>
+        <i class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></i>
           {% blocktrans %}Signup closed.{% endblocktrans %}
         </div>
         <p>
diff --git a/aleksis/core/templates/account/verification_email_required.html b/aleksis/core/templates/account/verification_email_required.html
index b1a4a482a3ee64b0f2c6339e76ed44c66b29ef50..d1f826fff71270f8b207e03e070ac1373ec85924 100644
--- a/aleksis/core/templates/account/verification_email_required.html
+++ b/aleksis/core/templates/account/verification_email_required.html
@@ -10,7 +10,7 @@
   <div class="container">
     <div class="card green">
       <div class="card-content white-text">
-        <div class="material-icons small left">success</div>
+        <div class="material-icons iconify small left" data-icon="mdi:check"></div>
         <span class="card-title">{% blocktrans %}Password reset mail sent!{% endblocktrans %}</span>
         <p>
           {% blocktrans %}
diff --git a/aleksis/core/templates/account/verification_sent.html b/aleksis/core/templates/account/verification_sent.html
index df3ae47c82911a01997ac9f4d7919c9e1b95a36e..65bb4c188c3279a5d2bf4bdab37edf92b9fa7760 100644
--- a/aleksis/core/templates/account/verification_sent.html
+++ b/aleksis/core/templates/account/verification_sent.html
@@ -10,7 +10,7 @@
   <div class="container">
     <div class="card red">
       <div class="card-content white-text">
-        <div class="material-icons small left">error_outline</div>
+        <div class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></div>
         <span class="card-title">{% blocktrans %}Verify your email!{% endblocktrans %}</span>
         <p>
           {% blocktrans %}
diff --git a/aleksis/core/templates/components/materialize-chips.html b/aleksis/core/templates/components/materialize-chips.html
index e02662adc354dcb1e3784804fdf46783b282ea39..c4bea23ae6ec79b4e3598053b81348cb6286254f 100644
--- a/aleksis/core/templates/components/materialize-chips.html
+++ b/aleksis/core/templates/components/materialize-chips.html
@@ -3,10 +3,10 @@
     <img class="{{ img_classes }}" src="{{ img }}" alt="{{ alt }}">
   {% endif %}
   {% if icon %}
-    <i class="material-icons left">{{ icon }}</i>
+    <i class="material-icons iconify left" data-icon="{{ icon }}"></i>
   {% endif %}
   {{ content }}
   {% if close %}
-    <i class="close material-icons"></i>
+    <i class="material-icons iconify" data-icon="mdi:close"></i>
   {% endif %}
 </div>
diff --git a/aleksis/core/templates/components/msgbox.html b/aleksis/core/templates/components/msgbox.html
index 8903ebd1a750f1965326cd59499605872b997fc0..de7b12e5eafdfa0974989c20a86dfda3c8372517 100644
--- a/aleksis/core/templates/components/msgbox.html
+++ b/aleksis/core/templates/components/msgbox.html
@@ -2,7 +2,7 @@
   <div class="alert {{ status }}">
     <div>
       {% if icon != "" %}
-        <i class="material-icons left">{{ icon }}</i>
+        <i class="material-icons iconify left" data-icon="{{ icon }}"></i>
       {% endif %}
       {{ msg }}
     </div>
diff --git a/aleksis/core/templates/components/pagination.html b/aleksis/core/templates/components/pagination.html
index 5eb33cdf8fff272d766a628e1656c109ed87e32c..03d05ecf053ddb498d6ab5181f1b774b24e9884a 100644
--- a/aleksis/core/templates/components/pagination.html
+++ b/aleksis/core/templates/components/pagination.html
@@ -5,7 +5,7 @@
     {% if page.has_previous %}
       <li class="waves-effect">
         <a href="?page={{ page.previous_page_number }}" class="page-link">
-          <i class="material-icons">chevron_left</i>
+          <i class="material-icons iconify" data-icon="mdi:chevron-right"></i>
         </a>
       </li>
     {% endif %}
@@ -21,7 +21,7 @@
     {% if page.has_next %}
       <li class="waves-effect">
         <a href="?page={{ page.next_page_number }}" class="page-link">
-          <i class="material-icons">chevron_right</i>
+          <i class="material-icons iconify" data-icon="mdi:chevron-right"></i>
         </a>
       </li>
     {% endif %}
diff --git a/aleksis/core/templates/components/text_collapsible.html b/aleksis/core/templates/components/text_collapsible.html
index 7096e8e9311d7fdcd5f6cffb6af0934fa9d56977..bceac0eb58eb83cc9def914fce97d625ac13802a 100644
--- a/aleksis/core/templates/components/text_collapsible.html
+++ b/aleksis/core/templates/components/text_collapsible.html
@@ -3,11 +3,11 @@
       {% include template with item=qs.first %} –
       {% include template with item=qs.last %}
     </span>
-    <i class="material-icons open-icon" title="Show more">add_circle_outline</i>
+    <i class="material-icons iconify open-icon" title="Show more">plus-circle-outline</i>
     <span class="b">
       {% for item in qs %}
         {% include template with item=item %}
       {% endfor %}
     </span>
-    <i class="material-icons close-icon" title="Show less">remove_circle_outline</i>
+    <i class="material-icons iconify close-icon" title="Show less">minus-circle-outline</i>
 </span>
diff --git a/aleksis/core/templates/core/additional_field/list.html b/aleksis/core/templates/core/additional_field/list.html
index 1ba2a77a32c528b8443f4dc5a4c201b5739414fe..13f320520f2c695e315dd28cec9c78d367d1b6bf 100644
--- a/aleksis/core/templates/core/additional_field/list.html
+++ b/aleksis/core/templates/core/additional_field/list.html
@@ -10,7 +10,7 @@
 
 {% block content %}
   <a class="btn green waves-effect waves-light" href="{% url 'create_additional_field' %}">
-    <i class="material-icons left">add</i>
+    <i class="material-icons left iconify" data-icon="mdi:add"></i>
     {% trans "Create additional field" %}
   </a>
 
diff --git a/aleksis/core/templates/core/announcement/form.html b/aleksis/core/templates/core/announcement/form.html
index 44d60280c6d358f74c60cb9c5ccff2c7aba89b07..f085758a3395ca74de1dba8e0affec9fb453cc03 100644
--- a/aleksis/core/templates/core/announcement/form.html
+++ b/aleksis/core/templates/core/announcement/form.html
@@ -30,7 +30,7 @@
     {% form form=form %}{% endform %}
 
     <button type="submit" class="btn green waves-effect waves-light">
-      <i class="material-icons left">save</i>
+      <i class="material-icons left iconify" data-icon="mdi:content-save-outline"></i>
       {% trans "Save und publish announcement" %}
     </button>
   </form>
diff --git a/aleksis/core/templates/core/announcement/list.html b/aleksis/core/templates/core/announcement/list.html
index 9f84617e255c78b7ee37e502c212e0704aa1da5e..763f4f10cce6c240f80812502c7a378bbdb3a0f7 100644
--- a/aleksis/core/templates/core/announcement/list.html
+++ b/aleksis/core/templates/core/announcement/list.html
@@ -9,7 +9,7 @@
 
 {% block content %}
   <a class="btn green waves-effect waves-light" href="{% url "add_announcement" %}">
-    <i class="material-icons left">add</i>
+    <i class="material-icons left iconify" data-icon="mdi:add"></i>
     {% trans "Publish new announcement" %}
   </a>
   <table class="highlight">
@@ -32,13 +32,13 @@
         <td>
           <a class="btn-flat waves-effect waves-orange orange-text"
              href="{% url "edit_announcement" announcement.id %}">
-            <i class="material-icons left">edit</i>
+            <i class="material-icons left iconify" data-icon="mdi:pencil-outline"></i>
             {% trans "Edit" %}
           </a>
           <form action="{% url "delete_announcement" announcement.id %}" method="post">
             {% csrf_token %}
             <button class="btn-flat waves-effect waves-re red-text" type="submit">
-              <i class="material-icons left">delete</i>
+              <i class="material-icons left iconify" data-icon="mdi:delete-outline"></i>
               {% trans "Delete" %}
             </button>
           </form>
diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html
index 745a6668cdc42eb265536950a0acbecfb57ee537..3158986ac5ed921244b43663e64c2fe9d728cd32 100644
--- a/aleksis/core/templates/core/base.html
+++ b/aleksis/core/templates/core/base.html
@@ -53,6 +53,12 @@
     </script>
   {% endif %}
 
+  <script type="text/javascript" src="{% url 'config.js' %}"></script>
+  {% include_js "iconify" %}
+
+  <script type="text/javascript" src="{% url 'config.js' %}"></script>
+  {% include_js "iconify" %}
+
   {# Include jQuery early to provide $(document).ready #}
   {% include_js "jQuery" %}
 
@@ -61,32 +67,60 @@
 <body {% if no_menu %}class="without-menu"{% endif %}>
 
 <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="nav-extended">
     <div class="nav-wrapper">
+      <a href="#" data-target="slide-out" class="top-nav sidenav-trigger hide-on-large-only">
+        <i class="material-icons iconify" data-icon="mdi:menu"></i>
+      </a>
+
       <a class="brand-logo" href="/">{{ request.site.preferences.general__title }}</a>
 
-      <ul id="nav-mobile" class="right hide-on-med-and-down">
-        {% if user.is_authenticated %}
-          <li>{% trans "Logged in as" %} {{ user.get_username }}</li>
+      {% if user.is_authenticated %}
+        <ul class="account-nav">
+          {% trans "Notifications" as notifications_text %}
           <li>
-            <a href="{% url 'logout' %}">{% trans "Logout" %} <i class="material-icons right">exit_to_app</i></a>
+            <a href="{% url "notifications" %}" data-position="bottom"
+               class="tooltipped {% if request.user.person.unread_notifications_count > 0 %}new-notification{% endif %}"
+               data-tooltip="{{ notifications_text }}" aria-label="{{ notifications_text }}">
+              <i class="material-icons iconify" data-icon="mdi:bell-outline"></i>
+            </a>
           </li>
-        {% endif %}
-      </ul>
+          <li>
+            <a href="#!" class="navbar-dropdown-trigger" data-target="account-dropdown">
+              {{ request.user.person.identicon }}
+              {% include "core/partials/avatar_content.html" with person_or_user=request.user.person %}
+            </a>
+          </li>
+        </ul>
+      {% else %}
+        <span class="nav-spacer"></span>
+      {% endif %}
     </div>
     <div class="nav-content">
       {% block nav_content %}{% endblock %}
     </div>
   </nav>
 
+  {% get_menu "NAVBAR_ACCOUNT_MENU" as account_menu %}
+  <ul id="account-dropdown" class="dropdown-content">
+    {% for item in account_menu %}
+      {% if item.divider %}
+        <li class="divider"></li>
+      {% endif %}
+      <li>
+        <a href="{{ item.url }}">
+          {% if item.icon %}
+            <i class="material-icons">{{ item.icon }}</i>
+          {% elif item.svg_icon %}
+            <i class="material-icons iconify" data-icon="{{ item.svg_icon }}"></i>
+          {% endif %}
+          {{ item.name }}
+        </a>
+      </li>
+    {% endfor %}
+  </ul>
+
   <!-- Main nav (sidenav) -->
   {% if not no_menu %}
     <ul id="slide-out" class="sidenav sidenav-fixed">
@@ -104,7 +138,7 @@
             <div class="search-wrapper">
               <input id="search" name="q" type="search" enterkeyhint="search" placeholder="{% trans "Search" %}">
               <button class="btn btn-flat search-button" type="submit" aria-label="{% trans "Search" %}">
-                <i class="material-icons">search</i>
+                <i class="material-icons iconify" data-icon="mdi:search">search</i>
               </button>
               <div class="progress" id="search-loader">
                 <div class="indeterminate"></div>
@@ -127,16 +161,16 @@
   {% if messages %}
     {% for message in messages %}
       <figure class="alert {% if message.tags %}{{ message.tags }}{% else %}info{% endif %}">
-          {% 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 }}
+        {% if message.tags == "success" %}
+          <i class="material-icons left iconify" data-icon="mdi:check-circle-outline"></i>
+        {% elif message.tags == "info" %}
+          <i class="material-icons left iconify" data-icon="mdi:information-outline"></i>
+        {% elif message.tags == "warning" %}
+          <i class="material-icons left iconify" data-icon="mdi:alert-outline"></i>
+        {% elif message.tags == "error" %}
+          <i class="material-icons left iconify" data-icon="mdi:alert-octagon-outline"></i>
+        {% endif %}
+        {{ message }}
       </figure>
     {% endfor %}
   {% endif %}
diff --git a/aleksis/core/templates/core/dashboard_widget/dashboardwidget_broken.html b/aleksis/core/templates/core/dashboard_widget/dashboardwidget_broken.html
index 23a18714637bf90d9610547210b412eec6fae729..950f925e138e1f3583e0099926fbcbaf79f581e3 100644
--- a/aleksis/core/templates/core/dashboard_widget/dashboardwidget_broken.html
+++ b/aleksis/core/templates/core/dashboard_widget/dashboardwidget_broken.html
@@ -2,7 +2,7 @@
 
 <div class="card horizontal">
   <div class="card-image">
-    <i class="material-icons large red-text stacked-card-icon">assignment_late</i>
+    <i class="material-icons large red-text stacked-card-icon iconify" data-icon="mdi:clipboard-alert-outline"></i>
   </div>
   <div class="card-stacked">
     <div class="card-content">
diff --git a/aleksis/core/templates/core/dashboard_widget/list.html b/aleksis/core/templates/core/dashboard_widget/list.html
index ec5fac70d20c3997d2a02128691b947719eb4309..78356cfed3e3b3cce6358855cbdf615d07712a0d 100644
--- a/aleksis/core/templates/core/dashboard_widget/list.html
+++ b/aleksis/core/templates/core/dashboard_widget/list.html
@@ -11,7 +11,7 @@
 {% block content %}
 
   <a class="btn green waves-effect waves-light dropdown-trigger" href="#" data-target="widget-dropdown">
-    <i class="material-icons left">add</i>
+    <i class="material-icons left iconify" data-icon="mdi:add"></i>
     {% trans "Create dashboard widget" %}
   </a>
   <ul id="widget-dropdown" class="dropdown-content">
@@ -28,7 +28,7 @@
   {% has_perm "core.edit_default_dashboard_rule" user as can_edit_default_dashboard %}
   {% if can_edit_default_dashboard %}
     <a class="btn orange waves-effect waves-light" href="{% url "edit_default_dashboard" %}">
-      <i class="material-icons left">edit</i>
+      <i class="material-icons left iconify" data-icon="mdi:pencil-outline"></i>
       {% trans "Edit default dashboard" %}
     </a>
   {% endif %}
diff --git a/aleksis/core/templates/core/dashboard_widget/static_content_widget.html b/aleksis/core/templates/core/dashboard_widget/static_content_widget.html
new file mode 100644
index 0000000000000000000000000000000000000000..96e5549d5890e4cc95dbe6b4a843f1b1612c5d7f
--- /dev/null
+++ b/aleksis/core/templates/core/dashboard_widget/static_content_widget.html
@@ -0,0 +1,10 @@
+{% load html_helpers %}
+
+<div class="card" style="padding: 2em">
+	<div class="card-title">
+		{{ title }}
+	</div>
+        <div class="card-text">
+		{{ content|add_class_to_el:"ul, browser-default"|safe }}
+        </div>
+</div>
diff --git a/aleksis/core/templates/core/data_check/list.html b/aleksis/core/templates/core/data_check/list.html
index 36d83d4cd7d3c917db98c104586bd2aa9c82c139..b192dcdc1623a05fa3ad7b3568b4b8bdc3622abf 100644
--- a/aleksis/core/templates/core/data_check/list.html
+++ b/aleksis/core/templates/core/data_check/list.html
@@ -11,14 +11,14 @@
 
 {% block content %}
   <a class="btn green waves-effect waves-light" href="{% url "data_check_run" %}">
-    <i class="material-icons left">refresh</i>
+    <i class="material-icons left iconify" data-icon="mdi:refresh"></i>
     {% trans "Check data again" %}
   </a>
 
   {% if results %}
     <div class="card">
       <div class="card-content">
-        <i class="material-icons left medium red-text">warning</i>
+        <i class="material-icons left medium red-text iconify" data-icon="mdi:alert-outline"></i>
         <span class="card-title">{% trans "The system detected some problems with your data." %}</span>
         <p>{% blocktrans %}Please go through all data and check whether some extra action is
           needed.{% endblocktrans %}</p>
@@ -27,7 +27,7 @@
   {% else %}
     <div class="card">
       <div class="card-content">
-        <i class="material-icons left medium green-text">check_circle</i>
+        <i class="material-icons left medium green-text iconify" data-icon="mdi:check-circle-outline"></i>
         <span class="card-title">{% trans "Everything is fine." %}</span>
         <p>{% blocktrans %}The system hasn't detected any problems with your data.{% endblocktrans %}</p>
       </div>
@@ -86,7 +86,7 @@
       <div class="card-title">{% trans "Registered checks" %}</div>
       <div class="alert primary">
         <div>
-          <i class="material-icons left">info</i>
+          <i class="material-icons left iconify" data-icon="mdi:information-outline"></i>
           {% blocktrans %}
             The system will check for the following problems:
           {% endblocktrans %}
@@ -95,7 +95,7 @@
       <ul class="collection">
         {% for check in registered_checks %}
           <li class="collection-item">
-            <i class="material-icons left">check</i>
+            <i class="material-icons left iconify" data-icon="mdi:check"></i>
             {{ check.verbose_name }}
           </li>
         {% endfor %}
diff --git a/aleksis/core/templates/core/edit_dashboard.html b/aleksis/core/templates/core/edit_dashboard.html
index da50adeea4b2380075113753a9626ef3cbb30fbe..ba4614db61abf50ba8c4bfbabb5383175351c8d8 100644
--- a/aleksis/core/templates/core/edit_dashboard.html
+++ b/aleksis/core/templates/core/edit_dashboard.html
@@ -19,7 +19,7 @@
 {% block content %}
   <div class="alert primary">
     <p>
-      <i class="material-icons left">info</i>
+      <i class="material-icons iconify left" data-icon="mdi:information-outline"></i>
       {% if not default_dashboard %}
         {% blocktrans %}
           On this page you can arrange your personal dashboard. You can drag any items from "Available widgets" to "Your
diff --git a/aleksis/core/templates/core/group/child_groups.html b/aleksis/core/templates/core/group/child_groups.html
index 900df71a59f24817bbec128b5ec15748b5a9d752..a9b93355b8d775b32d3c20123980828fdcae0fd7 100644
--- a/aleksis/core/templates/core/group/child_groups.html
+++ b/aleksis/core/templates/core/group/child_groups.html
@@ -14,7 +14,7 @@
   {% if not page %}
     <div class="alert info">
       <p>
-        <i class="material-icons left">info</i>
+        <i class="material-icons left iconify" data-icon="mdi:information-outline"></i>
         {% blocktrans %}
           You can use this to assign child groups to groups. Please use the filters below to select groups you want to
           change and click "Next".
@@ -27,11 +27,11 @@
       {% form form=filter.form %}{% endform %}
 
       <button type="submit" class="btn green waves-effect waves-light">
-        <i class="material-icons left">refresh</i>
+        <i class="material-icons left iconify" data-icon="mdi:refresh"></i>
         {% trans "Update selection" %}
       </button>
       <a href="{% url "groups_child_groups" %}" class="btn red waves-effect waves-light">
-        <i class="material-icons left">clear</i>
+        <i class="material-icons left iconify" data-icon="mdi:close"></i>
         {% trans "Clear all filters" %}
       </a>
     </form>
@@ -50,14 +50,14 @@
         {% csrf_token %}
         <button class="btn btn-primary waves-effect waves-light" type="submit" name="page" value="1">
           {% trans "Start assigning child groups for this groups" %}
-          <i class="material-icons right">arrow_forward</i>
+          <i class="material-icons left iconify" data-icon="mdi:arrow-right"></i>
         </button>
       </form>
       </p>
     {% else %}
       <div class="alert warning">
         <p>
-          <i class="material-icons left">warning</i>
+          <i class="material-icons left iconify" data-icon="mdi:pencil-outline"></i>
           {% blocktrans %}
             Please select some groups in order to go on with assigning.
           {% endblocktrans %}
diff --git a/aleksis/core/templates/core/group/full.html b/aleksis/core/templates/core/group/full.html
index 65d94ad13a7e563302e9ae283fe4df24c6135353..3c6e5488d6174e30847eabcfda2e97b244112938 100644
--- a/aleksis/core/templates/core/group/full.html
+++ b/aleksis/core/templates/core/group/full.html
@@ -20,21 +20,21 @@
     <p>
       {% if can_change_group %}
         <a href="{% url 'edit_group_by_id' group.id %}" class="btn waves-effect waves-light">
-          <i class="material-icons left">edit</i>
+          <i class="material-icons iconify left" data-icon="mdi:pencil-outline"></i>
           {% trans "Edit" %}
         </a>
       {% endif %}
 
       {% if can_delete_group %}
         <a href="{% url 'delete_group_by_id' group.id %}" class="btn waves-effect waves-light red">
-          <i class="material-icons left">delete</i>
+          <i class="material-icons iconify left" data-icon="mdi:delete-outline"></i>
           {% trans "Delete" %}
         </a>
       {% endif %}
 
       {% if can_change_group_preferences %}
         <a href="{% url "preferences_group" group.id %}" class="btn waves-effect waves-light">
-          <i class="material-icons left">settings</i>
+          <i class="material-icons iconify left" data-icon="mdi:cog-outline"></i>
           {% trans "Change preferences" %}
         </a>
       {% endif %}
@@ -44,7 +44,7 @@
   <table>
     <tr>
       <th>
-        <i class="material-icons center" title="{% trans "Group type" %}">category</i>
+        <i class="material-icons iconify center" data-icon="mdi:shape-outline" title="{% trans "Group type" %}"></i>
       </th>
       <td>
         {{ group.group_type }}
@@ -52,7 +52,7 @@
     </tr>
     <tr>
       <th>
-        <i class="material-icons center" title="{% trans "Parent groups" %}">vertical_align_top</i>
+        <i class="material-icons iconify center" data-icon="mdi:format-vertical-align-top" title="{% trans "Parent groups" %}"></i>
       </th>
       <td>
         {{ group.parent_groups.all|join:", " }}
diff --git a/aleksis/core/templates/core/group/list.html b/aleksis/core/templates/core/group/list.html
index 9fe2c925f79f423cba0e8d195fb0299cca1c906b..38391cad82a3ff007b02bdca0f6cdda06d53ca2c 100644
--- a/aleksis/core/templates/core/group/list.html
+++ b/aleksis/core/templates/core/group/list.html
@@ -10,7 +10,7 @@
 
 {% block content %}
   <a class="btn green waves-effect waves-light" href="{% url 'create_group' %}">
-    <i class="material-icons left">add</i>
+    <i class="material-icons iconify left" data-icon="mdi:add"></i>
     {% trans "Create group" %}
   </a>
 
@@ -20,7 +20,7 @@
     {% trans "Search" as caption %}
     {% include "core/partials/save_button.html" with caption=caption icon="search" %}
     <button type="reset" class="btn red waves-effect waves-light">
-      <i class="material-icons left">clear</i>
+      <i class="material-icons iconify left" data-icon="mdi:close"></i>
       {% trans "Clear" %}
     </button>
   </form>
diff --git a/aleksis/core/templates/core/group_type/list.html b/aleksis/core/templates/core/group_type/list.html
index 6344416b1a8292fd5400e57d63c056e1b2839ef8..e862cb0191533178546b7b481b0db3ce26fa70a0 100644
--- a/aleksis/core/templates/core/group_type/list.html
+++ b/aleksis/core/templates/core/group_type/list.html
@@ -10,7 +10,7 @@
 
 {% block content %}
   <a class="btn green waves-effect waves-light" href="{% url 'create_group_type' %}">
-    <i class="material-icons left">add</i>
+    <i class="material-icons iconify left" data-icon="mdi:add"></i>
     {% trans "Create group type" %}
   </a>
 
diff --git a/aleksis/core/templates/core/ical/ical_create.html b/aleksis/core/templates/core/ical/ical_create.html
new file mode 100644
index 0000000000000000000000000000000000000000..64ff80083ebab3e61cd108d46ee1bff158cdd236
--- /dev/null
+++ b/aleksis/core/templates/core/ical/ical_create.html
@@ -0,0 +1,20 @@
+{% extends 'core/base.html' %}
+{% load i18n material_form %}
+
+{% block page_title %}{% blocktrans %}Create iCal URL{% endblocktrans %}{% endblock page_title %}
+{% block browser_title %}{% blocktrans %}Create iCal URL{% endblocktrans %}{% endblock browser_title %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+
+    {% form form=form %}{% endform %}
+
+    {% include "core/partials/save_button.html" %}
+    <a href="{% url "ical_feed_list" %}" class="btn red">
+      <i class="material-icons iconify left" data-icon="mdi:close"></i>
+      {% blocktrans %}Cancel{% endblocktrans %}
+    </a>
+  </form>
+
+{% endblock content %}
diff --git a/aleksis/core/templates/core/ical/ical_edit.html b/aleksis/core/templates/core/ical/ical_edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..cd46c475531bc1154f5f36c16ae1476b79e73137
--- /dev/null
+++ b/aleksis/core/templates/core/ical/ical_edit.html
@@ -0,0 +1,20 @@
+{% extends 'core/base.html' %}
+{% load i18n material_form %}
+
+{% block page_title %}{% blocktrans %}Edit iCal URL {{ object }}{% endblocktrans %}{% endblock page_title %}
+{% block browser_title %}{% blocktrans %}Edit iCal URL {{ object }}{% endblocktrans %}{% endblock browser_title %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+
+    {% form form=form %}{% endform %}
+
+    {% include "core/partials/save_button.html" %}
+    <a href="{% url "ical_feed_list" %}" class="btn red">
+      <i class="material-icons iconify left" data-icon="mdi:close"></i>
+      {% blocktrans %}Cancel{% endblocktrans %}
+    </a>
+  </form>
+
+{% endblock content %}
diff --git a/aleksis/core/templates/core/ical/ical_list.html b/aleksis/core/templates/core/ical/ical_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..2388feb2daa9c4b31abb80bc830055e2d2d3efc6
--- /dev/null
+++ b/aleksis/core/templates/core/ical/ical_list.html
@@ -0,0 +1,38 @@
+{% extends 'core/base.html' %}
+{% load i18n msg_box static html_helpers %}
+
+{% block page_title %}{% trans "ICal Feeds" %}{% endblock page_title %}
+{% block browser_title %}{% trans "ICal Feeds" %}{% endblock browser_title %}
+
+{% block content %}
+  {% trans "These are URLs for different Calendar Feeds in the iCal (.ics) format. You can create as many as you want and import them in your calendar software." as msg %}
+  {% msg_box msg=msg status="info" %}
+  <a href="{% url "ical_feed_create" %}" class="btn green">
+  <i class="material-icons iconify left" data-icon="mdi:add"></i>
+  {% trans "Create iCal URL" %}
+  </a>
+  <h2>{% trans "Your iCal URLs" %}</h2>
+  <div class="collection">
+    {% for object in object_list %}
+      <div class="collection-item">
+        <span class="title btn-small-line-height">
+          {{ object }}
+          <a href="{% url "ical_feed_delete" object.pk %}"
+                  class="secondary-content btn-flat btn-small red-text btn-smaller-padding waves-effect waves-red">
+            <i class="material-icons iconify" data-icon="mdi:delete-outline"></i>
+          </a>
+          <a href="{% url "ical_feed_edit" object.pk %}"
+             class="secondary-content btn-flat btn-small primary-color-text btn-smaller-padding waves-effect waves-primary">
+            <i class="material-icons iconify" data-icon="mdi:pencil-outline"></i>
+          </a>
+          {% generate_random_id "ical-copy-" as id %}
+          {% include "core/partials/copy_button.html" with classes="secondary-content btn-flat btn-small secondary-color-text btn-smaller-padding" target=id %}
+        </span>
+        <p class="ical-description">{{ object.ical_feed_object.title }} – {{ object.ical_feed_object.description }}</p>
+        <input type="url" readonly value="https://{{ request.site }}{{ object.get_absolute_url }}"
+               id="{{ id }}">
+      </div>
+    {% endfor %}
+  </div>
+  <script src="{% static "js/copy_button.js" %}" type="text/javascript"></script>
+{% endblock content %}
diff --git a/aleksis/core/templates/core/index.html b/aleksis/core/templates/core/index.html
index 7c9c018ca8102172dbd6e46c41048bcbcff4d46f..766c88278ca688381356c35e79b3424b7fb5ce75 100644
--- a/aleksis/core/templates/core/index.html
+++ b/aleksis/core/templates/core/index.html
@@ -13,7 +13,7 @@
   {% if can_edit_dashboard and show_edit_dashboard_button %}
     <div class="row no-margin">
       <a class="btn-flat waves-effect waves-light right" href="{% url "edit_dashboard" %}">
-        <i class="material-icons left">edit</i>
+        <i class="material-icons left iconify" data-icon="mdi:pencil-outline"></i>
         {% trans "Edit dashboard" %}
       </a>
     </div>
@@ -21,11 +21,11 @@
 
   {% for notification in unread_notifications %}
     <figure class="alert primary scale-transition">
-        <i class="material-icons left">info</i>
+        <i class="material-icons left iconify" data-icon="mdi:information-outline"></i>
 
         <div class="right">
           <a class="btn-flat waves-effect" href="{% url "notification_mark_read" notification.id %}">
-            <i class="material-icons center">close</i>
+            <i class="material-icons center iconify" data-icon="mdi:close"></i>
           </a>
         </div>
 
@@ -65,7 +65,7 @@
                 <span class="badge new primary-color">{{ activity.app }}</span>
                 <span class="title">{{ activity.title }}</span>
                 <p>
-                  <i class="material-icons left">access_time</i> {{ activity.created }}
+                  <i class="material-icons left iconify" data-icon="mdi:clock-outline"></i> {{ activity.created }}
                 </p>
                 <p>
                   {{ activity.description }}
@@ -88,7 +88,7 @@
                 <span class="badge new primary-color">{{ notification.sender }}</span>
                 <span class="title">{{ notification.title }}</span>
                 <p>
-                  <i class="material-icons left">access_time</i> {{ notification.created }}
+                  <i class="material-icons left iconify" data-icon="mdi:clock-outline"></i> {{ notification.created }}
                 </p>
                 <p>
                   {{ notification.description }}
diff --git a/aleksis/core/templates/core/notifications.html b/aleksis/core/templates/core/notifications.html
index 499bbcc4c866142ce9062efd75fab039dbd303f6..60640670e6ca02ae788c4c37ff5f65d7c9969e2d 100644
--- a/aleksis/core/templates/core/notifications.html
+++ b/aleksis/core/templates/core/notifications.html
@@ -13,7 +13,7 @@
           <span class="badge new primary-color">{{ notification.sender }}</span>
           <span class="title">{{ notification.title }}</span>
           <p>
-            <i class="material-icons left">access_time</i> {{ notification.created }}
+            <i class="material-icons iconify left" data-icon="mdi:clock-outline"></i> {{ notification.created }}
           </p>
           <p>
             {{ notification.description|linebreaks }}
diff --git a/aleksis/core/templates/core/pages/delete.html b/aleksis/core/templates/core/pages/delete.html
index db64ee8fc3b977ed9cb74333e1a25834ce3e58b7..9cbe50ca73913a6cd06ac1eca32b39f88941ee78 100644
--- a/aleksis/core/templates/core/pages/delete.html
+++ b/aleksis/core/templates/core/pages/delete.html
@@ -18,7 +18,7 @@
   <form method="post" action="">
     {% csrf_token %}
     <button type="submit" class="btn red waves-effect waves-light">
-      <i class="material-icons left">delete</i>
+      <i class="material-icons iconify left" data-icon="mdi:delete-outline"></i>
       {% trans "Delete" %}
     </button>
   </form>
diff --git a/aleksis/core/templates/core/pages/progress.html b/aleksis/core/templates/core/pages/progress.html
index fca1ddfc2ebe5980ee132de77bdf1baafce24ae0..dc12869c8130a26bf2d5d96f1d30e8a83444bd61 100644
--- a/aleksis/core/templates/core/pages/progress.html
+++ b/aleksis/core/templates/core/pages/progress.html
@@ -23,32 +23,32 @@
       <noscript>
         <div class="alert warning">
           <p>
-            <i class="material-icons left">warning</i>
+            <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
             {% blocktrans %}
               Without activated JavaScript the progress status can't be updated.
             {% endblocktrans %}
           </p>
         </div>
       </noscript>
-    
+
       <div id="messages"></div>
 
       <div id="result-box" style="display: none;">
         <div class="alert" id="result-alert">
           <div>
-            <i class="material-icons left" id="result-icon">check_circle</i>
+            <i class="material-icons iconify left" id="result-icon" data-icon="mdi:check-circle-outline"></i>
             <p id="result-text"></p>
           </div>
         </div>
 
         {% url "index" as index_url %}
         <a class="btn waves-effect waves-light" href="{{ back_url|default:index_url }}">
-          <i class="material-icons left">arrow_back</i>
+          <i class="material-icons iconify left" data-icon="mdi:arrow-left"></i>
           {% trans "Go back" %}
         </a>
         {% if additional_button %}
           <a class="btn waves-effect waves-light" href="{{ additional_button.href }}" id="result-button" style="display: none;">
-            <i class="material-icons left">{{ additional_button.icon|default:"" }}</i>
+            <i class="material-icons iconify left" data-icon="{{ additional_button.icon|default:"" }}"></i>
             {{ additional_button.caption }}
           </a>
         {% endif %}
diff --git a/aleksis/core/templates/core/pages/system_status.html b/aleksis/core/templates/core/pages/system_status.html
index 89c6ed65e3982a0318838a66ef7f6716935d0af3..4eee117807c2b2a45a1271e1f4aa9673189d105d 100644
--- a/aleksis/core/templates/core/pages/system_status.html
+++ b/aleksis/core/templates/core/pages/system_status.html
@@ -15,8 +15,9 @@
       <div class="row">
         {% if maintenance_mode %}
           <a class="btn-flat btn-flat-medium right waves-effect waves-red no-padding"
-             href="{% url 'maintenance_mode_off' %}"><i
-                  class="material-icons small red-text center">power_settings_new</i></a>
+             href="{% url 'maintenance_mode_off' %}">
+            <i class="material-icons iconify small red-text center" data-icon="mdi:power"></i>
+          </a>
           <div>
             <p class="flow-text">{% blocktrans %}Maintenance mode enabled{% endblocktrans %}</p>
             <p class="grey-text">
@@ -28,8 +29,9 @@
           <span class="badge badge-danger mdi mdi-power"><a href="{% url 'maintenance_mode_off' %}"></a></span>
         {% else %}
           <a class="btn-flat btn-flat-medium right waves-effect waves-green no-padding"
-             href="{% url 'maintenance_mode_on' %}"><i
-                  class="material-icons small green-text center">power_settings_new</i></a>
+             href="{% url 'maintenance_mode_on' %}">
+            <i class="material-icons iconify small green-text center" data-icon="mdi:power"></i>
+          </a>
           <div>
             <p class="flow-text">{% blocktrans %}Maintenance mode disabled{% endblocktrans %}</p>
             <p class="grey-text">{% blocktrans %}Everyone can access the site.{% endblocktrans %}</p>
@@ -40,7 +42,7 @@
       {# Debug mode #}
       <div class="row">
         {% if DEBUG %}
-          <i class="material-icons small red-text right">power_settings_new</i>
+          <i class="material-icons iconify small red-text right" data-icon="mdi:power"></i>
           <div>
             <p class="flow-text">{% blocktrans %}Debug mode enabled{% endblocktrans %}</p>
             <p class="grey-text">
@@ -49,7 +51,7 @@
               {% endblocktrans %}</p>
           </div>
         {% else %}
-          <i class="material-icons small green-text right">power_settings_new</i>
+          <i class="material-icons iconify small green-text right" data-icon="mdi:power"></i>
           <div>
             <p class="flow-text">{% blocktrans %}Debug mode disabled{% endblocktrans %}</p>
             <p class="grey-text">
@@ -82,11 +84,13 @@
             <tr>
               <td>
                 <a class="tooltipped" data-position="top" data-tooltip="{{ plugin.pretty_status }}">
-                {% if plugin.status %}
-                  <i class="material-icons green-text" aria-hidden="true" title="{{ plugin.pretty_status }}">check</i>
-                {% else %}
-                  <i class="material-icons red-text" aria-hidden="true" title="{{ plugin.pretty_status }}">warning</i>
-                {% endif %}
+                  {% if plugin.status %}
+                    <i class="material-icons iconify green-text" aria-hidden="true" title="{{ plugin.pretty_status }}"
+                       data-icon="mdi:check"></i>
+                  {% else %}
+                    <i class="material-icons iconify red-text" aria-hidden="true" title="{{ plugin.pretty_status }}"
+                       data-icon="mdi:alert-outline"></i>
+                  {% endif %}
                 </a>
               </td>
               <td>{{ plugin.identifier }}</td>
@@ -126,32 +130,32 @@
                     {% if task.status == "PENDING" %}
                       <a class="tooltipped" data-position="top"
                       data-tooltip="{{ task.status }}">
-                        <i class="material-icons orange-text">hourglass_empty</i>
+                        <i class="material-icons iconify orange-text" data-icon="mdi:timer-sand-empty"></i>
                       </a>
                     {% elif task.status == "STARTED" %}
                       <a class="tooltipped" data-position="top"
                       data-tooltip="{{ task.status }}">
-                        <i class="material-icons orange-text">directions_run</i>
+                        <i class="material-icons iconify orange-text" data-icon="mdi:timer-sand"></i>
                       </a>
                     {% elif task.status == "SUCCESS" %}
                       <a class="tooltipped" data-position="top"
                       data-tooltip="{{ task.status }}">
-                        <i class="material-icons green-text">done</i>
+                        <i class="material-icons iconify green-text" data-icon="mdi:check"></i>
                       </a>
                     {% elif task.status == "FAILURE" %}
                       <a class="tooltipped" data-position="top"
                       data-tooltip="{{ task.status }}">
-                        <i class="material-icons red-text">error</i>
+                        <i class="material-icons iconify red-text" data-icon="mdi:alert-octagon-outline"></i>
                       </a>
                     {% elif task.status == "RETRY" %}
                       <a class="tooltipped" data-position="top"
                       data-tooltip="{{ task.status }}">
-                        <i class="material-icons orange-text">hourglass_full</i>
+                        <i class="material-icons iconify orange-text" data-icon="mdi:timer-sand-full"></i>
                       </a>
                     {% elif task.status == "REVOKED" %}
                       <a class="tooltipped" data-position="top"
                       data-tooltip="{{ task.status }}">
-                        <i class="material-icons red-text">clear</i>
+                        <i class="material-icons iconify red-text" data-icon="mdi:close"></i>
                       </a>
                     {% endif %}
                   </td>
diff --git a/aleksis/core/templates/core/pages/test_pdf.html b/aleksis/core/templates/core/pages/test_pdf.html
index f0c0a6169c85a6f37252136b3777dc0967350e43..1730d10e655759ad193cd7282654374795951721 100644
--- a/aleksis/core/templates/core/pages/test_pdf.html
+++ b/aleksis/core/templates/core/pages/test_pdf.html
@@ -10,7 +10,7 @@
 {% block content %}
   <div class="alert primary">
     <p>
-      <i class="material-icons left">info</i>
+      <i class="material-icons iconify left" data-icon="mdi:information-outline"></i>
       {% blocktrans %}
         This simple view can be used to ensure the correct function of the built-in PDF generation system.
       {% endblocktrans %}
diff --git a/aleksis/core/templates/core/partials/announcements.html b/aleksis/core/templates/core/partials/announcements.html
index e3dcd5165f4149e3d5fa2016487ef771ad84f12d..4a286a5d79224023995f48dc38a43fb27f2988d7 100644
--- a/aleksis/core/templates/core/partials/announcements.html
+++ b/aleksis/core/templates/core/partials/announcements.html
@@ -16,7 +16,7 @@
         </em>
       {% endif %}
 
-      <i class="material-icons left">announcement</i>
+      <i class="material-icons iconify left" data-icon="mdi:message-alert-outline"></i>
 
       {% if show_recipients and announcement.recipients %}
         <p>
diff --git a/aleksis/core/templates/core/partials/avatar_content.html b/aleksis/core/templates/core/partials/avatar_content.html
new file mode 100644
index 0000000000000000000000000000000000000000..deef48489f4ed118a4420f05ea226f87d7c0b895
--- /dev/null
+++ b/aleksis/core/templates/core/partials/avatar_content.html
@@ -0,0 +1,29 @@
+{% load rules i18n %}
+{% has_perm 'core.view_avatar_rule' request.user person_or_user as can_view_avatar %}
+{% has_perm 'core.view_photo_rule' request.user person_or_user as can_view_photo %}
+{% if SITE_PREFERENCES.account__person_prefer_photo and person_or_user.photo and can_view_photo %}
+  <div class="{% firstof class "clip-circle" %}">
+    <img class="{% firstof img_class "hundred-percent" %}" src="{{ person_or_user.photo.url }}"
+         alt="{{ person_or_user.full_name }}" {% if title %} title="{{ person_or_user.full_name }}"{% endif %}/>
+  </div>
+{% elif person_or_user.identicon_url %}
+  {# If this is a person #}
+  <div class="{% firstof class "clip-circle" %}">
+    {% if can_view_avatar %}
+      <img class="{% firstof img_class "hundred-percent" %}" src="{{ person_or_user.avatar_url }}"
+           alt="{{ person_or_user.full_name }} ({% trans "Avatar" %})" {% if title %}
+           title="{{ person_or_user.full_name }} ({% trans "Avatar" %})"{% endif %}/>
+    {% else %}
+
+      <img class="{% firstof img_class "hundred-percent" %}" src="{{ person_or_user.identicon_url }}"
+           alt="{{ person_or_user.full_name }} ({% trans "Identicon" %})" {% if title %}
+           title="{{ person_or_user.full_name }} ({% trans "Identicon" %})"{% endif %} />
+    {% endif %}
+  </div>
+
+{% else %}
+  {# There is a user without a person #}
+  <div class="{% firstof class "clip-circle" %} no-image">
+    <i class="material-icons">person</i>
+  </div>
+{% endif %}
diff --git a/aleksis/core/templates/core/partials/copy_button.html b/aleksis/core/templates/core/partials/copy_button.html
new file mode 100644
index 0000000000000000000000000000000000000000..2ca0168333bc7215a580b05316c4556db436bb12
--- /dev/null
+++ b/aleksis/core/templates/core/partials/copy_button.html
@@ -0,0 +1,5 @@
+<button type="button" data-target="{{ target }}"
+        class="{{ classes }} copy-button waves-effect waves-secondary">
+  <i class="material-icons iconify copy-icon-copy" data-icon="mdi:content-copy"></i>
+  <i class="material-icons iconify copy-icon-success" style="display: none" data-icon="mdi:check"></i>
+</button>
diff --git a/aleksis/core/templates/core/partials/crud_events.html b/aleksis/core/templates/core/partials/crud_events.html
index b0edf14d154690e33a2fac523f5fa38429fcdb5e..d0eba480a1cceec09117f96d7e18c4141729be45 100644
--- a/aleksis/core/templates/core/partials/crud_events.html
+++ b/aleksis/core/templates/core/partials/crud_events.html
@@ -5,9 +5,9 @@
     <div class="collection-item">
       <div class="left" style="margin-right: 10px;">
         {% if forloop.first %}
-          <i class="material-icons">add_circle</i>
+          <i class="material-icons iconify" data-icon="mdi:plus-circle-outline"></i>
         {% else %}
-          <i class="material-icons">edit</i>
+          <i class="material-icons iconify" data-icon="mdi:pencil-circle-outline"></i>
         {% endif %}
       </div>
       <strong>
diff --git a/aleksis/core/templates/core/partials/edit_dashboard_widget.html b/aleksis/core/templates/core/partials/edit_dashboard_widget.html
index b842c0937f05c4e90a4d7337871460130fa43012..1a469c9cee2a129334bc93779521084c6297074c 100644
--- a/aleksis/core/templates/core/partials/edit_dashboard_widget.html
+++ b/aleksis/core/templates/core/partials/edit_dashboard_widget.html
@@ -2,7 +2,7 @@
      data-pk="{{ widget.pk }}">
   <div class="card placeholder">
     <div class="card-content">
-      <i class="material-icons left small">drag_handle</i>
+      <i class="material-icons iconify left small" data-icon="mdi:drag-horizontal-variant"></i>
       <span class="card-title">{{ widget.title }}</span>
     </div>
   </div>
diff --git a/aleksis/core/templates/core/partials/footer-menu.html b/aleksis/core/templates/core/partials/footer-menu.html
index 5a1d268ea2916899bb3d5bddd47e26d4962734ba..2db2689efed0b3488dc7ba4362d2ff6747863f0e 100644
--- a/aleksis/core/templates/core/partials/footer-menu.html
+++ b/aleksis/core/templates/core/partials/footer-menu.html
@@ -3,7 +3,7 @@
 {% for item in FOOTER_MENU.items.all %}
   <a class="blue-text text-lighten-4 btn-flat" href="{{ item.url }}">
     {% if item.icon %}
-      <i class="material-icons footer-icon left">{{ item.icon }}</i>
+      <i class="material-icons iconify footer-icon left" data-icon="{{ item.icon }}">{{ item.icon }}</i>
     {% endif %}
     {{ item.name }}
   </a>
diff --git a/aleksis/core/templates/core/partials/no_person.html b/aleksis/core/templates/core/partials/no_person.html
index 62b398a1af779b0fc17eef8aa2c0e845760a4e5d..d99a1e0cbcbd0d6a39ea8565bbcadcb39d7482ce 100644
--- a/aleksis/core/templates/core/partials/no_person.html
+++ b/aleksis/core/templates/core/partials/no_person.html
@@ -5,7 +5,7 @@
 {% if user.person.is_dummy or not user.person and not user.is_anonymous %}
   <div class="alert error">
     <div>
-      <i class="material-icons left">error</i>
+      <i class="material-icons iconify left" data-icon="mdi:alert-octagon-outline"></i>
 
       {% if user.person.is_dummy %}
         <p>
diff --git a/aleksis/core/templates/core/partials/save_button.html b/aleksis/core/templates/core/partials/save_button.html
index ebc48f2db3d4529f292e3352dbf0cd45ccec2868..3a43323677c44ab6e393c818b451c2b8b846610c 100644
--- a/aleksis/core/templates/core/partials/save_button.html
+++ b/aleksis/core/templates/core/partials/save_button.html
@@ -1,5 +1,6 @@
 {% load i18n %}
 <button type="submit" class="btn waves-effect waves-light green">
   {% trans "Save" as default %}
-  <i class="material-icons left">{{ icon|default:"save" }}</i> {{ caption|default:default }}
+  <i class="material-icons left iconify" data-icon="{{ icon|default:"mdi:content-save-outline" }}"></i>
+  {{ caption|default:default }}
 </button>
diff --git a/aleksis/core/templates/core/partials/sidenav.html b/aleksis/core/templates/core/partials/sidenav.html
index 9b3be97059ca834238d39a4dd6f6bfe35205a9d2..0120f6b71b0f505e6376e4b3a36894d71b38b914 100644
--- a/aleksis/core/templates/core/partials/sidenav.html
+++ b/aleksis/core/templates/core/partials/sidenav.html
@@ -14,6 +14,8 @@
             <i class="{{ item.icon_class }}"></i>
           {% elif item.icon %}
             <i class="material-icons">{{ item.icon }}</i>
+          {% elif item.svg_icon %}
+            <i class="material-icons iconify" data-icon="{{item.svg_icon}}"></i>
           {% endif %}
           {{ item.name }}
           {% build_badge item as badge %}
@@ -30,6 +32,8 @@
             <i class="{{ item.icon_class }}"></i>
           {% elif item.icon %}
             <i class="material-icons">{{ item.icon }}</i>
+          {% elif item.svg_icon %}
+            <i class="material-icons iconify" data-icon="{{item.svg_icon}}"></i>
           {% endif %}
           {{ item.name }}
           {% build_badge item as badge %}
@@ -46,6 +50,8 @@
                     <i class="{{ menu.icon_class }}"></i>
                   {% elif menu.icon %}
                     <i class="material-icons">{{ menu.icon }}</i>
+                  {% elif menu.svg_icon %}
+                    <i class="material-icons iconify" data-icon="{{menu.svg_icon}}"></i>
                   {% endif %}
                   {{ menu.name }}
                   {% build_badge item as badge %}
diff --git a/aleksis/core/templates/core/partials/turnable.html b/aleksis/core/templates/core/partials/turnable.html
index e122b2fa4396282fe0941376161fe3086cda4deb..dabfcbb93ddce6d9323a84757c3753e713c8efea 100644
--- a/aleksis/core/templates/core/partials/turnable.html
+++ b/aleksis/core/templates/core/partials/turnable.html
@@ -2,9 +2,9 @@
 
 <div class="row">
   <a href="{{ url_prev }}" class="btn-flat left">
-    <i class="material-icons small">chevron_left</i>
+    <i class="material-icons iconify small"data-icon="mdi:chevron-left"></i>
   </a>
   <a href="{{ url_next }}" class="btn-flat right">
-    <i class="material-icons small">chevron_right</i>
+    <i class="material-icons iconify small" data-icon="mdi:chevron-right"></i>
   </a>
 </div>
diff --git a/aleksis/core/templates/core/perms/assign.html b/aleksis/core/templates/core/perms/assign.html
index 1833ece952d04f01f2f38440cc0fe8fc1d509bf7..75ca29dbad18c1b84e79638cf1c192a7b6b9707d 100644
--- a/aleksis/core/templates/core/perms/assign.html
+++ b/aleksis/core/templates/core/perms/assign.html
@@ -22,7 +22,7 @@
     <input type="hidden" name="step" value="{{ step }}">
     {% form form=form %}{% endform %}
     <button type="submit" class="btn green waves-effect waves-light">
-      <i class="material-icons left">save</i>
+      <i class="material-icons left iconify" data-icon="mdi:content-save-outline"></i>
       {% trans "Assign" %}
     </button>
   </form>
diff --git a/aleksis/core/templates/core/perms/list.html b/aleksis/core/templates/core/perms/list.html
index 73dcfb8730d5e2235b3b398a4ef0f0fd70c8c31b..319b00285f10753a966047df23669ac40d5b4c95 100644
--- a/aleksis/core/templates/core/perms/list.html
+++ b/aleksis/core/templates/core/perms/list.html
@@ -54,7 +54,7 @@
         {% csrf_token %}
         {% form form=filter.form %}{% endform %}
         <button type="submit" class="btn waves-effect waves-light">
-          <i class="material-icons left">refresh</i>
+          <i class="material-icons left iconify" data-icon="mdi:refresh"></i>
           {% trans "Update" %}
         </button>
       </form>
diff --git a/aleksis/core/templates/core/person/collection.html b/aleksis/core/templates/core/person/collection.html
index fb8518e97e3e8d770260c8e6b004df420d8efa15..c86da61e43ae19ceaa0f601c2f3ec012357bc04d 100644
--- a/aleksis/core/templates/core/person/collection.html
+++ b/aleksis/core/templates/core/person/collection.html
@@ -3,13 +3,7 @@
 <div class="collection person-collection">
   {% for person in persons %}
     <a class="collection-item avatar waves-effect" href="{% url "person_by_id" person.pk %}">
-      {% has_perm 'core.view_photo_rule' user person as can_view_photo %}
-      {% if person.photo and can_view_photo %}
-        <img class="circle" src="{{ person.photo.url }}"
-             alt="{{ person.first_name }} {{ person.last_name }}"/>
-      {% else %}
-        <i class="material-icons materialize-circle">person</i>
-      {% endif %}
+      {% include "core/partials/avatar_content.html" with person_or_user=person class="materialize-circle" img_class="materialize-circle" %}
       {{ person }}
     </a>
   {% endfor %}
diff --git a/aleksis/core/templates/core/person/full.html b/aleksis/core/templates/core/person/full.html
index 23f8ba29b30188dd4cdf5b76af23991a0f618119..9a58951118e4bdafe3278cb7e4280e455fa99943 100644
--- a/aleksis/core/templates/core/person/full.html
+++ b/aleksis/core/templates/core/person/full.html
@@ -22,35 +22,35 @@
       <p class="person-buttons hide-on-med-and-down">
         {% if can_change_person %}
           <a href="{% url 'edit_person_by_id' person.id %}" class="btn waves-effect waves-light">
-            <i class="material-icons left">edit</i>
+            <i class="material-icons left iconify" data-icon="mdi:pencil-outline"></i>
             {% trans "Edit" %}
           </a>
         {% endif %}
 
         {% if can_delete_person %}
           <a href="{% url 'delete_person_by_id' person.id %}" class="btn waves-effect waves-light red">
-            <i class="material-icons left">delete</i>
+            <i class="material-icons left iconify" data-icon="mdi:delete-outline"></i>
             {% trans "Delete" %}
           </a>
         {% endif %}
 
         {% if can_change_person_preferences %}
           <a href="{% url 'preferences_person' person.id %}" class="btn waves-effect waves-light">
-            <i class="material-icons left">settings</i>
+            <i class="material-icons left iconify" data-icon="mdi:cog-outline"></i>
             {% trans "Change preferences" %}
           </a>
         {% endif %}
 
         {% if can_impersonate and person.user %}
           <a href="{% url 'impersonate-start' person.user.id %}" class="btn waves-effect waves-light">
-            <i class="material-icons left">portrait</i>
+            <i class="material-icons left iconify" data-icon="mdi:account-box-outline"></i>
             {% trans "Impersonate" %}
           </a>
         {% endif %}
 
         {% if invite_enabled and can_invite and not person.user %}
           <a href="{% url "invite_person_by_id" person.id %}" class="btn waves-effect waves-light">
-            <i class="material-icons left">card_giftcard</i>
+            <i class="material-icons iconify left" data-icon="mdi:account-plus-outline"></i>
             {% trans "Invite user" %}
           </a>
         {% endif %}
@@ -59,19 +59,7 @@
 
   <header class="person-container">
     <div class="image-wrapper">
-      {% has_perm 'core.view_avatar_rule' user person as can_view_avatar %}
-      {% if person.avatar and can_view_avatar %}
-        <div class="clip-circle materialboxed z-depth-2">
-          <img class="hundred-percent" src="{{ person.avatar.url }}"
-               alt="{{ person.first_name }} {{ person.last_name }}"/>
-        </div>
-
-      {% else %}
-
-        <div class="clip-circle no-image z-depth-2">
-          {{ person.initials }}
-        </div>
-      {% endif %}
+      {% include "core/partials/avatar_content.html" with class="clip-circle materialboxed z-depth-2" person_or_user=person title=True %}
     </div>
     <h1>
       {{ person.first_name }} {{ person.last_name }}
@@ -94,35 +82,35 @@
         <div class="collection">
           {% if can_change_person %}
             <a href="{% url 'edit_person_by_id' person.id %}" class="collection-item waves-effect waves-dark">
-              <i class="material-icons left">edit</i>
+              <i class="material-icons iconify left" data-icon="mdi:pencil-outline"></i>
               {% trans "Edit" %}
             </a>
           {% endif %}
 
           {% if can_delete_person %}
             <a href="{% url 'delete_person_by_id' person.id %}" class="collection-item waves-effect waves-red red-text">
-              <i class="material-icons left">delete</i>
+              <i class="material-icons iconify left" data-icon="mdi:delete-outline"></i>
               {% trans "Delete" %}
             </a>
           {% endif %}
 
           {% if can_change_person_preferences %}
             <a href="{% url 'preferences_person' person.id %}" class="collection-item waves-effect waves-dark">
-              <i class="material-icons left">settings</i>
+              <i class="material-icons iconify left" data-icon="mdi:cog-outline"></i>
               {% trans "Change preferences" %}
             </a>
           {% endif %}
 
         {% if can_impersonate and person.user %}
             <a href="{% url 'impersonate-start' person.user.id %}" class="collection-item waves-effect waves-dark">
-              <i class="material-icons left">portrait</i>
+              <i class="material-icons iconify left" data-icon="mdi:account-box-outline"></i>
               {% trans "Impersonate" %}
             </a>
           {% endif %}
 
         {% if can_invite and not person.user %}
           <a href="{% url "invite_person_by_id" person.id %}" class="collection-item waves-effect waves-light">
-            <i class="material-icons left">card_giftcard</i>
+            <i class="material-icons iconify left" data-icon="mdi:account-plus-outline"></i>
             {% trans "Invite user" %}
           </a>
         {% endif %}
@@ -135,13 +123,13 @@
         <table class="highlight">
         <tr>
           <td>
-            <i class="material-icons small">person</i>
+            <i class="material-icons small iconify" data-icon="mdi:account-outline"></i>
           </td>
           <td>{{ person.first_name }} {{ person.additional_name }} {{ person.last_name }}</td>
         </tr>
         <tr>
           <td>
-            <i class="material-icons small">face</i>
+            <i class="material-icons small iconify" data-icon="mdi:human-non-binary"></i>
           </td>
           <td>{% firstof person.get_sex_display "–" %}</td>
         </tr>
@@ -149,7 +137,7 @@
         {% if can_view_address %}
           <tr>
             <td rowspan="2">
-              <i class="material-icons small">home</i>
+              <i class="material-icons small iconify" data-icon="mdi:map-marker-outline"></i>
             </td>
             <td>{% firstof person.street "–" %} {{ person.housenumber }}</td>
           </tr>
@@ -161,7 +149,7 @@
         {% if can_view_contact_details %}
           <tr>
             <td rowspan="2">
-              <i class="material-icons small">phone</i>
+              <i class="material-icons small iconify" data-icon="mdi:phone-outline"></i>
             </td>
             <td>
               {% if person.phone_number %}
@@ -184,7 +172,7 @@
           </tr>
           <tr>
             <td>
-              <i class="material-icons small">email</i>
+              <i class="material-icons small iconify" data-icon="mdi:email-outline"></i>
             </td>
             <td>
               {% if person.email %}
@@ -199,7 +187,7 @@
         {% if can_view_personal_details %}
           <tr>
             <td>
-              <i class="material-icons small">cake</i>
+              <i class="material-icons small iconify" data-icon="mdi:cake"></i>
             </td>
             <td>
               <time datetime="{{ person.date_of_birth|date:'c' }}">{{ person.date_of_birth|date }}</time>
@@ -209,18 +197,26 @@
         {% endif %}
       </table>
       </div>
+      {% has_perm 'core.view_avatar_rule' user person as can_view_avatar %}
       {% has_perm 'core.view_photo_rule' user person as can_view_photo %}
-      {% if person.photo and can_view_photo %}
+      {% if person.photo and can_view_photo and not SITE_PREFERENCES.account__person_prefer_photo %}
         <div class="card">
           <div class="card-image">
             <img src="{{ person.photo.url }}" alt="{{ person.first_name }} {{ person.last_name }}" class="materialboxed">
             <span class="card-title">{{ person.first_name }} {{ person.last_name }}</span>
           </div>
         </div>
-
+      {% elif person.avatar and can_view_avatar %}
+        <div class="card">
+          <div class="card-image">
+            <img src="{{ person.avatar.url }}"
+                 alt="{{ person.first_name }} {{ person.last_name }}  ({% trans "Avatar" %})" class="materialboxed">
+            <span class="card-title">{{ person.first_name }} {{ person.last_name }} ({% trans "Avatar" %})</span>
+          </div>
+        </div>
       {% else %}
         <div class="card-panel">
-          <i class="material-icons left">image_not_supported</i>
+          <i class="material-icons iconify left" data-icon="mdi:image-off-outline"></i>
           {% trans "This person didn't upload a personal photo." %}
         </div>
       {% endif %}
diff --git a/aleksis/core/templates/core/person/list.html b/aleksis/core/templates/core/person/list.html
index ca441cac485847cf6a4975071d73787473804694..acafd75bad5762e3367f4f00b50010987c4f3fae 100644
--- a/aleksis/core/templates/core/person/list.html
+++ b/aleksis/core/templates/core/person/list.html
@@ -13,7 +13,7 @@
 
   {% if can_create_person %}
     <a class="btn green waves-effect waves-light" href="{% url 'create_person' %}">
-      <i class="material-icons left">add</i>
+      <i class="material-icons iconify left" data-icon="mdi:add"></i>
       {% trans "Create person" %}
     </a>
   {% endif %}
@@ -22,9 +22,9 @@
   <form method="get">
     {% form form=persons_filter.form %}{% endform %}
     {% trans "Search" as caption %}
-    {% include "core/partials/save_button.html" with caption=caption icon="search" %}
+    {% include "core/partials/save_button.html" with caption=caption icon="mdi:search" %}
     <button type="reset" class="btn red waves-effect waves-light">
-      <i class="material-icons left">clear</i>
+      <i class="material-icons iconify left" data-icon="mdi:close"></i>
       {% trans "Clear" %}
     </button>
   </form>
diff --git a/aleksis/core/templates/core/school_term/list.html b/aleksis/core/templates/core/school_term/list.html
index 14aa2f0a697a3efd54b27154b431ef3f424923de..9df6af9727b868e13d43a75c42730b375ff6aa47 100644
--- a/aleksis/core/templates/core/school_term/list.html
+++ b/aleksis/core/templates/core/school_term/list.html
@@ -10,7 +10,7 @@
 
 {% block content %}
   <a class="btn green waves-effect waves-light" href="{% url 'create_school_term' %}">
-    <i class="material-icons left">add</i>
+    <i class="material-icons left iconify" data-icon="mdi:add"></i>
     {% trans "Create school term" %}
   </a>
 
diff --git a/aleksis/core/templates/django_tables2/materialize.html b/aleksis/core/templates/django_tables2/materialize.html
index 8901c2d5d9dfddf14934195dee11641bca9bcb8d..b472a5b0c9d5e185482959c7536c5c6dd513f211 100644
--- a/aleksis/core/templates/django_tables2/materialize.html
+++ b/aleksis/core/templates/django_tables2/materialize.html
@@ -66,7 +66,7 @@
               <li class="waves-effect">
                 <a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}"
                    class="page-link">
-                  <i class="material-icons">chevron_left</i>
+                  <i class="material-icons iconify" data-icon="mdi:chevron-left"></i>
                 </a>
               </li>
             {% endblock pagination.previous %}
@@ -86,7 +86,7 @@
             {% block pagination.next %}
               <li class="waves-effect">
                 <a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}" class="page-link">
-                  <i class="material-icons">chevron_right</i>
+                  <i class="material-icons iconify" data-icon="mdi:chevron-right"></i>
                 </a>
               </li>
             {% endblock pagination.next %}
diff --git a/aleksis/core/templates/material/fields/colorfield_colorwidget.html b/aleksis/core/templates/material/fields/colorfield_colorwidget.html
index ab8bc7f955126d365be64085b7be740ef70a398f..5fff8c00ac913134abcb8e869464aeb7fe1e6330 100644
--- a/aleksis/core/templates/material/fields/colorfield_colorwidget.html
+++ b/aleksis/core/templates/material/fields/colorfield_colorwidget.html
@@ -2,7 +2,7 @@
 {% part bound_field.field %}
   <div class="row">
     <div class="input-field col s12{% if widget.attrs.required %} required{% endif %}">
-    <i class="material-icons prefix" id="{{ bound_field.html_name }}_preview" style="color: {% firstof bound_field.value field.widget.attrs.default '' %};">color_lens</i>
+    <i class="material-icons iconify prefix" id="{{ bound_field.html_name }}_preview" style="color: {% firstof bound_field.value field.widget.attrs.default '' %};" data-icon="mdi:palette-outline"></i>
       {% part field prefix %}{% endpart %}{% part field control %}
         <input
           type="text"
diff --git a/aleksis/core/templates/material/non_field_errors.html b/aleksis/core/templates/material/non_field_errors.html
index c8a92eb7ef813325580738a7819fccf6da2dfa70..b64aa87982a9285f6ee023cff210bbff1ae6b67a 100644
--- a/aleksis/core/templates/material/non_field_errors.html
+++ b/aleksis/core/templates/material/non_field_errors.html
@@ -1,7 +1,7 @@
 {% if form.non_field_errors %}
   <div class="alert error">
     {% for error in form.non_field_errors %}
-      <div><i class="material-icons left">error</i> {{ error }}</div>
+      <div><i class="material-icons iconify left" data-icon="mdi:alert-octagon-outline"></i> {{ error }}</div>
     {% endfor %}
   </div>
 {% endif %}
diff --git a/aleksis/core/templates/oauth2_provider/application/create.html b/aleksis/core/templates/oauth2_provider/application/create.html
index 73b94677206d38fca163858551536d575b2dce3d..683c81fa25966dea43c5737537ff090b4b042f01 100644
--- a/aleksis/core/templates/oauth2_provider/application/create.html
+++ b/aleksis/core/templates/oauth2_provider/application/create.html
@@ -11,7 +11,7 @@
     {% form form=form %}{% endform %}
     {% include "core/partials/save_button.html" %}
     <a class="btn waves-effect red waves-light" href="{% url "oauth2_applications" %}">
-      <i class="material-icons left">clear</i> {% trans "Cancel" %}
+      <i class="material-icons iconify left" data-icon="mdi:close"></i> {% trans "Cancel" %}
     </a>
   </form>
 {% endblock %}
diff --git a/aleksis/core/templates/oauth2_provider/application/detail.html b/aleksis/core/templates/oauth2_provider/application/detail.html
index 28e2af7d70ff4b6dd5d93b46b8ec52d31bc36538..1a7709f14dacdef5ab414d516ec35d72cbc1e5f8 100644
--- a/aleksis/core/templates/oauth2_provider/application/detail.html
+++ b/aleksis/core/templates/oauth2_provider/application/detail.html
@@ -6,18 +6,18 @@
 {% block page_title %}
   <a href="{% url "oauth2_applications" %}"
      class="btn-flat primary-color-text waves-light waves-effect">
-    <i class="material-icons left">chevron_left</i> {% trans "Back" %}
+    <i class="material-icons iconify left" data-icon="mdi:chevron-left"></i> {% trans "Back" %}
   </a>
   {{ application.name }}
 {% endblock %}
 
 {% block content %}
   <a class="btn orange waves-effect waves-light btn-margin" href="{% url "edit_oauth2_application" application.id %}">
-    <i class="material-icons left">edit</i>
+    <i class="material-icons iconify left" data-icon="mdi:pencil-outline"></i>
     {% trans "Edit" %}
   </a>
   <a class="btn red waves-effect waves-light btn-margin" href="{% url "delete_oauth2_application" application.id %}">
-    <i class="material-icons left">delete</i>
+    <i class="material-icons iconify left" data-icon="mdi:delete-outline"></i>
     {% trans "Delete" %}
   </a>
   <table class="responsive-table">
@@ -79,7 +79,7 @@
         {% trans "Skip Authorisation" %}
       </th>
       <td>
-        <i class="material-icons">{{ application.skip_authorization|yesno:"check,close" }}</i>
+        <i class="material-icons iconify" data-icon="mdi:{{ application.skip_authorization|yesno:"check,close" }}"></i>
       </td>
     </tr>
     </tbody>
diff --git a/aleksis/core/templates/oauth2_provider/application/edit.html b/aleksis/core/templates/oauth2_provider/application/edit.html
index 30f50fff94e330e941d7b4730fe7d875b039d74e..e4d837a32d713ef07b05656425e97979efef92cd 100644
--- a/aleksis/core/templates/oauth2_provider/application/edit.html
+++ b/aleksis/core/templates/oauth2_provider/application/edit.html
@@ -11,7 +11,7 @@
     {% form form=form %}{% endform %}
     {% include "core/partials/save_button.html" %}
     <a class="btn waves-effect red waves-light" href="{% url "oauth2_application" application.id %}">
-      <i class="material-icons left">clear</i> {% trans "Cancel" %}
+      <i class="material-icons iconify left" data-icon="mdi:close"></i> {% trans "Cancel" %}
     </a>
   </form>
 {% endblock %}
diff --git a/aleksis/core/templates/oauth2_provider/application/list.html b/aleksis/core/templates/oauth2_provider/application/list.html
index ced7d718dfe1b561c2ea8253e25658a9f449024d..1525dc99c28326701d5b91a59c1992a23abff439 100644
--- a/aleksis/core/templates/oauth2_provider/application/list.html
+++ b/aleksis/core/templates/oauth2_provider/application/list.html
@@ -7,7 +7,7 @@
 
 {% block content %}
   <a href="{% url "register_oauth_application" %}" class="btn green waves-effect waves-light">
-    <i class="material-icons left">add</i>
+    <i class="material-icons iconify left" data-icon="mdi:add"></i>
     {% blocktrans %}Register new application{% endblocktrans %}
   </a>
   <div class="collection">
diff --git a/aleksis/core/templates/oauth2_provider/authorize.html b/aleksis/core/templates/oauth2_provider/authorize.html
index c90d5e8dd9d3d72cff9e19ce167487900e904636..09293713fc126ec63fadec4e470206f78177a475 100644
--- a/aleksis/core/templates/oauth2_provider/authorize.html
+++ b/aleksis/core/templates/oauth2_provider/authorize.html
@@ -25,7 +25,7 @@
           <p class="margin-bottom">{% trans "The application requests access to the following scopes:" %}</p>
           {% for scope in scopes_descriptions %}
             <p class="margin-bottom">
-              <i class="material-icons left">check</i>
+              <i class="material-icons iconify left" data-icon="mdi:check"></i>
               {{ scope }}
             </p>
           {% endfor %}
@@ -37,10 +37,10 @@
               {% part form.allow %}<input type="hidden" value="true" name="allow">{% endpart %}
             {% endform %}
             <button type="submit" class="btn green waves-effect waves-light btn-margin">
-              <i class="material-icons left">done_all</i> {% trans "Allow" %}
+              <i class="material-icons iconify left" data-icon="mdi:check-all"></i> {% trans "Allow" %}
             </button>
             <a class="btn red waves-effect waves-light btn-margin" href="{% block app-form-back-url %}{% url "oauth2_application" application.id %}{% endblock app-form-back-url %}">
-              <i class="material-icons left">cancel</i> {% trans "Disallow" %}
+              <i class="material-icons iconify left" data-icon="mdi:close"></i> {% trans "Disallow" %}
             </a>
           </form>
         </div>
@@ -50,7 +50,7 @@
     <div class="container">
       <div class="card red">
         <div class="card-content white-text">
-          <div class="material-icons small left">error_outline</div>
+          <div class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></div>
           <span class="card-title">{% trans "Error" %}: {{ error.error }}</span>
           <p>
             {{ error.description }}
diff --git a/aleksis/core/templates/oauth2_provider/authorized-oob.html b/aleksis/core/templates/oauth2_provider/authorized-oob.html
index 3e95b0bf3a25691e0fc84e36dbcf71559cf86dc1..892ae5c9f863dcc32259210e652d18cfa3a27598 100644
--- a/aleksis/core/templates/oauth2_provider/authorized-oob.html
+++ b/aleksis/core/templates/oauth2_provider/authorized-oob.html
@@ -8,7 +8,7 @@
     <div class="container">
       <div class="card green">
         <div class="card-content white-text">
-          <div class="material-icons small left">check</div>
+          <div class="material-icons iconify small left" data-icon="mdi:check"></div>
           <span class="card-title">{% blocktrans %}Success!{% endblocktrans %}</span>
           <p>
             {% trans "Please return to your application and enter this code:" %} {{ code }}
@@ -20,7 +20,7 @@
     <div class="container">
       <div class="card red">
         <div class="card-content white-text">
-          <div class="material-icons small left">error_outline</div>
+          <div class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></div>
           <span class="card-title">{% trans "Error" %}: {{ error.error }}</span>
           <p>
             {{ error.description }}
diff --git a/aleksis/core/templates/oauth2_provider/authorized-token-delete.html b/aleksis/core/templates/oauth2_provider/authorized-token-delete.html
index 7f6f25920bebcbbaad7bb93fd486db617f25d662..ece2011b6e6170bd8ed845f8464302bb1e6fb8fb 100644
--- a/aleksis/core/templates/oauth2_provider/authorized-token-delete.html
+++ b/aleksis/core/templates/oauth2_provider/authorized-token-delete.html
@@ -8,7 +8,7 @@
 {% block content %}
   <div class="alert info">
     <p>
-      <i class="material-icons left">warning</i>
+      <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
       {% trans "Are you sure to revoke the access for this application?" %}
     </p>
   </div>
@@ -16,11 +16,11 @@
   <form method="post">
     {% csrf_token %}
     <a class="btn waves-effect waves-light red" href="{% url "oauth2_applications" %}">
-      <i class="material-icons left">delete</i>
+      <i class="material-icons iconify left" data-icon="mdi:delete-outline"></i>
       {% trans "Revoke" %}
     </a>
     <a class="btn waves-effect waves-light" href="{% url "oauth2_applications" %}">
-      <i class="material-icons left">cancel</i>
+      <i class="material-icons iconify left" data-icon="mdi:close"></i>
       {% trans "Cancel" %}
     </a>
   </form>
diff --git a/aleksis/core/templates/oauth2_provider/authorized-tokens.html b/aleksis/core/templates/oauth2_provider/authorized-tokens.html
index 46b0ce570a5ec1077825b283a244b6652045c437..dfe058056fb4ffaabd2a44eaac04adaa8aa3ad3b 100644
--- a/aleksis/core/templates/oauth2_provider/authorized-tokens.html
+++ b/aleksis/core/templates/oauth2_provider/authorized-tokens.html
@@ -29,7 +29,7 @@
   {% else %}
     <div class="alert info">
       <p>
-        <i class="material-icons left">info</i>
+        <i class="material-icons iconify left" data-icon="mdi:information-outline"></i>
         {% trans "No authorized applications." %}
       </p>
     </div>
diff --git a/aleksis/core/templates/offline.html b/aleksis/core/templates/offline.html
index bd741268a8902ac6708f33e353337695c03158a3..a386d40c1f9eb3d900676427f05904957f03211b 100644
--- a/aleksis/core/templates/offline.html
+++ b/aleksis/core/templates/offline.html
@@ -5,8 +5,10 @@
 {% block browser_title %}{% blocktrans %}Network error{% endblocktrans %}{% endblock %}
 
 {% block content %}
-  <h3><i class="material-icons left medium" style="font-size: 2.92rem;">signal_wifi_off</i>{% blocktrans %}No internet
-    connection.{% endblocktrans %}</h3>
+  <h3>
+    <i class="material-icons iconify left medium" style="font-size: 2.92rem;" data-icon="mdi:wifi-strength-alert-outline"></i>
+    {% blocktrans %}No internet connection.{% endblocktrans %}
+  </h3>
 
   <p class="flow-text">
     {% blocktrans %}
diff --git a/aleksis/core/templates/search/search.html b/aleksis/core/templates/search/search.html
index 5af1e55cc1076e404a921596da1cbc0774f7dbc3..ca32339d6d88010d2f4dab4d9e7557cd191705fe 100644
--- a/aleksis/core/templates/search/search.html
+++ b/aleksis/core/templates/search/search.html
@@ -18,7 +18,7 @@
 
     <p>
       <button type="submit" class="btn waves-effect waves-light green">
-        <i class="material-icons left">search</i>
+        <i class="material-icons iconify left" data-icon="mdi:search"></i>
         {% blocktrans %}Search{% endblocktrans %}
       </button>
     </p>
@@ -29,7 +29,7 @@
       <div class="collection">
         {% for result in page_obj.object_list %}
           <a href="{{ result.object.get_absolute_url|default:"#" }}" class="collection-item">
-            <i class="material-icons left">{{ result.object.icon_ }}</i>
+            <i class="material-icons iconify left" data-icon="mdi:{{ result.object.icon_ }}"></i>
             {{ result.object }}
           </a>
         {% empty %}
@@ -46,12 +46,12 @@
           {% if page_obj.has_previous %}
             <li class="waves-effect">
               <a href="?q={{ query }}&amp;page={{ page_obj.previous_page_number }}">
-                <i class="material-icons">chevron_left</i>
+                <i class="material-icons iconify" data-icon="mdi:chevron-left"></i>
               </a>
             </li>
           {% else %}
             <li class="disabled">
-              <a href="#"><i class="material-icons">chevron_left</i></a>
+              <a href="#"><i class="material-icons iconify" data-icon="mdi:chevron-left"></i></a>
             </li>
           {% endif %}
 
@@ -70,12 +70,12 @@
           {% if page_obj.has_next %}
             <li class="waves-effect">
               <a href="?q={{ query }}&amp;page={{ page_obj.next_page_number }}">
-                <i class="material-icons">chevron_right</i>
+                <i class="material-icons iconify" data-icon="mdi:chevron-right"></i>
               </a>
             </li>
           {% else %}
             <li class="disabled">
-              <a href="#"><i class="material-icons">chevron_right</i></a>
+              <a href="#"><i class="material-icons iconify" data-icon="mdi:chevron-right"></i></a>
             </li>
           {% endif %}
         </ul>
diff --git a/aleksis/core/templates/search/searchbar_snippet.html b/aleksis/core/templates/search/searchbar_snippet.html
index b5b7188c6a1e0381fd30f41b6fccc7e587949a30..d2a401c4f874d6a2c2a71c5b122eb3879cd83d57 100644
--- a/aleksis/core/templates/search/searchbar_snippet.html
+++ b/aleksis/core/templates/search/searchbar_snippet.html
@@ -1,4 +1,4 @@
 <a href="{{ result.object.get_absolute_url|default:"#" }}" class="collection-item search-item">
   {{ result.object }}
-  <i class="material-icons secondary-content search-result-icon">{{ result.object.icon_ }}</i>
+  <i class="material-icons secondary-content search-result-icon" data-icon="mdi:{{ result.object.icon_ }}"></i>
 </a>
diff --git a/aleksis/core/templates/socialaccount/authentication_error.html b/aleksis/core/templates/socialaccount/authentication_error.html
index 00901daf7fc3542ba07490381f7903e8f6d14048..43288dbc00e6ce84139254bf1b15d0781074d9eb 100644
--- a/aleksis/core/templates/socialaccount/authentication_error.html
+++ b/aleksis/core/templates/socialaccount/authentication_error.html
@@ -9,7 +9,7 @@
   <div class="container">
     <div class="card red">
       <div class="card-content white-text">
-        <div class="material-icons small left">error_outline</div>
+        <div class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></div>
         <span class="card-title">{% blocktrans %}Third-party Account Login Failure.{% endblocktrans %}</span>
         <p>
           {% blocktrans %}
diff --git a/aleksis/core/templates/socialaccount/login.html b/aleksis/core/templates/socialaccount/login.html
index 1630b01fa4104f563a92663bc7fd386a2abd5266..6f9747e82978371eda6bbda59d1e195d399fc782 100644
--- a/aleksis/core/templates/socialaccount/login.html
+++ b/aleksis/core/templates/socialaccount/login.html
@@ -8,7 +8,7 @@
 {% block content %}
 {% if process == "connect" %}
     <p class="flow-text">
-       <i class="material-icons left">info</i>
+       <i class="material-icons iconify left" data-icon="mdi:information-outline"></i>
        {% blocktrans with provider.name as provider %}You are about to connect a new third party account from {{ provider }}.{% endblocktrans %}
     </p>
     <form method="post">
@@ -19,7 +19,7 @@
     </form>
 {% else %}
     <p class="flow-text">
-       <i class="material-icons left small">info</i>
+       <i class="material-icons iconify left small" data-icon="mdi:information-outline"></i>
        {% blocktrans with provider.name as provider %}You are about to sign in using a third party account from {{ provider }}.{% endblocktrans %}
     </p>
     <form method="post">
diff --git a/aleksis/core/templates/socialaccount/login_cancelled.html b/aleksis/core/templates/socialaccount/login_cancelled.html
index bc1ebb6cb31a6b15a9d448cb93fa40a7cccb7d26..cb56d41b013e43c56853a3816ad63d8047e08a29 100644
--- a/aleksis/core/templates/socialaccount/login_cancelled.html
+++ b/aleksis/core/templates/socialaccount/login_cancelled.html
@@ -9,7 +9,7 @@
   <div class="container">
     <div class="card red">
       <div class="card-content white-text">
-        <div class="material-icons small left">error_outline</div>
+        <div class="material-icons iconify small left" data-icon="mdi:alert-octagon-outline"></div>
         <span class="card-title">{% blocktrans %}Login cancelled{% endblocktrans %}</span>
         <p>
           {% blocktrans %}
diff --git a/aleksis/core/templates/socialaccount/signup.html b/aleksis/core/templates/socialaccount/signup.html
index df79ecce459fa0ea7bcd8fe83ea21f530944c49b..7b11a3c40537933c4c4425a51b8c8913d3d0f8aa 100644
--- a/aleksis/core/templates/socialaccount/signup.html
+++ b/aleksis/core/templates/socialaccount/signup.html
@@ -8,7 +8,7 @@
 {% block content %}
   <div class="alert success">
     <p>
-      <i class="material-icons left">check_circle_outline</i>
+      <i class="material-icons iconify left" data-icon="mdi:check-circle-outline"></i>
         {% blocktrans with provider_name=account.get_provider.name site_name=site.name %}You are about to use your {{provider_name}} account to login to
         {{site_name}}. As a final step, please complete the following form:{% endblocktrans %}
     </p>
diff --git a/aleksis/core/templates/socialaccount/snippets/provider_list.html b/aleksis/core/templates/socialaccount/snippets/provider_list.html
index 9d9a9c87712076d8954ebce192ea2ac38383cc48..ea75c04fbd6d323308144e11a511a9e2fa7d4770 100644
--- a/aleksis/core/templates/socialaccount/snippets/provider_list.html
+++ b/aleksis/core/templates/socialaccount/snippets/provider_list.html
@@ -5,9 +5,9 @@
     {% for provider in socialaccount_providers %}
       {% if provider.id == "openid" %}
         {% for brand in provider.get_brands %}
-            <a title="{{brand.name}}" 
+            <a title="{{brand.name}}"
               class="socialaccount_provider {{provider.id}} {{brand.id}}
-              btn-large waves-effect waves-light primary-color" 
+              btn-large waves-effect waves-light primary-color margin-bottom"
               href="{% provider_login_url provider.id openid=brand.openid_url process=process %}">
               {% blocktrans with name=brand.name %}
                 Login with {{ name }}
@@ -16,7 +16,7 @@
         {% endfor %}
       {% endif %}
         <a title="{{provider.name}}" class="socialaccount_provider {{provider.id}}
-        btn hundred-percent waves-effect waves-light primary-color" 
+        btn hundred-percent waves-effect waves-light primary-color margin-bottom"
           href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
           {% blocktrans with name=provider.name %}
             Login with {{ name }}
@@ -26,7 +26,7 @@
   {% else %}
     <div class="alert primary">
       <div>
-        <i class="material-icons left">info</i>
+        <i class="material-icons iconify left" data-icon="mdi:information-outline"></i>
         {% blocktrans %}
           No third-party account providers available.
         {% endblocktrans %}
diff --git a/aleksis/core/templates/two_factor/_wizard_actions.html b/aleksis/core/templates/two_factor/_wizard_actions.html
index 95be950552fe6abf47e481bd507e6c28899986b0..dc4a046d6ce3ac4aa1a390b444f0080267a865d4 100644
--- a/aleksis/core/templates/two_factor/_wizard_actions.html
+++ b/aleksis/core/templates/two_factor/_wizard_actions.html
@@ -2,7 +2,7 @@
 
 {% if cancel_url %}
   <a href="{{ cancel_url }}" class="btn red waves-effect waves-light">
-    <i class="material-icons left">cancel</i>
+    <i class="material-icons iconify left" data-icon="mdi:close"></i>
     {% trans "Cancel" %}
   </a>
 {% endif %}
@@ -11,17 +11,17 @@
   <button name="wizard_goto_step" type="submit"
           value="{{ wizard.steps.prev }}"
           class="btn grey waves-effect waves-light">
-    <i class="material-icons left">arrow_back</i>
+    <i class="material-icons iconify left" data-icon="mdi:arrow-left"></i>
     {% trans "Back" %}
   </button>
 {% else %}
   <button disabled name="" type="button" class="btn grey disabled">
-    <i class="material-icons left">arrow_back</i>
+    <i class="material-icons iconify left" data-icon="mdi:arrow-left"></i>
     {% trans "Back" %}
   </button>
 {% endif %}
 
 <button type="submit" class="btn green waves-effect waves-light">
-  <i class="material-icons right">arrow_forward</i>
+  <i class="material-icons iconify right" data-icon="mdi:arrow-right"></i>
   {% trans "Next" %}
 </button>
diff --git a/aleksis/core/templates/two_factor/core/backup_tokens.html b/aleksis/core/templates/two_factor/core/backup_tokens.html
index b85450747fbe00623900af8d2832c0060446a042..94631e14d0bd993341bd51b3fdb493254fbb23e3 100644
--- a/aleksis/core/templates/two_factor/core/backup_tokens.html
+++ b/aleksis/core/templates/two_factor/core/backup_tokens.html
@@ -10,7 +10,7 @@
 
   <div class="alert info">
     <p>
-      <i class="material-icons left">info</i>
+      <i class="material-icons iconify left" data-icon="mdi:information-outline"></i>
       {% blocktrans %}
         Backup tokens can be used when your primary and backup
         phone numbers aren't available. The backup tokens below can be used
@@ -29,7 +29,7 @@
     </ul>
     <div class="alert warning">
       <p>
-        <i class="material-icons left">warning</i>
+        <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
         {% blocktrans %}
           Print these tokens and keep them somewhere safe.
         {% endblocktrans %}
@@ -41,11 +41,11 @@
 
   <form method="post">{% csrf_token %}{{ form }}
     <a href="{% url 'two_factor:profile' %}" class="btn primary waves-effect waves-light">
-      <i class="material-icons left">arrow_back</i>
+      <i class="material-icons iconify left" data-icon="mdi:arrow-left"></i>
       {% trans "Back to Account Security" %}
     </a>
     <button class="btn green waves-effect waves-light" type="submit">
-      <i class="material-icons left">vpn_key</i>
+      <i class="material-icons iconify left" data-icon="mdi:key-outline"></i>
       {% trans "Generate Tokens" %}
     </button>
   </form>
diff --git a/aleksis/core/templates/two_factor/core/login.html b/aleksis/core/templates/two_factor/core/login.html
index 834c4b98b543bd994542b105aec1b99932f7d186..77b135569d797181a45bd49d7fd7e9cded4e9a5c 100644
--- a/aleksis/core/templates/two_factor/core/login.html
+++ b/aleksis/core/templates/two_factor/core/login.html
@@ -90,13 +90,13 @@
 
             {% include "two_factor/_wizard_forms.html" %}
           </div>
-          <div class="card-action-light">
+          <div class="card-action-light login-card-action">
             <button type="submit" class="btn green waves-effect waves-light">
               {% trans "Login" %}
               <i class="material-icons right">send</i>
             </button>
-            {% if request.site.preferences.auth__allow_password_change and wizard.steps.current == "auth" %}
-              <a href="{% url "account_reset_password" %}" class="btn-flat right waves-effect waves-light">
+            {% if request.site.preferences.auth__allow_password_reset and wizard.steps.current == "auth" %}
+              <a href="{% url "account_reset_password" %}" class="btn-flat right waves-effect waves-red">
                 {% trans "Reset password" %}
               </a>
             {% endif %}
@@ -111,7 +111,8 @@
                 <p>{% trans "Or, alternatively, use one of your backup phones:" %}</p>
                 <p>
                   {% for other in other_devices %}
-                    <button name="challenge_device" value="{{ other.persistent_id }}" class="btn" type="submit">
+                    <button name="challenge_device" value="{{ other.persistent_id }}" class="btn margin-bottom"
+                            type="submit">
                       {{ other|device_action }}
                     </button>
                   {% endfor %}
diff --git a/aleksis/core/templates/two_factor/core/setup_complete.html b/aleksis/core/templates/two_factor/core/setup_complete.html
index df3f5e93acb1ded3138b9aea2375ce7d529c9033..afd9f1722b168bfe30807f5e8a4d4cb49c4bc900 100644
--- a/aleksis/core/templates/two_factor/core/setup_complete.html
+++ b/aleksis/core/templates/two_factor/core/setup_complete.html
@@ -10,7 +10,7 @@
 
   <div class="alert success">
     <p>
-      <i class="material-icons left">check_circle</i>
+      <i class="material-icons iconify left" data-icon="mdi:check-circle-outline"></i>
       {% blocktrans %}
         Congratulations, you've successfully enabled two-factor authentication.
       {% endblocktrans %}
@@ -20,17 +20,17 @@
   {% if not phone_methods %}
     <a href="{% url 'two_factor:profile' %}"
        class="btn btn-primary waves-effect waves-light">
-      <i class="material-icons left">arrow_back</i>
+      <i class="material-icons iconify left" data-icon="mdi:arrow-left"></i>
       {% trans "Back to Profile" %}
     </a>
     <a href="{% url 'two_factor:backup_tokens' %}" class="btn green waves-effect waves-light">
-      <i class="material-icons left">vpn_key</i>
+      <i class="material-icons iconify left" data-icon="mdi:key-outline"></i>
       {% trans "Generate backup codes" %}
     </a>
   {% else %}
     <div class="warning">
       <p>
-        <i class="material-icons left">warning</i>
+        <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
         {% blocktrans %}
           However, it might happen that you don't have access to
           your primary token device. To enable account recovery, generate backup codes
@@ -40,15 +40,15 @@
     </div>
     <a href="{% url 'two_factor:profile' %}"
        class="btn btn-primary waves-effect waves-light">
-      <i class="material-icons left">arrow_back</i>
+      <i class="material-icons iconify left" data-icon="mdi:arrow-left"></i>
       {% trans "Back to Profile" %}
     </a>
     <a href="{% url 'two_factor:backup_tokens' %}" class="btn green waves-effect waves-light">
-      <i class="material-icons left">vpn_key</i>
+      <i class="material-icons iconify left" data-icon="mdi:key-outline"></i>
       {% trans "Generate backup codes" %}
     </a>
     <a href="{% url 'two_factor:phone_create' %}" class="btn green waves-effect waves-light">
-      <i class="material-icons left">add</i>
+      <i class="material-icons iconify left" data-icon="mdi:phone-plus"></i>
       {% trans "Add Phone Number" %}
     </a>
   {% endif %}
diff --git a/aleksis/core/templates/two_factor/profile/disable.html b/aleksis/core/templates/two_factor/profile/disable.html
index 56bf20164b16b75a2b1d7e0759c9d43ba8d5b242..4fe45187e3bbb9ae4ffadb5807f0d26a8bafff9a 100644
--- a/aleksis/core/templates/two_factor/profile/disable.html
+++ b/aleksis/core/templates/two_factor/profile/disable.html
@@ -22,7 +22,7 @@
     </p>
 
     <button class="btn red waves-effect waves-light" type="submit">
-      <i class="material-icons left">power_settings_new</i>
+      <i class="material-icons iconify left" data-icon="mdi:power"></i>
       {% trans "Disable" %}
     </button>
   </form>
diff --git a/aleksis/core/templates/two_factor/profile/profile.html b/aleksis/core/templates/two_factor/profile/profile.html
index eaaa0ff6a5a1bde9463682100dfb9cc6f6b0f3dc..dd9472e7fb9a09051b35d7be57f59dcab1549dae 100644
--- a/aleksis/core/templates/two_factor/profile/profile.html
+++ b/aleksis/core/templates/two_factor/profile/profile.html
@@ -37,7 +37,7 @@
       </ul>
       <p>
         <a href="{% url 'two_factor:phone_create' %}" class="btn green waves-effect waves-light">
-          <i class="material-icons left">add</i>
+          <i class="material-icons iconify left" data-icon="mdi:phone-plus"></i>
           {% trans "Add Phone Number" %}
         </a>
       </p>
@@ -55,7 +55,7 @@
     </p>
     <p>
       <a href="{% url 'two_factor:backup_tokens' %}" class="btn primary waves-effect waves-light">
-        <i class="material-icons left">vpn_key</i>
+        <i class="material-icons iconify left" data-icon="mdi:key-outline"></i>
         {% trans "Show Codes" %}
       </a>
     </p>
@@ -69,7 +69,7 @@
     </p>
     <p>
       <a class="btn red waves-effect waves-light" href="{% url 'two_factor:disable' %}">
-        <i class="material-icons left">power_settings_new</i>
+        <i class="material-icons iconify left" data-icon="mdi:power"></i>
         {% trans "Disable Two-Factor Authentication" %}
       </a>
     </p>
@@ -84,6 +84,7 @@
 
     <p>
       <a href="{% url 'two_factor:setup' %}" class="green btn waves-effect waves-light ">
+        <i class="material-icons iconify left" data-icon="mdi:key-outline"></i>
         {% trans "Enable Two-Factor Authentication" %}
       </a>
     </p>
diff --git a/aleksis/core/templatetags/html_helpers.py b/aleksis/core/templatetags/html_helpers.py
index 5f78b1b195f9030cc97d96a0488c14845d450741..34066192527930c2a4096f49deb3d247f883c31e 100644
--- a/aleksis/core/templatetags/html_helpers.py
+++ b/aleksis/core/templatetags/html_helpers.py
@@ -1,3 +1,6 @@
+import random
+import string
+
 from django import template
 
 from bs4 import BeautifulSoup
@@ -22,3 +25,18 @@ def add_class_to_el(value: str, arg: str) -> str:
         el["class"] = el.get("class", []) + [cls]
 
     return str(soup)
+
+
+@register.simple_tag
+def generate_random_id(prefix: str, length: int = 10) -> str:
+    """Generate a random ID for templates.
+
+    :Example:
+
+    .. code-block::
+
+        {% generate_random_id "prefix-" %}
+    """
+    return prefix + "".join(
+        random.choice(string.ascii_lowercase) for i in range(length)  # noqa: S311
+    )
diff --git a/aleksis/core/tests/models/test_notification.py b/aleksis/core/tests/models/test_notification.py
index 1b1a6df054fe24f06bd363e825df3f1259af53e8..cdb01a04b99a398469ddfcd4466101c0b2d79f34 100644
--- a/aleksis/core/tests/models/test_notification.py
+++ b/aleksis/core/tests/models/test_notification.py
@@ -1,11 +1,81 @@
+from datetime import timedelta
+from time import sleep
+from unittest.mock import patch
+
+from django.test import override_settings
+from django.utils import timezone
+
 import pytest
+from freezegun import freeze_time
 
 from aleksis.core.models import Notification, Person
+from aleksis.core.util.notifications import _send_due_notifications
 
 pytestmark = pytest.mark.django_db
 
 
-def test_email_notification(mailoutbox):
+def test_send_notification():
+    email = "doe@example.com"
+    recipient = Person.objects.create(first_name="Jane", last_name="Doe", email=email)
+
+    sender = "Foo"
+    title = "There is happened something."
+    description = "Here you get some more information."
+    link = "https://aleksis.org/"
+
+    notification = Notification(
+        sender=sender,
+        recipient=recipient,
+        title=title,
+        description=description,
+        link=link,
+    )
+
+    with patch("aleksis.core.models.Notification.send") as patched_send:
+        patched_send.assert_not_called()
+
+        notification.save()
+
+        patched_send.assert_called()
+
+
+def test_send_scheduled_notification():
+    email = "doe@example.com"
+    recipient = Person.objects.create(first_name="Jane", last_name="Doe", email=email)
+
+    sender = "Foo"
+    title = "There is happened something."
+    description = "Here you get some more information."
+    link = "https://aleksis.org/"
+
+    notification = Notification(
+        sender=sender,
+        recipient=recipient,
+        title=title,
+        description=description,
+        link=link,
+        send_at=timezone.now() + timedelta(days=1),
+    )
+    notification.save()
+
+    with patch("aleksis.core.models.Notification.send") as patched_send:
+        patched_send.assert_not_called()
+
+        _send_due_notifications()
+
+        patched_send.assert_not_called()
+
+        with freeze_time(timezone.now() + timedelta(days=1)):
+            _send_due_notifications()
+
+        patched_send.assert_called()
+
+
+@override_settings(CELERY_BROKER_URL="memory://localhost//")
+@pytest.mark.django_db(
+    databases=["default", "default_oot"], serialized_rollback=True, transaction=True
+)
+def test_email_notification(mailoutbox, celery_worker):
     email = "doe@example.com"
     recipient = Person.objects.create(first_name="Jane", last_name="Doe", email=email)
 
@@ -19,6 +89,9 @@ def test_email_notification(mailoutbox):
     )
     notification.save()
 
+    sleep(3)
+
+    notification.refresh_from_db()
     assert notification.sent
 
     assert len(mailoutbox) == 1
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index c91bae7f29c978d30e96361f92213eec7c5679d1..e76da89d18ed2078fc2405db11829aa344725fdf 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -30,6 +30,11 @@ urlpatterns = [
         views.CustomPasswordChangeView.as_view(),
         name="account_change_password",
     ),
+    path(
+        "accounts/password/reset/",
+        views.CustomPasswordResetView.as_view(),
+        name="account_reset_password",
+    ),
     path("accounts/", include("allauth.urls")),
     path("invitations/send-invite", views.InvitePerson.as_view(), name="invite_person"),
     path(
@@ -315,6 +320,12 @@ urlpatterns = [
         name="assign_permission",
     ),
     path("pdfs/<int:pk>/", views.RedirectToPDFFile.as_view(), name="redirect_to_pdf_file"),
+    path("ical/", views.ICalFeedListView.as_view(), name="ical_feed_list"),
+    path("ical/create/", views.ICalFeedCreateView.as_view(), name="ical_feed_create"),
+    path("ical/<int:pk>/edit/", views.ICalFeedEditView.as_view(), name="ical_feed_edit"),
+    path("ical/<int:pk>/delete/", views.ICalFeedDeleteView.as_view(), name="ical_feed_delete"),
+    path("ical/<slug:slug>.ics", views.ICalFeedView.as_view(), name="ical_feed"),
+    path("__icons__/", include("dj_iconify.urls")),
 ]
 
 # Use custom server error handler to get a request object in the template
diff --git a/aleksis/core/util/auth_helpers.py b/aleksis/core/util/auth_helpers.py
index 056b5156da68233d3b3ee2378ccffdff95cfbcd6..6edfac83373882d077d0a588d822fd3a0d0cc9b4 100644
--- a/aleksis/core/util/auth_helpers.py
+++ b/aleksis/core/util/auth_helpers.py
@@ -3,6 +3,8 @@
 from typing import Any, Optional
 
 from django.conf import settings
+from django.contrib.auth.validators import ASCIIUsernameValidator
+from django.core.validators import RegexValidator
 from django.http import HttpRequest
 
 from allauth.account.adapter import DefaultAccountAdapter
@@ -16,6 +18,7 @@ from oauth2_provider.views.mixins import (
 from oauthlib.common import Request as OauthlibRequest
 
 from .apps import AppConfig
+from .core_helpers import get_site_preferences
 
 
 class OurSocialAccountAdapter(DefaultSocialAccountAdapter):
@@ -134,3 +137,11 @@ class ClientProtectedResourceMixin(_ClientProtectedResourceMixin):
         required_scopes = set(self.get_scopes() or [])
         allowed_scopes = set(AppScopes().get_available_scopes(oauth_request.client) or [])
         return required_scopes.issubset(allowed_scopes)
+
+
+def validate_username_preference_regex(value: str):
+    regex = get_site_preferences()["auth__allowed_username_regex"]
+    return RegexValidator(regex)(value)
+
+
+custom_username_validators = [validate_username_preference_regex, ASCIIUsernameValidator()]
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index 6988dd53137f2d5ca364bcfd7091d9b1795e7f10..ffdba5f45c55454489c8b21c81688458c9808163 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
 from importlib import import_module, metadata
 from itertools import groupby
 from operator import itemgetter
+from types import ModuleType
 from typing import Any, Callable, Dict, Optional, Sequence, Union
 from warnings import warn
 
@@ -59,9 +60,31 @@ def dt_show_toolbar(request: HttpRequest) -> bool:
     return False
 
 
-def get_app_packages() -> Sequence[str]:
+def get_app_packages(only_official: bool = False) -> Sequence[str]:
     """Find all registered apps from the setuptools entrypoint."""
-    return [f"{ep.module}.{ep.attr}" for ep in metadata.entry_points().get("aleksis.app", [])]
+    apps = []
+
+    for ep in metadata.entry_points().get("aleksis.app", []):
+        path = f"{ep.module}.{ep.attr}"
+        if path.startswith("aleksis.apps.") or not only_official:
+            apps.append(path)
+
+    return apps
+
+
+def get_app_settings_module(app: str) -> Optional[ModuleType]:
+    """Get the settings module of an app."""
+    pkg = ".".join(app.split(".")[:-2])
+    mod_settings = None
+    while "." in pkg:
+        try:
+            return import_module(pkg + ".settings")
+        except ImportError:
+            # Import errors are non-fatal.
+            pkg = ".".join(pkg.split(".")[:-1])
+
+    # The app does not have settings
+    return None
 
 
 def merge_app_settings(
@@ -77,18 +100,8 @@ def merge_app_settings(
     potentially malicious apps!
     """
     for app in get_app_packages():
-        pkg = ".".join(app.split(".")[:-2])
-        mod_settings = None
-        while "." in pkg:
-            try:
-                mod_settings = import_module(pkg + ".settings")
-            except ImportError:
-                # Import errors are non-fatal.
-                pkg = ".".join(pkg.split(".")[:-1])
-                continue
-            break
+        mod_settings = get_app_settings_module(app)
         if not mod_settings:
-            # The app does not have settings
             continue
 
         app_setting = getattr(mod_settings, setting, None)
@@ -109,6 +122,26 @@ def merge_app_settings(
                     raise TypeError("Only dict and list settings can be merged.")
 
 
+def get_app_settings_overrides() -> dict[str, Any]:
+    """Get app settings overrides.
+
+    Official apps (those under the ``aleksis.apps` namespace) can override
+    or add settings by listing them in their ``settings.overrides``.
+    """
+    overrides = {}
+
+    for app in get_app_packages(True):
+        mod_settings = get_app_settings_module(app)
+        if not mod_settings:
+            continue
+
+        if hasattr(mod_settings, "overrides"):
+            for name in mod_settings.overrides:
+                overrides[name] = getattr(mod_settings, name)
+
+    return overrides
+
+
 def get_site_preferences():
     """Get the preferences manager of the current site."""
     from django.contrib.sites.models import Site  # noqa
@@ -288,11 +321,6 @@ def generate_random_code(length, packet_size) -> str:
     return get_random_string(packet_size * length).lower()
 
 
-def unread_notifications_badge(request: HttpRequest) -> int:
-    """Generate badge content with the number of unread notifications."""
-    return request.user.person.unread_notifications_count
-
-
 def monkey_patch() -> None:  # noqa
     """Monkey-patch dependencies for special behaviour."""
     # Unwrap promises in JSON serializer instead of stringifying
diff --git a/aleksis/core/util/notifications.py b/aleksis/core/util/notifications.py
index 13887a197defb9e24f5a9b1538561287b80c942f..061b8d3d8fae61f68b1c3f2fa9e9f3f421491292 100644
--- a/aleksis/core/util/notifications.py
+++ b/aleksis/core/util/notifications.py
@@ -5,6 +5,7 @@ from typing import Sequence, Union
 from django.apps import apps
 from django.conf import settings
 from django.template.loader import get_template
+from django.utils import timezone
 from django.utils.functional import lazy
 from django.utils.translation import gettext_lazy as _
 
@@ -82,6 +83,8 @@ def send_notification(notification: Union[int, "Notification"], resend: bool = F
             name, check, send = _CHANNELS_MAP[channel]
             if check():
                 send(notification)
+                notification.sent = True
+                notification.save()
 
 
 def get_notification_choices() -> list:
@@ -99,3 +102,12 @@ def get_notification_choices() -> list:
 
 
 get_notification_choices_lazy = lazy(get_notification_choices, tuple)
+
+
+def _send_due_notifications():
+    """Send all notifications that are due to be sent."""
+    Notification = apps.get_model("core", "Notification")
+
+    due_notifications = Notification.objects.filter(sent=False, send_at__lte=timezone.now())
+    for notification in due_notifications:
+        notification.send()
diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py
index bcba7e8637d81aa9c554aef65cdb431b059753a9..5ba4271c08a4244ba5f359e9c46e01eff6c6d112 100644
--- a/aleksis/core/util/predicates.py
+++ b/aleksis/core/util/predicates.py
@@ -154,3 +154,9 @@ def contains_site_preference_value(section: str, pref: str, value: str):
 def has_activated_2fa(user: User) -> bool:
     """Check if the user has activated two-factor authentication."""
     return user_has_device(user)
+
+
+@predicate
+def is_assigned_to_current_person(user: User, obj: Model) -> bool:
+    """Check if the object is assigned to the current person."""
+    return getattr(obj, "person", None) == user.person
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index 7be99d6a0e67aa552f53822f3b9fcbc4abf9e87b..ed20571dd83aee3bfc5d3bc4fd1dc2c8a7211b1d 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -36,7 +36,7 @@ from django.views.generic.list import ListView
 
 import reversion
 from allauth.account.utils import _has_verified_for_login, send_email_confirmation
-from allauth.account.views import PasswordChangeView, SignupView
+from allauth.account.views import PasswordChangeView, PasswordResetView, SignupView
 from allauth.socialaccount.adapter import get_adapter
 from allauth.socialaccount.models import SocialAccount
 from celery_progress.views import get_progress
@@ -102,6 +102,7 @@ from .models import (
     OAuthApplication,
     PDFFile,
     Person,
+    PersonalICalUrl,
     PersonInvitation,
     SchoolTerm,
     TaskUserAssignment,
@@ -216,8 +217,12 @@ def index(request: HttpRequest) -> HttpResponse:
         widgets = []
 
     activities = person.activities.all().order_by("-created")[:5]
-    notifications = person.notifications.all().order_by("-created")[:5]
-    unread_notifications = person.notifications.all().filter(read=False).order_by("-created")
+    notifications = person.notifications.filter(send_at__lte=timezone.now()).order_by("-created")[
+        :5
+    ]
+    unread_notifications = person.notifications.filter(
+        send_at__lte=timezone.now(), read=False
+    ).order_by("-created")
 
     context["activities"] = activities
     context["notifications"] = notifications
@@ -246,7 +251,9 @@ class NotificationsListView(PermissionRequiredMixin, ListView):
     template_name = "core/notifications.html"
 
     def get_queryset(self) -> QuerySet:
-        return self.request.user.person.notifications.order_by("-created")
+        return self.request.user.person.notifications.filter(send_at__lte=timezone.now()).order_by(
+            "-created"
+        )
 
     def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
         self.get_queryset().filter(read=False).update(read=True)
@@ -1365,6 +1372,20 @@ class CustomPasswordChangeView(PermissionRequiredMixin, PasswordChangeView):
         super().__init__(*args, **kwargs)
 
 
+class CustomPasswordResetView(PermissionRequiredMixin, PasswordResetView):
+    """Custom password reset view to allow to disable resetting of password."""
+
+    permission_required = "core.can_reset_password"
+
+    def __init__(self, *args, **kwargs):
+        if get_site_preferences()["auth__allow_password_reset"]:
+            self.template_name = "account/password_reset.html"
+        else:
+            self.template_name = "account/password_change_disabled.html"
+
+        super().__init__(*args, **kwargs)
+
+
 class SocialAccountDeleteView(DeleteView):
     """Custom view to delete django-allauth social account."""
 
@@ -1546,3 +1567,59 @@ class CustomAuthorizationView(AuthorizationView):
         context = super().get_context_data(**kwargs)
         context["no_menu"] = True
         return context
+
+
+class ICalFeedView(DetailView):
+    model = PersonalICalUrl
+    slug_field = "uuid"
+
+    def get(self, request, *args, **kwargs):
+        obj: PersonalICalUrl = self.get_object()
+        if obj.ical_feed_object:
+            kwargs["person"] = obj.person
+            return obj.ical_feed_object()(request, *args, **kwargs)
+        else:
+            return HttpResponse(status=410)
+
+
+class ICalFeedListView(PermissionRequiredMixin, ListView):
+    model = PersonalICalUrl
+    template_name = "core/ical/ical_list.html"
+    permission_required = "core.view_ical_rule"
+
+    def get_queryset(self):
+        return self.model.objects.filter(person=self.request.user.person)
+
+
+class ICalFeedEditView(PermissionRequiredMixin, AdvancedEditView):
+    model = PersonalICalUrl
+    template_name = "core/ical/ical_edit.html"
+    success_url = reverse_lazy("ical_feed_list")
+    success_message = _("ICal feed updated successfully")
+    permission_required = "core.edit_ical_rule"
+
+    fields = ["name", "ical_feed"]
+
+
+class ICalFeedDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
+    model = PersonalICalUrl
+    template_name = "core/pages/delete.html"
+    success_url = reverse_lazy("ical_feed_list")
+    success_message = _("ICal feed deleted successfully")
+    permission_required = "core.delete_ical_rule"
+
+
+class ICalFeedCreateView(PermissionRequiredMixin, AdvancedCreateView):
+    model = PersonalICalUrl
+    template_name = "core/ical/ical_create.html"
+    success_url = reverse_lazy("ical_feed_list")
+    success_message = _("ICal feed created successfully")
+    permission_required = "core.create_ical_rule"
+
+    fields = ["name", "ical_feed"]
+
+    def form_valid(self, form):
+        obj = form.save(commit=False)
+        obj.person = self.request.user.person
+        obj.save()
+        return super().form_valid(form)
diff --git a/docs/admin/10_install.rst b/docs/admin/10_install.rst
index c234fa5d7dfcd472c1e786a9909dca503a40e362..f8b41d0d98efd5daa1df64a5fceea9ae5b86c662 100644
--- a/docs/admin/10_install.rst
+++ b/docs/admin/10_install.rst
@@ -34,6 +34,7 @@ For an installation on a dedicated server, the following prerequisites are neede
  * nginx
  * Python 3.9
  * Some system dependencies to build Python modules and manage frontend files
+ * System locales for all supported languages
  * The aforementioned paths
 
 Install system packages
@@ -53,7 +54,8 @@ Install some packages from the Debian package system.
                python3-virtualenv \
                chromium \
                redis-server \
-               postgresql
+               postgresql \
+               locales-all
 
 Create PostgreSQL user and database
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -252,6 +254,5 @@ Finally, bring the stack up using:
 AlekSIS will be reachable on port 80 if you forgot to configure the environment.
 You are responsible for adding a reverse proxy like nginx providing TLS, etc.
 
-.. _Dynaconf: https://dynaconf.readthedocs.io/en/latest/
 .. _Let's Encrypt: https://certbot.eff.org/instructions
 .. _PyPI: https://pypi.org
diff --git a/docs/admin/15_config_files.rst b/docs/admin/15_config_files.rst
index adb94a8cac6375ec1b06dbb1de4b60fb0c761165..556713437cc467c38a4b3f2acf05c070b46554c6 100644
--- a/docs/admin/15_config_files.rst
+++ b/docs/admin/15_config_files.rst
@@ -40,7 +40,7 @@ A configuration file might look like this::
   password = "SuperSecretPassword"
 
   [caching]
-  redis = { enabled = true, address = "127.0.0.1" }
+  redis = { enabled = true, address = "redis://127.0.0.1" }
 
 The `secret_key` setting above defines a single value. The following `http`
 section defines a table (cf. a dictionary) in one way, and you can see the
diff --git a/docs/admin/22_registration.rst b/docs/admin/22_registration.rst
index 2442b5ce6c59314928fb3f6504b2db51eaa02010..510500f2198ec6377c91d97bf1be903411f0daea 100644
--- a/docs/admin/22_registration.rst
+++ b/docs/admin/22_registration.rst
@@ -26,6 +26,16 @@ signup for everyone. A menu item will be added for public registration.
 .. warning::
    Do not enable this feature unless you intend to run a public AlekSIS instance.
 
+Before enabling registration, you should consider restricting allowed usernames.
+By default, all ASCII characters are allowed in usernames. Often, it is advisable
+to not allow special characters. This often depends on the systems that will be
+linked to AlekSIS.
+
+To restrict usernames to a certain format, a regular expression can be defined
+in the ``Regular expression for allowed usernames`` preference. For example, to
+restrict the username to lower case letters and numbers, and beginning with a number,
+the regex can be set to ``^[a-z][a-z0-9]+$`.
+
 User invitations
 ~~~~~~~~~~~~~~~~
 
diff --git a/docs/admin/32_tasks.rst b/docs/admin/32_tasks.rst
index f8e2dcaf2597487a853568fb6517d35d5d0093ae..4b7fd22353deebdf51b9ebec74fda531bc465633 100644
--- a/docs/admin/32_tasks.rst
+++ b/docs/admin/32_tasks.rst
@@ -24,3 +24,5 @@ is currently only possible through the Django Admin backend, under
 Under the *Periodic Tasks* app, you can define schedules and tasks. The names
 of tasks you can add manually are documented in the respective sections
 of the manual.
+
+.. _Celery: https://celeryproject.org/
diff --git a/docs/admin/50_dashboard.rst b/docs/admin/50_dashboard.rst
index 0c7f6a3b5a5ebda81f417b1a86a1135540a580ba..bf2c72d8d4ed0d76832635bc727612ac982e182f 100644
--- a/docs/admin/50_dashboard.rst
+++ b/docs/admin/50_dashboard.rst
@@ -18,6 +18,14 @@ optionally with an icon or picture next to it. It therefore provides the followi
 
 As link title, the widget title will be used.
 
+Static content widget
+^^^^^^^^^^^^^^^^^^^^
+
+The static content widget allows to display custom static information on the dashboard,
+It therefore provides the following additional attribute:
+
+* **Content**: The content of the widget. HTML can be used for formatting.
+
 More dashboard widgets from apps
 --------------------------------
 
diff --git a/docs/conf.py b/docs/conf.py
index 2acaa4acf472ffb6ea2a1124d9371644186cc5c6..dc58a3b9a1acc1ecb89552e27e33a4c66dd48c96 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -29,9 +29,9 @@ copyright = "2019-2022 The AlekSIS team"
 author = "The AlekSIS Team"
 
 # The short X.Y version
-version = "2.7"
+version = "2.8"
 # The full version, including alpha/beta/rc tags
-release = "2.7.5.dev0"
+release = "2.8.2.dev0"
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/docs/dev/02_install_apps.rst b/docs/dev/02_install_apps.rst
index 335f92f56735e0f1eb02eecdde626d5a074674ff..a376dd4f413380750c8b466c73fdd07973ac36f6 100644
--- a/docs/dev/02_install_apps.rst
+++ b/docs/dev/02_install_apps.rst
@@ -13,7 +13,7 @@ Installing a development environment for own apps
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 If you are developing your own app, you probably do not want to
-ru na development environment from the `AlekSIS-Core` repository.
+run a development environment from the `AlekSIS-Core` repository.
 
 Instead, simply install the environment using ``poetry install`` from
 your app repository – it will pull in `AlekSIS-Core` as a dependency
diff --git a/docs/dev/04_materialize_templates.rst b/docs/dev/04_materialize_templates.rst
index b997c32f293e6268a2deae5b69001abaf9a84e10..b49677252af51905bf46084f42eb6a5811f173d7 100644
--- a/docs/dev/04_materialize_templates.rst
+++ b/docs/dev/04_materialize_templates.rst
@@ -56,13 +56,13 @@ Furthermore, you can use tabs with integrated icons that are higher, but more co
         <ul class="tabs tabs-transparent tabs-icons tabs-fixed-width">
             <li class="tab">
                 <a href="#test1">
-                    <i class="material-icons">speaker_notes</i>
+	            <i class="material-icons iconify" data-icon="mdi:speaker_notes"></i>
                     Test 1
                 </a>
             </li>
             <li class="tab">
                 <a href="#test2">
-                    <i class="material-icons">people</i>
+                    <i class="material-icons iconify" data-icon="mdi:people"></i>
                     Test 2
                 </a>
             </li>
diff --git a/docs/dev/06_merging_app_settings.rst b/docs/dev/06_merging_app_settings.rst
index ad30c595f3dc8ef7bc39ab1b3a06b4785cc7276a..3c0c1affd210c706ac2959d02324f6ad9e009a2b 100644
--- a/docs/dev/06_merging_app_settings.rst
+++ b/docs/dev/06_merging_app_settings.rst
@@ -3,9 +3,15 @@ Merging of app settings
 
 AlekSIS provides features to merge app settings into main ``settings.py``.
 
+Third-party apps can only add values to some select existing settings.
+Official apps (those under the ``aleksis.apps.`` namespace) can mark any
+setting for overriding.
+
 Currently mergable settings
 ---------------------------
 
+The following settings can be amended by any app:
+
  * INSTALLED_APPS
  * DATABASES
  * YARN_INSTALLED_APPS
@@ -23,3 +29,14 @@ the following into your ``settings.py``::
             "HOST": "127.0.0.1",
             "PORT": 5432,
         }
+    }
+
+Overriding any setting
+----------------------
+
+Official apps only (currently) can override any setting, but need to explicitly
+mark it by listing it in a list called ``overrides`` in their ``settings.py``::
+
+    PAYMENT_MODEL = "tezor.Invoice"
+
+    overrides = ["PAYMENT_MODEL"]
diff --git a/pyproject.toml b/pyproject.toml
index 12199f73cd28724dffa7087dcb6628ea68943fe1..51f859a3a864acf61fc4755581242f751567d6dd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "AlekSIS-Core"
-version = "2.7.5.dev0"
+version = "2.8.2.dev0"
 packages = [
     { include = "aleksis" }
 ]
@@ -60,7 +60,7 @@ django-any-js = "^1.1"
 django-debug-toolbar = "^3.2"
 django-menu-generator-ng = "^1.2.3"
 django-tables2 = "^2.1"
-django-phonenumber-field = {version = "^5.2", extras = ["phonenumbers"]}
+django-phonenumber-field = {version = "^6.1", extras = ["phonenumbers"]}
 django-sass-processor = "1.0"
 libsass = "^0.21.0"
 colour = "^0.1.5"
@@ -98,7 +98,7 @@ celery-haystack-ng = "^2.0"
 django-dbbackup = "^3.3.0"
 spdx-license-list = "^0.5.0"
 license-expression = "^21.6"
-django-reversion = "^4.0.0"
+django-reversion = "^5.0.0"
 django-favicon-plus-reloaded = "^1.1.5"
 django-health-check = "^3.12.1"
 psutil = "^5.7.0"
@@ -126,6 +126,9 @@ python-gnupg = "^0.4.7"
 sentry-sdk = {version = "^1.4.3", optional = true}
 django-cte = "^1.1.5"
 pycountry = "^22.0.0"
+django-ical = "^1.8.3"
+django-iconify = "^0.2.0"
+customidenticon = "^0.1.5"
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]
@@ -133,7 +136,7 @@ s3 = ["boto3", "django-storages"]
 sentry = ["sentry-sdk"]
 
 [tool.poetry.dev-dependencies]
-aleksis-builddeps = "^6"
+aleksis-builddeps = "*"
 uwsgi = "^2.0"
 
 [tool.poetry.scripts]