diff --git a/aleksis/core/__init__.py b/aleksis/core/__init__.py
index 587b829c611913af41c69f2734a48235956ff787..8025958440c84991c6d28b463bde735bae2efcbc 100644
--- a/aleksis/core/__init__.py
+++ b/aleksis/core/__init__.py
@@ -1,7 +1,7 @@
-import pkg_resources
-
 from django.utils.translation import gettext_lazy as _
 
+import pkg_resources
+
 try:
     from .celery import app as celery_app
 except ModuleNotFoundError:
diff --git a/aleksis/core/admin.py b/aleksis/core/admin.py
index 85fc6756b86e72c20d277326ca1f95d35ca5ad4c..383b35ad2c5e244a766b7abfe35287b16dd60cc3 100644
--- a/aleksis/core/admin.py
+++ b/aleksis/core/admin.py
@@ -4,16 +4,15 @@ from reversion.admin import VersionAdmin
 
 from .mixins import BaseModelAdmin
 from .models import (
-    Group,
-    Person,
     Activity,
-    Notification,
     Announcement,
     AnnouncementRecipient,
     CustomMenuItem,
+    Group,
+    Notification,
+    Person,
 )
 
-
 admin.site.register(Person, VersionAdmin)
 admin.site.register(Group, VersionAdmin)
 admin.site.register(Activity, VersionAdmin)
diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py
index 492da4a3e808583bd8ca820a42812a1d33d6f240..921d12ff785c39c76ac0af63cdb73d9732faaf80 100644
--- a/aleksis/core/apps.py
+++ b/aleksis/core/apps.py
@@ -7,7 +7,11 @@ from django.utils.translation import gettext_lazy as _
 
 from dynamic_preferences.registries import preference_models
 
-from .registries import group_preferences_registry, person_preferences_registry, site_preferences_registry
+from .registries import (
+    group_preferences_registry,
+    person_preferences_registry,
+    site_preferences_registry,
+)
 from .util.apps import AppConfig
 from .util.core_helpers import has_person
 from .util.sass_helpers import clean_scss
@@ -34,9 +38,9 @@ class CoreConfig(AppConfig):
     def ready(self):
         super().ready()
 
-        SitePreferenceModel = self.get_model('SitePreferenceModel')
-        PersonPreferenceModel = self.get_model('PersonPreferenceModel')
-        GroupPreferenceModel = self.get_model('GroupPreferenceModel')
+        SitePreferenceModel = self.get_model("SitePreferenceModel")
+        PersonPreferenceModel = self.get_model("PersonPreferenceModel")
+        GroupPreferenceModel = self.get_model("GroupPreferenceModel")
 
         preference_models.register(SitePreferenceModel, site_preferences_registry)
         preference_models.register(PersonPreferenceModel, person_preferences_registry)
@@ -52,16 +56,15 @@ class CoreConfig(AppConfig):
         **kwargs,
     ) -> None:
         if section == "theme":
-            if name  in ("primary", "secondary"):
+            if name in ("primary", "secondary"):
                 clean_scss()
             elif name in ("favicon", "pwa_icon"):
                 from favicon.models import Favicon  # noqa
 
-                Favicon.on_site.update_or_create(title=name,
-                                                 defaults={
-                                                     "isFavicon": name == "favicon",
-                                                     "faviconImage": new_value,
-                                                 })
+                Favicon.on_site.update_or_create(
+                    title=name,
+                    defaults={"isFavicon": name == "favicon", "faviconImage": new_value,},
+                )
 
     def post_migrate(
         self,
diff --git a/aleksis/core/celery.py b/aleksis/core/celery.py
index 2f4dce954576a5d688311c466bc2b9fb4ad4e151..27a67c539babfc61701cc9521b42187ebebc5787 100644
--- a/aleksis/core/celery.py
+++ b/aleksis/core/celery.py
@@ -1,8 +1,9 @@
 import os
+
 from celery import Celery
 
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aleksis.core.settings")
 
 app = Celery("aleksis")  # noqa
-app.config_from_object('django.conf:settings', namespace='CELERY')
+app.config_from_object("django.conf:settings", namespace="CELERY")
 app.autodiscover_tasks()
diff --git a/aleksis/core/filters.py b/aleksis/core/filters.py
index 6e813e888c548254fc7d153bd641b8c857523293..12265c33c6b9e0d94a9dfb5e87d9111c721fc4ad 100644
--- a/aleksis/core/filters.py
+++ b/aleksis/core/filters.py
@@ -1,4 +1,4 @@
-from django_filters import FilterSet, CharFilter
+from django_filters import CharFilter, FilterSet
 from material import Layout, Row
 
 
diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py
index 92faded47a9da5912dbf2219ecb764737cd3b745..a7989eb4842e4e90291a7b92ab8bdee4805e71ba 100644
--- a/aleksis/core/forms.py
+++ b/aleksis/core/forms.py
@@ -1,4 +1,4 @@
-from datetime import time, datetime
+from datetime import datetime, time
 from typing import Optional
 
 from django import forms
@@ -10,11 +10,15 @@ from django.utils.translation import gettext_lazy as _
 
 from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget
 from dynamic_preferences.forms import PreferenceForm
-from material import Layout, Fieldset, Row
+from material import Fieldset, Layout, Row
 
 from .mixins import ExtensibleForm
-from .models import Group, Person, Announcement, AnnouncementRecipient
-from .registries import site_preferences_registry, person_preferences_registry, group_preferences_registry
+from .models import Announcement, AnnouncementRecipient, Group, Person
+from .registries import (
+    group_preferences_registry,
+    person_preferences_registry,
+    site_preferences_registry,
+)
 
 
 class PersonAccountForm(forms.ModelForm):
diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py
index 985273c9621f88ba4ccd4bec1a8cd58b1ae57d3d..c91287d016f24487163a332376b2cc95a9a78cf0 100644
--- a/aleksis/core/menus.py
+++ b/aleksis/core/menus.py
@@ -41,9 +41,7 @@ MENUS = {
                     "name": _("2FA"),
                     "url": "two_factor:profile",
                     "icon": "phonelink_lock",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                    ],
+                    "validators": ["menu_generator.validators.is_authenticated",],
                 },
                 {
                     "name": _("Me"),
@@ -78,7 +76,10 @@ MENUS = {
                     "url": "announcements",
                     "icon": "announcement",
                     "validators": [
-                        ("aleksis.core.util.predicates.permission_validator", "core.view_announcements"),
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "core.view_announcements",
+                        ),
                     ],
                 },
                 {
@@ -94,7 +95,10 @@ MENUS = {
                     "url": "system_status",
                     "icon": "power_settings_new",
                     "validators": [
-                        ("aleksis.core.util.predicates.permission_validator", "core.view_system_status"),
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "core.view_system_status",
+                        ),
                     ],
                 },
                 {
@@ -110,16 +114,17 @@ MENUS = {
                     "url": "preferences_site",
                     "icon": "settings",
                     "validators": [
-                        ("aleksis.core.util.predicates.permission_validator", "core.change_site_preferences"),
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "core.change_site_preferences",
+                        ),
                     ],
                 },
                 {
                     "name": _("Backend Admin"),
                     "url": "admin:index",
                     "icon": "settings",
-                    "validators": [
-                        "menu_generator.validators.is_superuser",
-                    ],
+                    "validators": ["menu_generator.validators.is_superuser",],
                 },
             ],
         },
@@ -128,7 +133,9 @@ MENUS = {
             "url": "#",
             "icon": "people",
             "root": True,
-            "validators": [("aleksis.core.util.predicates.permission_validator", "core.view_people_menu")],
+            "validators": [
+                ("aleksis.core.util.predicates.permission_validator", "core.view_people_menu")
+            ],
             "submenu": [
                 {
                     "name": _("Persons"),
@@ -151,7 +158,10 @@ MENUS = {
                     "url": "persons_accounts",
                     "icon": "person_add",
                     "validators": [
-                        ("aleksis.core.util.predicates.permission_validator", "core.link_persons_accounts")
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "core.link_persons_accounts",
+                        )
                     ],
                 },
                 {
@@ -159,7 +169,10 @@ MENUS = {
                     "url": "groups_child_groups",
                     "icon": "group_add",
                     "validators": [
-                        ("aleksis.core.util.predicates.permission_validator", "core.assign_child_groups_to_groups")
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "core.assign_child_groups_to_groups",
+                        )
                     ],
                 },
             ],
@@ -170,7 +183,10 @@ MENUS = {
             "name": _("Assign child groups to groups"),
             "url": "groups_child_groups",
             "validators": [
-                ("aleksis.core.util.predicates.permission_validator", "core.assign_child_groups_to_groups")
+                (
+                    "aleksis.core.util.predicates.permission_validator",
+                    "core.assign_child_groups_to_groups",
+                )
             ],
         },
     ],
diff --git a/aleksis/core/migrations/0001_initial.py b/aleksis/core/migrations/0001_initial.py
index 15eb9d297b01a571c60120dd9ce64fb8bbeca446..7a31953baef6db56de5898e53b0ac1da9ddacb5f 100644
--- a/aleksis/core/migrations/0001_initial.py
+++ b/aleksis/core/migrations/0001_initial.py
@@ -1,13 +1,15 @@
 # Generated by Django 3.0.2 on 2020-01-03 19:18
 
-import aleksis.core.mixins
-from django.conf import settings
 import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
 import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
 import image_cropping.fields
 import phonenumber_field.modelfields
 
+import aleksis.core.mixins
+
 
 class Migration(migrations.Migration):
     initial = True
diff --git a/aleksis/core/migrations/0002_activity_notification.py b/aleksis/core/migrations/0002_activity_notification.py
index 0b05696e31ab754c009dd64743b06cd2d73deaa1..8df1be8adbc034dd0c69f7e378697da39b0161c5 100644
--- a/aleksis/core/migrations/0002_activity_notification.py
+++ b/aleksis/core/migrations/0002_activity_notification.py
@@ -1,9 +1,9 @@
 # Generated by Django 3.0.2 on 2020-01-03 19:19
 
-from django.conf import settings
-from django.db import migrations, models
 import django.db.models.deletion
 import django.utils.timezone
+from django.conf import settings
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/migrations/0003_add_verbose_names.py b/aleksis/core/migrations/0003_add_verbose_names.py
index 68e2e00302fdfc5d8e4be9c891e76ca2aea28225..776919bc99e4af2a20479183a924189a150db499 100644
--- a/aleksis/core/migrations/0003_add_verbose_names.py
+++ b/aleksis/core/migrations/0003_add_verbose_names.py
@@ -1,7 +1,7 @@
 # Generated by Django 3.0.2 on 2020-01-05 16:50
 
-from django.db import migrations, models
 import django.utils.timezone
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/migrations/0004_replace_user_by_person.py b/aleksis/core/migrations/0004_replace_user_by_person.py
index 32222f8782866903eaa138880c2477291993cb75..48952ca885da4f7c9081ebd37c6bb2d66e371772 100644
--- a/aleksis/core/migrations/0004_replace_user_by_person.py
+++ b/aleksis/core/migrations/0004_replace_user_by_person.py
@@ -1,7 +1,7 @@
 # Generated by Django 3.0.2 on 2020-01-05 18:32
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/migrations/0009_dashboard_widget.py b/aleksis/core/migrations/0009_dashboard_widget.py
index b57c272b4cc8501e41b5ffaf9f2ec3d796795021..672d516d6c9dda37ff672a2e747e80ba0cf81b2b 100644
--- a/aleksis/core/migrations/0009_dashboard_widget.py
+++ b/aleksis/core/migrations/0009_dashboard_widget.py
@@ -1,7 +1,7 @@
 # Generated by Django 3.0.2 on 2020-01-29 16:45
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/migrations/0011_make_primary_group_optional.py b/aleksis/core/migrations/0011_make_primary_group_optional.py
index 0d28af2f4c01013a139328851aba9f5881e1d883..d63c65e45d57124684d5bec69135a5ca1590bb2f 100644
--- a/aleksis/core/migrations/0011_make_primary_group_optional.py
+++ b/aleksis/core/migrations/0011_make_primary_group_optional.py
@@ -1,7 +1,7 @@
 # Generated by Django 3.0.2 on 2020-02-03 22:41
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/migrations/0012_announcement.py b/aleksis/core/migrations/0012_announcement.py
index 7cfd66158d4565fed228194e753c56f516fd4b6b..5d73e965829370c42357d45e82dcd5f0f897ddd9 100644
--- a/aleksis/core/migrations/0012_announcement.py
+++ b/aleksis/core/migrations/0012_announcement.py
@@ -1,8 +1,9 @@
 # Generated by Django 3.0.3 on 2020-02-10 14:22
 
 import datetime
-from django.db import migrations, models
+
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/migrations/0013_extensible_model_as_default.py b/aleksis/core/migrations/0013_extensible_model_as_default.py
index 4e23d86f49c0fcc53d92813b794c4083e475a6e3..00abbb392cb8e76441168a3934c5763fb318d828 100644
--- a/aleksis/core/migrations/0013_extensible_model_as_default.py
+++ b/aleksis/core/migrations/0013_extensible_model_as_default.py
@@ -1,9 +1,10 @@
 # Generated by Django 3.0.3 on 2020-02-20 12:24
 
-import aleksis.core.util.core_helpers
 import django.contrib.postgres.fields.jsonb
 from django.db import migrations, models
 
+import aleksis.core.util.core_helpers
+
 
 class Migration(migrations.Migration):
 
diff --git a/aleksis/core/migrations/0014_accouncement_recipients.py b/aleksis/core/migrations/0014_accouncement_recipients.py
index 50e78da8fbb67c81fade2cd3e8923ada4653cc08..aac4f16b5f3cb607d4580eed3748ee1ae4e66800 100644
--- a/aleksis/core/migrations/0014_accouncement_recipients.py
+++ b/aleksis/core/migrations/0014_accouncement_recipients.py
@@ -1,9 +1,10 @@
 # Generated by Django 3.0.3 on 2020-03-11 18:43
 
-import aleksis.core.models
 import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
+
+import aleksis.core.models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/migrations/0016_custom_menus.py b/aleksis/core/migrations/0016_custom_menus.py
index 4961d51c6b2e3e34da2245c288e06864bbef3090..816bbee4472db71d677f4b51ba045390af16f04d 100644
--- a/aleksis/core/migrations/0016_custom_menus.py
+++ b/aleksis/core/migrations/0016_custom_menus.py
@@ -1,8 +1,8 @@
 # Generated by Django 3.0.4 on 2020-03-21 16:39
 
 import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/migrations/0022_group_types.py b/aleksis/core/migrations/0022_group_types.py
index c521da4a6f5a5d6f6c314ee0b34e57cb374c9e7c..8abdd146705aed72807ccadae24fc1290cc912e3 100644
--- a/aleksis/core/migrations/0022_group_types.py
+++ b/aleksis/core/migrations/0022_group_types.py
@@ -1,8 +1,8 @@
 # Generated by Django 3.0.5 on 2020-04-13 13:44
 
 import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/migrations/0025_dynamic_preferences.py b/aleksis/core/migrations/0025_dynamic_preferences.py
index 35486e8d1b045733b7623ba972aa76e0f94dcaca..08d85f3877c145950091ab33a70f6f62d3961757 100644
--- a/aleksis/core/migrations/0025_dynamic_preferences.py
+++ b/aleksis/core/migrations/0025_dynamic_preferences.py
@@ -1,7 +1,7 @@
 # Generated by Django 3.0.5 on 2020-04-30 19:41
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py
index 0063750f80162ecd310253f9b26ebf221c5218fa..35fb855c6062c72192a33049ae8bb6ae861e6176 100644
--- a/aleksis/core/mixins.py
+++ b/aleksis/core/mixins.py
@@ -6,13 +6,13 @@ from django.contrib.sites.managers import CurrentSiteManager
 from django.contrib.sites.models import Site
 from django.db import models
 from django.db.models import QuerySet
-from django.forms.models import ModelFormMetaclass, ModelForm
+from django.forms.models import ModelForm, ModelFormMetaclass
 
+import reversion
 from easyaudit.models import CRUDEvent
 from guardian.admin import GuardedModelAdmin
 from jsonstore.fields import JSONField, JSONFieldMixin
-from material.base import LayoutNode, Layout
-import reversion
+from material.base import Layout, LayoutNode
 from rules.contrib.admin import ObjectPermissionsModelAdmin
 
 
@@ -187,8 +187,10 @@ class ExtensibleModel(CRUDMixin):
     class Meta:
         abstract = True
 
+
 class PureDjangoModel(object):
     """ No-op mixin to mark a model as deliberately not using ExtensibleModel """
+
     pass
 
 
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 03e38d52a8a7f7b3c9a76533dc7e18a59010469d..542a4025512ad37f6a45ea048819f9599abc413d 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -1,7 +1,6 @@
 from datetime import date, datetime
-from typing import Optional, Iterable, Union, Sequence, List
+from typing import Iterable, List, Optional, Sequence, Union
 
-import jsonstore
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Group as DjangoGroup
 from django.contrib.contenttypes.fields import GenericForeignKey
@@ -14,6 +13,8 @@ from django.urls import reverse
 from django.utils import timezone
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
+
+import jsonstore
 from dynamic_preferences.models import PerInstancePreferenceModel
 from image_cropping import ImageCropField, ImageRatioField
 from phonenumber_field.modelfields import PhoneNumberField
@@ -24,7 +25,6 @@ from .tasks import send_notification
 from .util.core_helpers import get_site_preferences, now_tomorrow
 from .util.model_helpers import ICONS
 
-
 FIELD_CHOICES = (
     ("BooleanField", _("Boolean (Yes/No)")),
     ("CharField", _("Text (one line)")),
@@ -63,7 +63,12 @@ class Person(ExtensibleModel):
     SEX_CHOICES = [("f", _("female")), ("m", _("male"))]
 
     user = models.OneToOneField(
-        get_user_model(), on_delete=models.SET_NULL, blank=True, null=True, related_name="person", verbose_name=_("Linked user")
+        get_user_model(),
+        on_delete=models.SET_NULL,
+        blank=True,
+        null=True,
+        related_name="person",
+        verbose_name=_("Linked user"),
     )
     is_active = models.BooleanField(verbose_name=_("Is person active?"), default=True)
 
@@ -94,14 +99,19 @@ class Person(ExtensibleModel):
     photo_cropping = ImageRatioField("photo", "600x800", size_warning=True)
 
     guardians = models.ManyToManyField(
-        "self", verbose_name=_("Guardians / Parents"), symmetrical=False, related_name="children", blank=True
+        "self",
+        verbose_name=_("Guardians / Parents"),
+        symmetrical=False,
+        related_name="children",
+        blank=True,
     )
 
-    primary_group = models.ForeignKey("Group", models.SET_NULL, null=True, blank=True, verbose_name=_("Primary group"))
+    primary_group = models.ForeignKey(
+        "Group", models.SET_NULL, null=True, blank=True, verbose_name=_("Primary group")
+    )
 
     description = models.TextField(verbose_name=_("Description"), blank=True, null=True)
 
-
     def get_absolute_url(self) -> str:
         return reverse("person_by_id", args=[self.id])
 
@@ -150,9 +160,9 @@ class Person(ExtensibleModel):
         """ Age of the person at a given date and time """
 
         years = today.year - self.date_of_birth.year
-        if (self.date_of_birth.month > today.month
-            or (self.date_of_birth.month == today.month
-                and self.date_of_birth.day > today.day)):
+        if self.date_of_birth.month > today.month or (
+            self.date_of_birth.month == today.month and self.date_of_birth.day > today.day
+        ):
             years -= 1
         return years
 
@@ -182,9 +192,7 @@ class Person(ExtensibleModel):
         User = get_user_model()
         if not User.objects.filter(is_superuser=True).exists():
             admin = User.objects.create_superuser(
-                username='admin',
-                email='root@example.com',
-                password='admin'
+                username="admin", email="root@example.com", password="admin"
             )
             admin.save()
 
@@ -225,7 +233,9 @@ class AdditionalField(ExtensibleModel):
     """ An additional field that can be linked to a group """
 
     title = models.CharField(verbose_name=_("Title of field"), max_length=255)
-    field_type = models.CharField(verbose_name=_("Type of field"), choices=FIELD_CHOICES, max_length=50)
+    field_type = models.CharField(
+        verbose_name=_("Type of field"), choices=FIELD_CHOICES, max_length=50
+    )
 
     class Meta:
         verbose_name = _("Addtitional field for groups")
@@ -241,17 +251,25 @@ class Group(ExtensibleModel):
         ordering = ["short_name", "name"]
         verbose_name = _("Group")
         verbose_name_plural = _("Groups")
-        permissions = (
-            ("assign_child_groups_to_groups", _("Can assign child groups to groups")),
-        )
+        permissions = (("assign_child_groups_to_groups", _("Can assign child groups to groups")),)
 
     icon_ = "group"
 
     name = models.CharField(verbose_name=_("Long name"), max_length=255, unique=True)
-    short_name = models.CharField(verbose_name=_("Short name"), max_length=255, unique=True, blank=True, null=True)
+    short_name = models.CharField(
+        verbose_name=_("Short name"), max_length=255, unique=True, blank=True, null=True
+    )
 
-    members = models.ManyToManyField("Person", related_name="member_of", blank=True, through="PersonGroupThrough", verbose_name=_("Members"))
-    owners = models.ManyToManyField("Person", related_name="owner_of", blank=True, verbose_name=_("Owners"))
+    members = models.ManyToManyField(
+        "Person",
+        related_name="member_of",
+        blank=True,
+        through="PersonGroupThrough",
+        verbose_name=_("Members"),
+    )
+    owners = models.ManyToManyField(
+        "Person", related_name="owner_of", blank=True, verbose_name=_("Owners")
+    )
 
     parent_groups = models.ManyToManyField(
         "self",
@@ -261,10 +279,16 @@ class Group(ExtensibleModel):
         blank=True,
     )
 
-    type = models.ForeignKey("GroupType", on_delete=models.SET_NULL, related_name="type", verbose_name=_("Type of group"), null=True, blank=True)
+    type = models.ForeignKey(
+        "GroupType",
+        on_delete=models.SET_NULL,
+        related_name="type",
+        verbose_name=_("Type of group"),
+        null=True,
+        blank=True,
+    )
     additional_fields = models.ManyToManyField(AdditionalField, verbose_name=_("Additional fields"))
 
-
     def get_absolute_url(self) -> str:
         return reverse("group_by_id", args=[self.id])
 
@@ -284,9 +308,9 @@ class Group(ExtensibleModel):
         dj_group, _ = DjangoGroup.objects.get_or_create(name=self.name)
         dj_group.user_set.set(
             list(
-                self.members.filter(user__isnull=False).values_list("user", flat=True).union(
-                    self.owners.filter(user__isnull=False).values_list("user", flat=True)
-                )
+                self.members.filter(user__isnull=False)
+                .values_list("user", flat=True)
+                .union(self.owners.filter(user__isnull=False).values_list("user", flat=True))
             )
         )
         dj_group.save()
@@ -311,10 +335,13 @@ class PersonGroupThrough(ExtensibleModel):
             field_instance = field_class(verbose_name=field.title)
             setattr(self, field_name, field_instance)
 
+
 class Activity(ExtensibleModel):
     """ Activity of a user to trace some actions done in AlekSIS in displayable form """
 
-    user = models.ForeignKey("Person", on_delete=models.CASCADE, related_name="activities", verbose_name=_("User"))
+    user = models.ForeignKey(
+        "Person", on_delete=models.CASCADE, related_name="activities", verbose_name=_("User")
+    )
 
     title = models.CharField(max_length=150, verbose_name=_("Title"))
     description = models.TextField(max_length=500, verbose_name=_("Description"))
@@ -333,7 +360,12 @@ class Notification(ExtensibleModel):
     """ Notification to submit to a user """
 
     sender = models.CharField(max_length=100, verbose_name=_("Sender"))
-    recipient = models.ForeignKey("Person", on_delete=models.CASCADE, related_name="notifications", verbose_name=_("Recipient"))
+    recipient = models.ForeignKey(
+        "Person",
+        on_delete=models.CASCADE,
+        related_name="notifications",
+        verbose_name=_("Recipient"),
+    )
 
     title = models.CharField(max_length=150, verbose_name=_("Title"))
     description = models.TextField(max_length=500, verbose_name=_("Description"))
@@ -367,14 +399,14 @@ class AnnouncementQuerySet(models.QuerySet):
 
         if isinstance(obj, models.QuerySet):
             ct = ContentType.objects.get_for_model(obj.model)
-            pks = list(obj.values_list('pk', flat=True))
+            pks = list(obj.values_list("pk", flat=True))
         else:
             ct = ContentType.objects.get_for_model(obj)
             pks = [obj.pk]
 
         return self.filter(recipients__content_type=ct, recipients__recipient_id__in=pks)
 
-    def at_time(self,when: Optional[datetime] = None ) -> models.QuerySet:
+    def at_time(self, when: Optional[datetime] = None) -> models.QuerySet:
         """ Get all announcements at a certain time """
 
         when = when or timezone.datetime.now()
@@ -429,8 +461,7 @@ class Announcement(ExtensibleModel):
         verbose_name=_("Date and time from when to show"), default=timezone.datetime.now
     )
     valid_until = models.DateTimeField(
-        verbose_name=_("Date and time until when to show"),
-        default=now_tomorrow,
+        verbose_name=_("Date and time until when to show"), default=now_tomorrow,
     )
 
     @property
@@ -464,7 +495,9 @@ class AnnouncementRecipient(ExtensibleModel):
     returning a flat list of Person objects.
     """
 
-    announcement = models.ForeignKey(Announcement, on_delete=models.CASCADE, related_name="recipients")
+    announcement = models.ForeignKey(
+        Announcement, on_delete=models.CASCADE, related_name="recipients"
+    )
 
     content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
     recipient_id = models.PositiveIntegerField()
diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py
index a5538eb96420d49b995bcaddc077fa56d84ad99d..c363875824b5ebba86637c5c557953233278982f 100644
--- a/aleksis/core/preferences.py
+++ b/aleksis/core/preferences.py
@@ -1,16 +1,24 @@
 from django.conf import settings
-from django.forms import EmailField, URLField, ImageField
+from django.forms import EmailField, ImageField, URLField
 from django.utils.translation import gettext_lazy as _
 
 from colorfield.widgets import ColorWidget
-from dynamic_preferences.types import BooleanPreference, ChoicePreference, StringPreference, FilePreference
 from dynamic_preferences.preferences import Section
 from dynamic_preferences.registries import global_preferences_registry
-
-from .registries import group_preferences_registry, person_preferences_registry, site_preferences_registry
+from dynamic_preferences.types import (
+    BooleanPreference,
+    ChoicePreference,
+    FilePreference,
+    StringPreference,
+)
+
+from .registries import (
+    group_preferences_registry,
+    person_preferences_registry,
+    site_preferences_registry,
+)
 from .util.notifications import get_notification_choices_lazy
 
-
 general = Section("general")
 school = Section("school")
 theme = Section("theme")
@@ -127,11 +135,11 @@ class AdressingNameFormat(ChoicePreference):
     required = False
     verbose_name = _("Name format for addressing")
     choices = (
-               (None, "-----"),
-               ("german", "John Doe"),
-               ("english", "Doe, John"),
-               ("dutch", "Doe John"),
-              )
+        (None, "-----"),
+        ("german", "John Doe"),
+        ("english", "Doe, John"),
+        ("dutch", "Doe John"),
+    )
 
 
 @person_preferences_registry.register
diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index 4ebbaee0bc302de092b4f0676eb107254602a547..5753bbc5374e03dccb97b935b1eba6ae65f04239 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -1,17 +1,16 @@
 from rules import add_perm, always_allow
 
-from .models import Person, Group, Announcement
+from .models import Announcement, Group, Person
 from .util.predicates import (
-    has_person,
-    has_global_perm,
     has_any_object,
-    is_current_person,
+    has_global_perm,
     has_object_perm,
+    has_person,
+    is_current_person,
     is_group_owner,
     is_notification_recipient,
 )
 
-
 add_perm("core", always_allow)
 
 # View dashboard
@@ -41,7 +40,9 @@ add_perm("core.view_address", view_address_predicate)
 
 # View person contact details
 view_contact_details_predicate = has_person & (
-    has_global_perm("core.view_contact_details") | has_object_perm("core.view_contact_details") | is_current_person
+    has_global_perm("core.view_contact_details")
+    | has_object_perm("core.view_contact_details")
+    | is_current_person
 )
 add_perm("core.view_contact_details", view_contact_details_predicate)
 
@@ -53,7 +54,9 @@ add_perm("core.view_photo", view_photo_predicate)
 
 # View persons groups
 view_groups_predicate = has_person & (
-    has_global_perm("core.view_person_groups") | has_object_perm("core.view_person_groups") | is_current_person
+    has_global_perm("core.view_person_groups")
+    | has_object_perm("core.view_person_groups")
+    | is_current_person
 )
 add_perm("core.view_person_groups", view_groups_predicate)
 
@@ -86,7 +89,9 @@ edit_group_predicate = has_person & (
 add_perm("core.edit_group", edit_group_predicate)
 
 # Assign child groups to groups
-assign_child_groups_to_groups_predicate = has_person & has_global_perm("core.assign_child_groups_to_groups")
+assign_child_groups_to_groups_predicate = has_person & has_global_perm(
+    "core.assign_child_groups_to_groups"
+)
 add_perm("core.assign_child_groups_to_groups", assign_child_groups_to_groups_predicate)
 
 # Edit school information
@@ -111,13 +116,15 @@ add_perm("core.mark_notification_as_read", mark_notification_as_read_predicate)
 
 # View announcements
 view_announcements_predicate = has_person & (
-    has_global_perm("core.view_announcement") | has_any_object("core.view_announcement", Announcement)
+    has_global_perm("core.view_announcement")
+    | has_any_object("core.view_announcement", Announcement)
 )
 add_perm("core.view_announcements", view_announcements_predicate)
 
 # Create or edit announcement
 create_or_edit_announcement_predicate = has_person & (
-    has_global_perm("core.add_announcement") & (has_global_perm("core.change_announcement") | has_object_perm("core.change_announcement"))
+    has_global_perm("core.add_announcement")
+    & (has_global_perm("core.change_announcement") | has_object_perm("core.change_announcement"))
 )
 add_perm("core.create_or_edit_announcement", create_or_edit_announcement_predicate)
 
@@ -136,32 +143,54 @@ view_system_status_predicate = has_person & has_global_perm("core.view_system_st
 add_perm("core.view_system_status", view_system_status_predicate)
 
 # View people menu (persons + objects)
-add_perm("core.view_people_menu", has_person & (view_persons_predicate | view_groups_predicate | link_persons_accounts_predicate | assign_child_groups_to_groups_predicate))
+add_perm(
+    "core.view_people_menu",
+    has_person
+    & (
+        view_persons_predicate
+        | view_groups_predicate
+        | link_persons_accounts_predicate
+        | assign_child_groups_to_groups_predicate
+    ),
+)
 
 # View admin menu
-view_admin_menu_predicate = has_person & (manage_data_predicate | manage_school_predicate | impersonate_predicate | view_system_status_predicate | view_announcements_predicate)
+view_admin_menu_predicate = has_person & (
+    manage_data_predicate
+    | manage_school_predicate
+    | impersonate_predicate
+    | view_system_status_predicate
+    | view_announcements_predicate
+)
 add_perm("core.view_admin_menu", view_admin_menu_predicate)
 
 # View person personal details
 view_personal_details_predicate = has_person & (
-    has_global_perm("core.view_personal_details") | has_object_perm("core.view_personal_details") | is_current_person
+    has_global_perm("core.view_personal_details")
+    | has_object_perm("core.view_personal_details")
+    | is_current_person
 )
 add_perm("core.view_personal_details", view_personal_details_predicate)
 
 # Change site preferences
 change_site_preferences = has_person & (
-    has_global_perm("core.change_site_preferences") | has_object_perm("core.change_site_preferences")
+    has_global_perm("core.change_site_preferences")
+    | has_object_perm("core.change_site_preferences")
 )
 add_perm("core.change_site_preferences", change_site_preferences)
 
 # Change person preferences
 change_person_preferences = has_person & (
-    has_global_perm("core.change_person_preferences") | has_object_perm("core.change_person_preferences") | is_current_person
+    has_global_perm("core.change_person_preferences")
+    | has_object_perm("core.change_person_preferences")
+    | is_current_person
 )
 add_perm("core.change_person_preferences", change_person_preferences)
 
 # Change group preferences
 change_group_preferences = has_person & (
-    has_global_perm("core.change_group_preferences") | has_object_perm("core.change_group_preferences") | is_group_owner
+    has_global_perm("core.change_group_preferences")
+    | has_object_perm("core.change_group_preferences")
+    | is_group_owner
 )
 add_perm("core.change_group_preferences", change_group_preferences)
diff --git a/aleksis/core/search_indexes.py b/aleksis/core/search_indexes.py
index 39d6be9906aa0ebe6afb9ca707a5ce355d7445ee..7c7beca9e1d691a5548113c37d13a0109c85fe61 100644
--- a/aleksis/core/search_indexes.py
+++ b/aleksis/core/search_indexes.py
@@ -1,4 +1,4 @@
-from .models import Person, Group
+from .models import Group, Person
 from .util.search import Indexable, SearchIndex
 
 
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 58ff7e2d6b17fec8e3f49fc2f2611cab366ed38f..3153f9d8dd83b128ba8c35852c6addcb4c3fe599 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -9,7 +9,12 @@ from django.utils.translation import gettext_lazy as _
 from dynaconf import LazySettings
 from easy_thumbnails.conf import Settings as thumbnail_settings
 
-from .util.core_helpers import get_app_packages, lazy_preference, merge_app_settings, lazy_get_favicon_url
+from .util.core_helpers import (
+    get_app_packages,
+    lazy_get_favicon_url,
+    lazy_preference,
+    merge_app_settings,
+)
 
 ENVVAR_PREFIX_FOR_DYNACONF = "ALEKSIS"
 DIRS_FOR_DYNACONF = ["/etc/aleksis"]
@@ -200,7 +205,12 @@ AUTHENTICATION_BACKENDS = []
 if _settings.get("ldap.uri", None):
     # LDAP dependencies are not necessarily installed, so import them here
     import ldap  # noqa
-    from django_auth_ldap.config import LDAPSearch, NestedGroupOfNamesType, NestedGroupOfUniqueNamesType, PosixGroupType  # noqa
+    from django_auth_ldap.config import (
+        LDAPSearch,
+        NestedGroupOfNamesType,
+        NestedGroupOfUniqueNamesType,
+        PosixGroupType,
+    )  # noqa
 
     # Enable Django's integration to LDAP
     AUTHENTICATION_BACKENDS.append("django_auth_ldap.backend.LDAPBackend")
@@ -243,16 +253,20 @@ if _settings.get("ldap.uri", None):
         elif _group_type == "posixgroup":
             AUTH_LDAP_GROUP_TYPE = PosixGroupType()
 
-        AUTH_LDAP_USER_FLAGS_BY_GROUP = {
-        }
+        AUTH_LDAP_USER_FLAGS_BY_GROUP = {}
         for _flag in ["is_active", "is_staff", "is_superuser"]:
             _dn = _settings.get(f"ldap.groups.flags.{_flag}", None)
             if _dn:
                 AUTH_LDAP_USER_FLAGS_BY_GROUP[_flag] = _dn
 
         # Backend admin requires superusers to also be staff members
-        if "is_superuser" in AUTH_LDAP_USER_FLAGS_BY_GROUP and "is_staff" not in AUTH_LDAP_USER_FLAGS_BY_GROUP:
-            AUTH_LDAP_USER_FLAGS_BY_GROUP["is_staff"] = AUTH_LDAP_USER_FLAGS_BY_GROUP["is_superuser"]
+        if (
+            "is_superuser" in AUTH_LDAP_USER_FLAGS_BY_GROUP
+            and "is_staff" not in AUTH_LDAP_USER_FLAGS_BY_GROUP
+        ):
+            AUTH_LDAP_USER_FLAGS_BY_GROUP["is_staff"] = AUTH_LDAP_USER_FLAGS_BY_GROUP[
+                "is_superuser"
+            ]
 
 # Add ModelBckend last so all other backends get a chance
 # to verify passwords first
@@ -313,7 +327,10 @@ ANY_JS = {
         "css_url": JS_URL + "/material-design-icons-iconfont/dist/material-design-icons.css"
     },
     "paper-css": {"css_url": JS_URL + "/paper-css/paper.min.css"},
-    "select2-materialize": {"css_url": JS_URL + "/select2-materialize/select2-materialize.css", "js_url": JS_URL + "/select2-materialize/index.js"},
+    "select2-materialize": {
+        "css_url": JS_URL + "/select2-materialize/select2-materialize.css",
+        "js_url": JS_URL + "/select2-materialize/index.js",
+    },
 }
 
 merge_app_settings("ANY_JS", ANY_JS, True)
@@ -344,7 +361,7 @@ if _settings.get("mail.server.host", None):
         EMAIL_HOST_USER = _settings.get("mail.server.user")
         EMAIL_HOST_PASSWORD = _settings.get("mail.server.password")
 
-TEMPLATED_EMAIL_BACKEND = 'templated_email.backends.vanilla_django'
+TEMPLATED_EMAIL_BACKEND = "templated_email.backends.vanilla_django"
 TEMPLATED_EMAIL_AUTO_PLAIN = True
 
 
@@ -421,24 +438,50 @@ PWA_APP_BACKGROUND_COLOR = "#ffffff"
 PWA_APP_DISPLAY = "standalone"
 PWA_APP_ORIENTATION = "any"
 PWA_APP_ICONS = [  # three icons to upload dbsettings
-    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="android",
-                                 default=STATIC_URL + "icons/android_192.png"), "sizes": "192x192"},
-    {"src": lazy_get_favicon_url(title="pwa_icon", size=512, rel="android",
-                                 default=STATIC_URL + "icons/android_512.png"), "sizes": "512x512"},
+    {
+        "src": lazy_get_favicon_url(
+            title="pwa_icon", size=192, rel="android", default=STATIC_URL + "icons/android_192.png"
+        ),
+        "sizes": "192x192",
+    },
+    {
+        "src": lazy_get_favicon_url(
+            title="pwa_icon", size=512, rel="android", default=STATIC_URL + "icons/android_512.png"
+        ),
+        "sizes": "512x512",
+    },
 ]
 PWA_APP_ICONS_APPLE = [
-    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple",
-                                 default=STATIC_URL + "icons/apple_76.png"), "sizes": "76x76"},
-    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple",
-                                 default=STATIC_URL + "icons/apple_114.png"), "sizes": "114x114"},
-    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple",
-                                 default=STATIC_URL + "icons/apple_152.png"), "sizes": "152x152"},
-    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple",
-                                 default=STATIC_URL + "icons/apple_180.png"), "sizes": "180x180"},
+    {
+        "src": lazy_get_favicon_url(
+            title="pwa_icon", size=192, rel="apple", default=STATIC_URL + "icons/apple_76.png"
+        ),
+        "sizes": "76x76",
+    },
+    {
+        "src": lazy_get_favicon_url(
+            title="pwa_icon", size=192, rel="apple", default=STATIC_URL + "icons/apple_114.png"
+        ),
+        "sizes": "114x114",
+    },
+    {
+        "src": lazy_get_favicon_url(
+            title="pwa_icon", size=192, rel="apple", default=STATIC_URL + "icons/apple_152.png"
+        ),
+        "sizes": "152x152",
+    },
+    {
+        "src": lazy_get_favicon_url(
+            title="pwa_icon", size=192, rel="apple", default=STATIC_URL + "icons/apple_180.png"
+        ),
+        "sizes": "180x180",
+    },
 ]
 PWA_APP_SPLASH_SCREEN = [
     {
-        "src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple", default=STATIC_URL + "icons/apple_180.png"),
+        "src": lazy_get_favicon_url(
+            title="pwa_icon", size=192, rel="apple", default=STATIC_URL + "icons/apple_180.png"
+        ),
         "media": "(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)",
     }
 ]
@@ -449,65 +492,114 @@ PWA_SERVICE_WORKER_PATH = os.path.join(STATIC_ROOT, "js", "serviceworker.js")
 SITE_ID = 1
 
 CKEDITOR_CONFIGS = {
-    'default': {
-        'toolbar_Basic': [
-            ['Source', '-', 'Bold', 'Italic']
-        ],
-        'toolbar_Full': [
-            {'name': 'document', 'items': ['Source', '-', 'Save', 'NewPage', 'Preview', 'Print', '-', 'Templates']},
-            {'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']},
-            {'name': 'editing', 'items': ['Find', 'Replace', '-', 'SelectAll']},
-            {'name': 'insert',
-             'items': ['Image', 'Table', 'HorizontalRule', 'Smiley', 'SpecialChar', 'PageBreak', 'Iframe']},
-            '/',
-            {'name': 'basicstyles',
-             'items': ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat']},
-            {'name': 'paragraph',
-             'items': ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-',
-                       'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl',
-                       'Language']},
-            {'name': 'links', 'items': ['Link', 'Unlink', 'Anchor']},
-            '/',
-            {'name': 'styles', 'items': ['Styles', 'Format', 'Font', 'FontSize']},
-            {'name': 'colors', 'items': ['TextColor', 'BGColor']},
-            {'name': 'tools', 'items': ['Maximize', 'ShowBlocks']},
-            {'name': 'about', 'items': ['About']},
-            {'name': 'customtools', 'items': [
-                'Preview',
-                'Maximize',
-            ]},
+    "default": {
+        "toolbar_Basic": [["Source", "-", "Bold", "Italic"]],
+        "toolbar_Full": [
+            {
+                "name": "document",
+                "items": ["Source", "-", "Save", "NewPage", "Preview", "Print", "-", "Templates"],
+            },
+            {
+                "name": "clipboard",
+                "items": [
+                    "Cut",
+                    "Copy",
+                    "Paste",
+                    "PasteText",
+                    "PasteFromWord",
+                    "-",
+                    "Undo",
+                    "Redo",
+                ],
+            },
+            {"name": "editing", "items": ["Find", "Replace", "-", "SelectAll"]},
+            {
+                "name": "insert",
+                "items": [
+                    "Image",
+                    "Table",
+                    "HorizontalRule",
+                    "Smiley",
+                    "SpecialChar",
+                    "PageBreak",
+                    "Iframe",
+                ],
+            },
+            "/",
+            {
+                "name": "basicstyles",
+                "items": [
+                    "Bold",
+                    "Italic",
+                    "Underline",
+                    "Strike",
+                    "Subscript",
+                    "Superscript",
+                    "-",
+                    "RemoveFormat",
+                ],
+            },
+            {
+                "name": "paragraph",
+                "items": [
+                    "NumberedList",
+                    "BulletedList",
+                    "-",
+                    "Outdent",
+                    "Indent",
+                    "-",
+                    "Blockquote",
+                    "CreateDiv",
+                    "-",
+                    "JustifyLeft",
+                    "JustifyCenter",
+                    "JustifyRight",
+                    "JustifyBlock",
+                    "-",
+                    "BidiLtr",
+                    "BidiRtl",
+                    "Language",
+                ],
+            },
+            {"name": "links", "items": ["Link", "Unlink", "Anchor"]},
+            "/",
+            {"name": "styles", "items": ["Styles", "Format", "Font", "FontSize"]},
+            {"name": "colors", "items": ["TextColor", "BGColor"]},
+            {"name": "tools", "items": ["Maximize", "ShowBlocks"]},
+            {"name": "about", "items": ["About"]},
+            {"name": "customtools", "items": ["Preview", "Maximize",]},
         ],
-        'toolbar': 'Full',
-        'tabSpaces': 4,
-        'extraPlugins': ','.join([
-            'uploadimage',
-            'div',
-            'autolink',
-            'autoembed',
-            'embedsemantic',
-            'autogrow',
-            # 'devtools',
-            'widget',
-            'lineutils',
-            'clipboard',
-            'dialog',
-            'dialogui',
-            'elementspath'
-        ]),
+        "toolbar": "Full",
+        "tabSpaces": 4,
+        "extraPlugins": ",".join(
+            [
+                "uploadimage",
+                "div",
+                "autolink",
+                "autoembed",
+                "embedsemantic",
+                "autogrow",
+                # 'devtools',
+                "widget",
+                "lineutils",
+                "clipboard",
+                "dialog",
+                "dialogui",
+                "elementspath",
+            ]
+        ),
     }
 }
 
 # Which HTML tags are allowed
-BLEACH_ALLOWED_TAGS = ['p', 'b', 'i', 'u', 'em', 'strong', 'a', 'div']
+BLEACH_ALLOWED_TAGS = ["p", "b", "i", "u", "em", "strong", "a", "div"]
 
 # Which HTML attributes are allowed
-BLEACH_ALLOWED_ATTRIBUTES = ['href', 'title', 'style']
+BLEACH_ALLOWED_ATTRIBUTES = ["href", "title", "style"]
 
 # Which CSS properties are allowed in 'style' attributes (assuming
 # style is an allowed attribute)
-BLEACH_ALLOWED_STYLES = [
-    'font-family', 'font-weight', 'text-decoration', 'font-variant'
-]
+BLEACH_ALLOWED_STYLES = ["font-family", "font-weight", "text-decoration", "font-variant"]
 
 # Strip unknown tags if True, replace with HTML escaped characters if
 # False
@@ -517,23 +609,11 @@ BLEACH_STRIP_TAGS = True
 BLEACH_STRIP_COMMENTS = True
 
 LOGGING = {
-    'version': 1,
-    'disable_existing_loggers': False,
-    'handlers': {
-        'console': {
-            'class': 'logging.StreamHandler',
-            'formatter': "verbose"
-        },
-    },
-    'formatters': {
-        'verbose': {
-            'format': '%(levelname)s %(asctime)s %(module)s: %(message)s'
-        }
-    },
-    'root': {
-        'handlers': ['console'],
-        'level': _settings.get("logging.level", "WARNING"),
-    },
+    "version": 1,
+    "disable_existing_loggers": False,
+    "handlers": {"console": {"class": "logging.StreamHandler", "formatter": "verbose"},},
+    "formatters": {"verbose": {"format": "%(levelname)s %(asctime)s %(module)s: %(message)s"}},
+    "root": {"handlers": ["console"], "level": _settings.get("logging.level", "WARNING"),},
 }
 
 # Rules and permissions
@@ -550,29 +630,27 @@ HAYSTACK_BACKEND_SHORT = _settings.get("search.backend", "simple")
 
 if HAYSTACK_BACKEND_SHORT == "simple":
     HAYSTACK_CONNECTIONS = {
-        'default': {
-            'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
-        },
+        "default": {"ENGINE": "haystack.backends.simple_backend.SimpleEngine",},
     }
 elif HAYSTACK_BACKEND_SHORT == "xapian":
     HAYSTACK_CONNECTIONS = {
-        'default': {
-            'ENGINE': 'xapian_backend.XapianEngine',
-            'PATH': _settings.get("search.index", os.path.join(BASE_DIR, "xapian_index")),
+        "default": {
+            "ENGINE": "xapian_backend.XapianEngine",
+            "PATH": _settings.get("search.index", os.path.join(BASE_DIR, "xapian_index")),
         },
     }
 elif HAYSTACK_BACKEND_SHORT == "whoosh":
     HAYSTACK_CONNECTIONS = {
-        'default': {
-            'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
-            'PATH': _settings.get("search.index", os.path.join(BASE_DIR, "whoosh_index")),
+        "default": {
+            "ENGINE": "haystack.backends.whoosh_backend.WhooshEngine",
+            "PATH": _settings.get("search.index", os.path.join(BASE_DIR, "whoosh_index")),
         },
     }
 
 if _settings.get("celery.enabled", False) and _settings.get("search.celery", True):
     INSTALLED_APPS.append("celery_haystack")
-    HAYSTACK_SIGNAL_PROCESSOR = 'celery_haystack.signals.CelerySignalProcessor'
+    HAYSTACK_SIGNAL_PROCESSOR = "celery_haystack.signals.CelerySignalProcessor"
 else:
-    HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
+    HAYSTACK_SIGNAL_PROCESSOR = "haystack.signals.RealtimeSignalProcessor"
 
 HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10
diff --git a/aleksis/core/tasks.py b/aleksis/core/tasks.py
index 6fea93569610b2fbe17ba44833073f239e469979..9a9f1ea39ac3b95aade5d6fffa21e766ab042f0f 100644
--- a/aleksis/core/tasks.py
+++ b/aleksis/core/tasks.py
@@ -21,7 +21,9 @@ def backup_data() -> None:
 
     # Assemble command-line options for dbbackup management command
     db_options = "-z " * settings.DBBACKUP_COMPRESS_DB + "-e" * settings.DBBACKUP_ENCRYPT_DB
-    media_options = "-z " * settings.DBBACKUP_COMPRESS_MEDIA + "-e" * settings.DBBACKUP_ENCRYPT_MEDIA
+    media_options = (
+        "-z " * settings.DBBACKUP_COMPRESS_MEDIA + "-e" * settings.DBBACKUP_ENCRYPT_MEDIA
+    )
 
     # Hand off to dbbackup's management commands
     management.call_command("dbbackup", db_options)
diff --git a/aleksis/core/templatetags/apps.py b/aleksis/core/templatetags/apps.py
index 6f48c994917130290f2c4400fca753c4df11b06f..c25203daf2e256a436b42ef223e7976db0de8de6 100644
--- a/aleksis/core/templatetags/apps.py
+++ b/aleksis/core/templatetags/apps.py
@@ -2,4 +2,4 @@ from django.apps import AppConfig
 
 
 class TemplatetagsConfig(AppConfig):
-    name = 'templatetags'
+    name = "templatetags"
diff --git a/aleksis/core/tests/views/test_account.py b/aleksis/core/tests/views/test_account.py
index ecc15f59920ddb493ae64323a7aa4fbeebdafec4..da1c2b90ef0f867b7239817b7ce60fc1cc2478b0 100644
--- a/aleksis/core/tests/views/test_account.py
+++ b/aleksis/core/tests/views/test_account.py
@@ -10,7 +10,7 @@ def test_index_not_logged_in(client):
     response = client.get("/")
 
     assert response.status_code == 302
-    assert response['Location'].startswith(reverse(settings.LOGIN_URL))
+    assert response["Location"].startswith(reverse(settings.LOGIN_URL))
 
 
 def test_login_without_person(client, django_user_model):
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 91e32af87edfbaa21335394c342ab69ee75395e7..6f58fc46e909db034f6a61d210a5e6bddba3868f 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -34,7 +34,11 @@ urlpatterns = [
     path("group/<int:id_>", views.group, name="group_by_id"),
     path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"),
     path("", views.index, name="index"),
-    path("notifications/mark-read/<int:id_>", views.notification_mark_read, name="notification_mark_read"),
+    path(
+        "notifications/mark-read/<int:id_>",
+        views.notification_mark_read,
+        name="notification_mark_read",
+    ),
     path("announcements/", views.announcements, name="announcements"),
     path("announcement/create/", views.announcement_form, name="add_announcement"),
     path("announcement/edit/<int:pk>/", views.announcement_form, name="edit_announcement"),
@@ -45,21 +49,78 @@ urlpatterns = [
     path("impersonate/", include("impersonate.urls")),
     path("__i18n__/", include("django.conf.urls.i18n")),
     path("select2/", include("django_select2.urls")),
-    path("jsreverse.js", urls_js, name='js_reverse'),
+    path("jsreverse.js", urls_js, name="js_reverse"),
     path("calendarweek_i18n.js", calendarweek.django.i18n_js, name="calendarweek_i18n_js"),
-    path('gettext.js', JavaScriptCatalog.as_view(), name='javascript-catalog'),
-    path("preferences/site/", views.preferences, {"registry_name": "site"}, name="preferences_site"),
-    path("preferences/person/", views.preferences, {"registry_name": "person"}, name="preferences_person"),
-    path("preferences/group/", views.preferences, {"registry_name": "group"}, name="preferences_group"),
-    path("preferences/site/<int:pk>/", views.preferences, {"registry_name": "site"}, name="preferences_site"),
-    path("preferences/person/<int:pk>/", views.preferences, {"registry_name": "person"}, name="preferences_person"),
-    path("preferences/group/<int:pk>/", views.preferences, {"registry_name": "group"}, name="preferences_group"),
-    path("preferences/site/<int:pk>/<str:section>/", views.preferences, {"registry_name": "site"}, name="preferences_site" ),
-    path("preferences/person/<int:pk>/<str:section>/", views.preferences, {"registry_name": "person"}, name="preferences_person"),
-    path("preferences/group/<int:pk>/<str:section>/", views.preferences, {"registry_name": "group"}, name="preferences_group"),
-    path("preferences/site/<str:section>/", views.preferences, {"registry_name": "site"}, name="preferences_site"),
-    path("preferences/person/<str:section>/", views.preferences, {"registry_name": "person"}, name="preferences_person"),
-    path("preferences/group/<str:section>/", views.preferences, {"registry_name": "group"}, name="preferences_group"),
+    path("gettext.js", JavaScriptCatalog.as_view(), name="javascript-catalog"),
+    path(
+        "preferences/site/", views.preferences, {"registry_name": "site"}, name="preferences_site"
+    ),
+    path(
+        "preferences/person/",
+        views.preferences,
+        {"registry_name": "person"},
+        name="preferences_person",
+    ),
+    path(
+        "preferences/group/",
+        views.preferences,
+        {"registry_name": "group"},
+        name="preferences_group",
+    ),
+    path(
+        "preferences/site/<int:pk>/",
+        views.preferences,
+        {"registry_name": "site"},
+        name="preferences_site",
+    ),
+    path(
+        "preferences/person/<int:pk>/",
+        views.preferences,
+        {"registry_name": "person"},
+        name="preferences_person",
+    ),
+    path(
+        "preferences/group/<int:pk>/",
+        views.preferences,
+        {"registry_name": "group"},
+        name="preferences_group",
+    ),
+    path(
+        "preferences/site/<int:pk>/<str:section>/",
+        views.preferences,
+        {"registry_name": "site"},
+        name="preferences_site",
+    ),
+    path(
+        "preferences/person/<int:pk>/<str:section>/",
+        views.preferences,
+        {"registry_name": "person"},
+        name="preferences_person",
+    ),
+    path(
+        "preferences/group/<int:pk>/<str:section>/",
+        views.preferences,
+        {"registry_name": "group"},
+        name="preferences_group",
+    ),
+    path(
+        "preferences/site/<str:section>/",
+        views.preferences,
+        {"registry_name": "site"},
+        name="preferences_site",
+    ),
+    path(
+        "preferences/person/<str:section>/",
+        views.preferences,
+        {"registry_name": "person"},
+        name="preferences_person",
+    ),
+    path(
+        "preferences/group/<str:section>/",
+        views.preferences,
+        {"registry_name": "group"},
+        name="preferences_group",
+    ),
 ]
 
 # Serve static files from STATIC_ROOT to make it work with runserver
diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py
index 7063e378908d5092efd9bea259d64759483ccd1c..c1162d4ea8e49d3fbbcde7777ce7f851569c512b 100644
--- a/aleksis/core/util/apps.py
+++ b/aleksis/core/util/apps.py
@@ -1,5 +1,5 @@
 from importlib import import_module
-from typing import Any, List, Optional, Tuple, Sequence
+from typing import Any, List, Optional, Sequence, Tuple
 
 import django.apps
 from django.contrib.auth.signals import user_logged_in, user_logged_out
@@ -7,7 +7,7 @@ from django.db.models.signals import post_migrate, pre_migrate
 from django.http import HttpRequest
 
 from dynamic_preferences.signals import preference_updated
-from license_expression import Licensing, LicenseSymbol
+from license_expression import LicenseSymbol, Licensing
 from spdx_license_list import LICENSES
 
 from .core_helpers import copyright_years
@@ -70,13 +70,13 @@ class AppConfig(django.apps.AppConfig):
         licence = getattr(cls, "licence", None)
 
         default_dict = {
-            'isDeprecatedLicenseId': False,
-            'isFsfLibre': False,
-            'isOsiApproved': False,
-            'licenseId': 'unknown',
-            'name': 'Unknown Licence',
-            'referenceNumber': -1,
-            'url': '',
+            "isDeprecatedLicenseId": False,
+            "isFsfLibre": False,
+            "isOsiApproved": False,
+            "licenseId": "unknown",
+            "name": "Unknown Licence",
+            "referenceNumber": -1,
+            "url": "",
         }
         if licence:
             # Parse licence string into object format
@@ -133,7 +133,9 @@ class AppConfig(django.apps.AppConfig):
             copyrights_processed.append(
                 (
                     # Sort copyright years and combine year ranges for display
-                    copyright[0] if isinstance(copyright[0], str) else copyright_years(copyright[0]),
+                    copyright[0]
+                    if isinstance(copyright[0], str)
+                    else copyright_years(copyright[0]),
                     copyright[1],
                     copyright[2],
                 )
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index c9c28deaf57fcdec72fb73470619ba40d5404002..aa47102931de63a881e2e76e26cc7d484bdff933 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -1,12 +1,10 @@
-from datetime import datetime, timedelta
-from itertools import groupby
-from operator import itemgetter
 import os
 import pkgutil
+from datetime import datetime, timedelta
 from importlib import import_module
-
+from itertools import groupby
+from operator import itemgetter
 from typing import Any, Callable, List, Optional, Sequence, Union
-
 from uuid import uuid4
 
 from django.conf import settings
@@ -15,6 +13,7 @@ from django.http import HttpRequest
 from django.utils import timezone
 from django.utils.functional import lazy
 
+
 def copyright_years(years: Sequence[int], seperator: str = ", ", joiner: str = "–") -> str:
     """ Takes a sequence of integegers and produces a string with ranges
 
@@ -22,11 +21,18 @@ def copyright_years(years: Sequence[int], seperator: str = ", ", joiner: str = "
     '1999–2001, 2005, 2007–2009'
     """
 
-    ranges = [list(map(itemgetter(1), group)) for _, group in groupby(enumerate(years), lambda e: e[1]-e[0])]
-    years_strs = [str(range_[0]) if len(range_) == 1 else joiner.join([str(range_[0]), str(range_[-1])]) for range_ in ranges]
+    ranges = [
+        list(map(itemgetter(1), group))
+        for _, group in groupby(enumerate(years), lambda e: e[1] - e[0])
+    ]
+    years_strs = [
+        str(range_[0]) if len(range_) == 1 else joiner.join([str(range_[0]), str(range_[-1])])
+        for range_ in ranges
+    ]
 
     return seperator.join(years_strs)
 
+
 def dt_show_toolbar(request: HttpRequest) -> bool:
     """ Helper to determin if Django debug toolbar should be displayed
 
@@ -59,7 +65,9 @@ def get_app_packages() -> Sequence[str]:
     return [f"aleksis.apps.{pkg[1]}" for pkg in pkgutil.iter_modules(aleksis.apps.__path__)]
 
 
-def merge_app_settings(setting: str, original: Union[dict, list], deduplicate: bool = False) -> Union[dict, list]:
+def merge_app_settings(
+    setting: str, original: Union[dict, list], deduplicate: bool = False
+) -> Union[dict, list]:
     """ Get a named settings constant from all apps and merge it into the original.
     To use this, add a settings.py file to the app, in the same format as Django's
     main settings.py.
@@ -97,6 +105,7 @@ def get_site_preferences():
     """ Get the preferences manager of the current site """
 
     from django.contrib.sites.models import Site  # noqa
+
     return Site.objects.get_current().preferences
 
 
@@ -114,7 +123,9 @@ def lazy_preference(section: str, name: str) -> Callable[[str, str], Any]:
     return lazy(_get_preference, str)(section, name)
 
 
-def lazy_get_favicon_url(title: str, size: int, rel: str, default: Optional[str] = None) -> Callable[[str, str], Any]:
+def lazy_get_favicon_url(
+    title: str, size: int, rel: str, default: Optional[str] = None
+) -> Callable[[str, str], Any]:
     """ Lazily get the URL to a favicon image """
 
     def _get_favicon_url(size: int, rel: str) -> Any:
@@ -170,6 +181,7 @@ def celery_optional(orig: Callable) -> Callable:
 
     if hasattr(settings, "CELERY_RESULT_BACKEND"):
         from ..celery import app  # noqa
+
         task = app.task(orig)
 
     def wrapped(*args, **kwargs):
@@ -200,6 +212,7 @@ def custom_information_processor(request: HttpRequest) -> dict:
     """ Provides custom information in all templates """
 
     from ..models import CustomMenu
+
     return {
         "FOOTER_MENU": CustomMenu.get_default("footer"),
     }
@@ -210,7 +223,9 @@ def now_tomorrow() -> datetime:
     return timezone.now() + timedelta(days=1)
 
 
-def objectgetter_optional(model: Model, default: Optional[Any] = None, default_eval: bool = False) -> Callable[[HttpRequest, Optional[int]], Model]:
+def objectgetter_optional(
+    model: Model, default: Optional[Any] = None, default_eval: bool = False
+) -> Callable[[HttpRequest, Optional[int]], Model]:
     """ Get an object by pk, defaulting to None """
 
     def get_object(request: HttpRequest, id_: Optional[int] = None) -> Model:
diff --git a/aleksis/core/util/middlewares.py b/aleksis/core/util/middlewares.py
index ce2915a28f846f77b578386e2f2aa851f4ab576a..79f15a1393dc93d1571e70d843d81ec6e31993d4 100644
--- a/aleksis/core/util/middlewares.py
+++ b/aleksis/core/util/middlewares.py
@@ -4,8 +4,8 @@ from django.core.exceptions import PermissionDenied
 from django.http import HttpRequest, HttpResponse
 from django.utils.translation import gettext_lazy as _
 
-from .core_helpers import has_person
 from ..models import DummyPerson
+from .core_helpers import has_person
 
 
 class EnsurePersonMiddleware:
@@ -23,7 +23,9 @@ class EnsurePersonMiddleware:
         if not has_person(request):
             if request.user.is_superuser:
                 # Super-users get a dummy person linked
-                dummy_person = DummyPerson(first_name=request.user.first_name, last_name=request.user.last_name)
+                dummy_person = DummyPerson(
+                    first_name=request.user.first_name, last_name=request.user.last_name
+                )
                 request.user.person = dummy_person
 
         response = self.get_response(request)
diff --git a/aleksis/core/util/notifications.py b/aleksis/core/util/notifications.py
index b9b146a2826ffde45f9924bfcd717ececaa39fcd..7ab63a64e93ab595b16d6c92b50940b8946d0c00 100644
--- a/aleksis/core/util/notifications.py
+++ b/aleksis/core/util/notifications.py
@@ -10,13 +10,13 @@ from django.utils.translation import gettext_lazy as _
 
 from templated_email import send_templated_mail
 
+from .core_helpers import celery_optional, lazy_preference
+
 try:
     from twilio.rest import Client as TwilioClient
 except ImportError:
     TwilioClient = None
 
-from .core_helpers import celery_optional, lazy_preference
-
 
 def send_templated_sms(
     template_name: str, from_number: str, recipient_list: Sequence[str], context: dict
diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py
index 1ff0a91f5dfd160da8c28311d8fc43b2f5e6c1e2..6c8deb1322b1b5407e04fe0de3a2f37322b182b9 100644
--- a/aleksis/core/util/predicates.py
+++ b/aleksis/core/util/predicates.py
@@ -2,13 +2,13 @@ from django.contrib.auth.backends import ModelBackend
 from django.contrib.auth.models import User
 from django.db.models import Model
 from django.http import HttpRequest
+
 from guardian.backends import ObjectPermissionBackend
 from guardian.shortcuts import get_objects_for_user
 from rules import predicate
 
-from .core_helpers import has_person as has_person_helper
-
 from ..models import Group
+from .core_helpers import has_person as has_person_helper
 
 
 def permission_validator(request: HttpRequest, perm: str) -> bool:
diff --git a/aleksis/core/util/sass_helpers.py b/aleksis/core/util/sass_helpers.py
index 2dcbf0aa0314354c91b417b40a6e5606ab841197..d624944c9b3a30fab843682f897c100e4dd4edc7 100644
--- a/aleksis/core/util/sass_helpers.py
+++ b/aleksis/core/util/sass_helpers.py
@@ -10,6 +10,7 @@ from sass import SassColor
 
 from .core_helpers import get_site_preferences
 
+
 def get_colour(html_colour: str) -> SassColor:
     """ Get a SASS colour object from an HTML colour string """
 
diff --git a/aleksis/core/util/search.py b/aleksis/core/util/search.py
index 6720fb6b4236f10d3bfb1932969483d4d4db6d8f..dd8fe793162c9fdde33cdb0803754e47e415dc35 100644
--- a/aleksis/core/util/search.py
+++ b/aleksis/core/util/search.py
@@ -5,11 +5,12 @@ from haystack import indexes
 # Not used here, but simplifies imports for apps
 Indexable = indexes.Indexable  # noqa
 
-if settings.HAYSTACK_SIGNAL_PROCESSOR == 'celery_haystack.signals.CelerySignalProcessor':
+if settings.HAYSTACK_SIGNAL_PROCESSOR == "celery_haystack.signals.CelerySignalProcessor":
     from haystack.indexes import SearchIndex as BaseSearchIndex
 else:
     from celery_haystack.indexes import CelerySearchIndex as BaseSearchIndex
 
+
 class SearchIndex(BaseSearchIndex):
     """ Base class for search indexes on AlekSIS models
 
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index bc82720fbb7c157113c7e8a53f7638a0b6e364c7..fab71fd0c306f0a471c4f6a31e54517648c7b016 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -19,17 +19,21 @@ from rules.contrib.views import permission_required
 
 from .filters import GroupFilter
 from .forms import (
+    AnnouncementForm,
+    ChildGroupsForm,
     EditGroupForm,
     EditPersonForm,
+    GroupPreferenceForm,
+    PersonPreferenceForm,
     PersonsAccountsFormSet,
-    AnnouncementForm,
-    ChildGroupsForm,
     SitePreferenceForm,
-    PersonPreferenceForm,
-    GroupPreferenceForm,
 )
-from .models import Activity, Group, Notification, Person, DashboardWidget, Announcement
-from .registries import site_preferences_registry, group_preferences_registry, person_preferences_registry
+from .models import Activity, Announcement, DashboardWidget, Group, Notification, Person
+from .registries import (
+    group_preferences_registry,
+    person_preferences_registry,
+    site_preferences_registry,
+)
 from .tables import GroupsTable, PersonsTable
 from .util import messages
 from .util.apps import AppConfig
@@ -73,7 +77,9 @@ def about(request: HttpRequest) -> HttpResponse:
 
     context = {}
 
-    context["app_configs"] = list(filter(lambda a: isinstance(a, AppConfig), apps.get_app_configs()))
+    context["app_configs"] = list(
+        filter(lambda a: isinstance(a, AppConfig), apps.get_app_configs())
+    )
 
     return render(request, "core/about.html", context)
 
@@ -97,7 +103,9 @@ def persons(request: HttpRequest) -> HttpResponse:
     return render(request, "core/persons.html", context)
 
 
-@permission_required("core.view_person", fn=objectgetter_optional(Person, "request.user.person", True))
+@permission_required(
+    "core.view_person", fn=objectgetter_optional(Person, "request.user.person", True)
+)
 def person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     """ Detail view for one person; defaulting to logged-in person """
 
@@ -224,7 +232,9 @@ def groups_child_groups(request: HttpRequest) -> HttpResponse:
     return render(request, "core/groups_child_groups.html", context)
 
 
-@permission_required("core.edit_person", fn=objectgetter_optional(Person, "request.user.person", True))
+@permission_required(
+    "core.edit_person", fn=objectgetter_optional(Person, "request.user.person", True)
+)
 def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     """ Edit view for a single person, defaulting to logged-in person """
 
@@ -301,7 +311,9 @@ def system_status(request: HttpRequest) -> HttpResponse:
     return render(request, "core/system_status.html", context)
 
 
-@permission_required("core.mark_notification_as_read", fn=objectgetter_optional(Notification, None, False))
+@permission_required(
+    "core.mark_notification_as_read", fn=objectgetter_optional(Notification, None, False)
+)
 def notification_mark_read(request: HttpRequest, id_: int) -> HttpResponse:
     """ Mark a notification read """
 
@@ -329,7 +341,9 @@ def announcements(request: HttpRequest) -> HttpResponse:
     return render(request, "core/announcement/list.html", context)
 
 
-@permission_required("core.create_or_edit_announcement", fn=objectgetter_optional(Announcement, None, False))
+@permission_required(
+    "core.create_or_edit_announcement", fn=objectgetter_optional(Announcement, None, False)
+)
 def announcement_form(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     """ View to create or edit an announcement """
 
@@ -339,10 +353,7 @@ def announcement_form(request: HttpRequest, id_: Optional[int] = None) -> HttpRe
 
     if announcement:
         # Edit form for existing announcement
-        form = AnnouncementForm(
-            request.POST or None,
-            instance=announcement
-        )
+        form = AnnouncementForm(request.POST or None, instance=announcement)
         context["mode"] = "edit"
     else:
         # Empty form to create new announcement
@@ -361,7 +372,9 @@ def announcement_form(request: HttpRequest, id_: Optional[int] = None) -> HttpRe
     return render(request, "core/announcement/form.html", context)
 
 
-@permission_required("core.delete_announcement", fn=objectgetter_optional(Announcement, None, False))
+@permission_required(
+    "core.delete_announcement", fn=objectgetter_optional(Announcement, None, False)
+)
 def delete_announcement(request: HttpRequest, id_: int) -> HttpResponse:
     """ View to delete an announcement """
 
@@ -377,8 +390,8 @@ def delete_announcement(request: HttpRequest, id_: int) -> HttpResponse:
 def searchbar_snippets(request: HttpRequest) -> HttpResponse:
     """ View to return HTML snippet with searchbar autocompletion results """
 
-    query = request.GET.get('q', '')
-    limit = int(request.GET.get('limit', '5'))
+    query = request.GET.get("q", "")
+    limit = int(request.GET.get("limit", "5"))
 
     results = SearchQuerySet().filter(text=AutoQuery(query))[:limit]
     context = {"results": results}
@@ -398,7 +411,12 @@ class PermissionSearchView(PermissionRequiredMixin, SearchView):
         return render(self.request, self.template, context)
 
 
-def preferences(request: HttpRequest, registry_name: str = "person", pk: Optional[int] = None, section: Optional[str] = None) -> HttpResponse:
+def preferences(
+    request: HttpRequest,
+    registry_name: str = "person",
+    pk: Optional[int] = None,
+    section: Optional[str] = None,
+) -> HttpResponse:
     """ View for changing preferences """
 
     context = {}