diff --git a/aleksis/core/checks.py b/aleksis/core/checks.py
index ebd6f8844c0e234af8685d70592485e8bd95efa4..9518b5e26bfe2f8d2cc8eff7494eeb6f4495035d 100644
--- a/aleksis/core/checks.py
+++ b/aleksis/core/checks.py
@@ -11,7 +11,7 @@ from .util.apps import AppConfig
 def check_app_configs_base_class(
     app_configs: Optional[django.apps.registry.Apps] = None, **kwargs
 ) -> list:
-    """Checks whether all apps derive from AlekSIS's base app config"""
+    """Checks whether all apps derive from AlekSIS's base app config."""
     results = []
 
     if app_configs is None:
@@ -39,7 +39,7 @@ def check_app_configs_base_class(
 def check_app_models_base_class(
     app_configs: Optional[django.apps.registry.Apps] = None, **kwargs
 ) -> list:
-    """Checks whether all app models derive from AlekSIS's base ExtensibleModel"""
+    """Checks whether all app models derive from AlekSIS's base ExtensibleModel."""
     results = []
 
     if app_configs is None:
diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py
index a359415a8ee58350bca0537a101b31ad1050e0ed..2fcafc68ba35e6e9d650bc9d2d6e9b73e223fdb2 100644
--- a/aleksis/core/forms.py
+++ b/aleksis/core/forms.py
@@ -19,7 +19,7 @@ from .registries import (
 
 
 class PersonAccountForm(forms.ModelForm):
-    """Form to assign user accounts to persons in the frontend :"""
+    """Form to assign user accounts to persons in the frontend."""
 
     class Meta:
         model = Person
@@ -67,7 +67,7 @@ PersonsAccountsFormSet = forms.modelformset_factory(
 
 
 class EditPersonForm(ExtensibleForm):
-    """Form to edit an existing person object in the frontend"""
+    """Form to edit an existing person object in the frontend."""
 
     layout = Layout(
         Fieldset(
@@ -122,7 +122,7 @@ class EditPersonForm(ExtensibleForm):
 
 
 class EditGroupForm(ExtensibleForm):
-    """Form to edit an existing group in the frontend"""
+    """Form to edit an existing group in the frontend."""
 
     layout = Layout(
         Fieldset(_("Common data"), "name", "short_name"),
@@ -154,7 +154,7 @@ class EditGroupForm(ExtensibleForm):
 
 
 class AnnouncementForm(ExtensibleForm):
-    """Form to create or edit an announcement in the frontend"""
+    """Form to create or edit an announcement in the frontend."""
 
     valid_from = forms.DateTimeField(required=False)
     valid_until = forms.DateTimeField(required=False)
@@ -259,24 +259,24 @@ class AnnouncementForm(ExtensibleForm):
 
 
 class ChildGroupsForm(forms.Form):
-    """Inline form for group editing to select child groups"""
+    """Inline form for group editing to select child groups."""
 
     child_groups = forms.ModelMultipleChoiceField(queryset=Group.objects.all())
 
 
 class SitePreferenceForm(PreferenceForm):
-    """Form to edit site preferences"""
+    """Form to edit site preferences."""
 
     registry = site_preferences_registry
 
 
 class PersonPreferenceForm(PreferenceForm):
-    """Form to edit preferences valid for one person"""
+    """Form to edit preferences valid for one person."""
 
     registry = person_preferences_registry
 
 
 class GroupPreferenceForm(PreferenceForm):
-    """Form to edit preferences valid for members of a group"""
+    """Form to edit preferences valid for members of a group."""
 
     registry = group_preferences_registry
diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py
index c391b5d084e3dbd61d8fab278312690b20c300c7..6da2f056475c418019fd5b312fed9e37dda6d401 100644
--- a/aleksis/core/mixins.py
+++ b/aleksis/core/mixins.py
@@ -18,7 +18,7 @@ from rules.contrib.admin import ObjectPermissionsModelAdmin
 
 @reversion.register()
 class ExtensibleModel():
-    """Base model for all objects in AlekSIS apps
+    """Base model for all objects in AlekSIS apps.
 
     This base model ensures all objects in AlekSIS apps fulfill the
     following properties:
@@ -71,12 +71,12 @@ class ExtensibleModel():
     objects_all_sites = models.Manager()
 
     def get_absolute_url(self) -> str:
-        """Get the URL o a view representing this model instance"""
+        """Get the URL o a view representing this model instance."""
         pass
 
     @property
     def crud_events(self) -> QuerySet:
-        """Get all CRUD events connected to this object from easyaudit"""
+        """Get all CRUD events connected to this object from easyaudit."""
         content_type = ContentType.objects.get_for_model(self)
 
         return CRUDEvent.objects.filter(
@@ -85,23 +85,23 @@ class ExtensibleModel():
 
     @property
     def crud_event_create(self) -> Optional[CRUDEvent]:
-        """Return create event of this object"""
+        """Return create event of this object."""
         return self.crud_events.filter(event_type=CRUDEvent.CREATE).latest("datetime")
 
     @property
     def crud_event_update(self) -> Optional[CRUDEvent]:
-        """Return last event of this object"""
+        """Return last event of this object."""
         return self.crud_events.latest("datetime")
 
     @property
     def created_at(self) -> Optional[datetime]:
-        """Determine creation timestamp from CRUD log"""
+        """Determine creation timestamp from CRUD log."""
         if self.crud_event_create:
             return self.crud_event_create.datetime
 
     @property
     def updated_at(self) -> Optional[datetime]:
-        """Determine last timestamp from CRUD log"""
+        """Determine last timestamp from CRUD log."""
         if self.crud_event_update:
             return self.crud_event_update.datetime
 
@@ -109,13 +109,13 @@ class ExtensibleModel():
 
     @property
     def created_by(self) -> Optional[models.Model]:
-        """Determine user who created this object from CRUD log"""
+        """Determine user who created this object from CRUD log."""
         if self.crud_event_create:
             return self.crud_event_create.user
 
     @property
     def updated_by(self) -> Optional[models.Model]:
-        """Determine user who last updated this object from CRUD log"""
+        """Determine user who last updated this object from CRUD log."""
         if self.crud_event_update:
             return self.crud_event_update.user
 
@@ -156,8 +156,7 @@ class ExtensibleModel():
 
     @classmethod
     def field(cls, **kwargs) -> None:
-        """Adds the passed jsonstore field. Must be one of the fields in
-        django-jsonstore.
+        """Adds the passed jsonstore field. Must be one of the fields in django-jsonstore.
 
         Accepts exactly one keyword argument, with the name being the desired
         model field name and the value the field instance.
@@ -181,7 +180,7 @@ class ExtensibleModel():
 
 
 class PureDjangoModel(object):
-    """No-op mixin to mark a model as deliberately not using ExtensibleModel"""
+    """No-op mixin to mark a model as deliberately not using ExtensibleModel."""
 
     pass
 
@@ -203,7 +202,7 @@ class _ExtensibleFormMetaclass(ModelFormMetaclass):
 
 
 class ExtensibleForm(ModelForm, metaclass=_ExtensibleFormMetaclass):
-    """Base model for extensible forms
+    """Base model for extensible forms.
 
     This mixin adds functionality which allows
     - apps to add layout nodes to the layout used by django-material
@@ -224,8 +223,7 @@ class ExtensibleForm(ModelForm, metaclass=_ExtensibleFormMetaclass):
 
     @classmethod
     def add_node_to_layout(cls, node: Union[LayoutNode, str]):
-        """
-        Add a node to `layout` attribute
+        """Add a node to `layout` attribute.
 
         :param node: django-material layout node (Fieldset, Row etc.)
         :type node: LayoutNode
@@ -235,6 +233,6 @@ class ExtensibleForm(ModelForm, metaclass=_ExtensibleFormMetaclass):
 
 
 class BaseModelAdmin(GuardedModelAdmin, ObjectPermissionsModelAdmin):
-    """A base class for ModelAdmin combining django-guardian and rules"""
+    """A base class for ModelAdmin combining django-guardian and rules."""
 
     pass
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index ea55dfe99dc4e504f8c1274f9155f32ad789e854..d924a7fdaabdcbb9e4c2aa728a390ce0e71cc54d 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -42,7 +42,8 @@ FIELD_CHOICES = (
 
 
 class Person(ExtensibleModel):
-    """A model describing any person related to a school, including, but not
+    """
+    A model describing any person related to a school, including, but not
     limited to, students, teachers and guardians (parents).
     """
 
@@ -117,16 +118,16 @@ class Person(ExtensibleModel):
 
     @property
     def primary_group_short_name(self) -> Optional[str]:
-        """Returns the short_name field of the primary
-        group related object.
-        """
+        """Returns the short_name field of the primary group related object."""
         if self.primary_group:
             return self.primary_group.short_name
 
     @primary_group_short_name.setter
     def primary_group_short_name(self, value: str) -> None:
-        """Sets the primary group related object by
-        a short name. It uses the first existing group
+        """
+        Sets the primary group related object by a short name.
+
+        It uses the first existing group
         with this short name it can find, creating one
         if it can't find one.
         """
@@ -135,12 +136,12 @@ class Person(ExtensibleModel):
 
     @property
     def full_name(self) -> str:
-        """Full name of person in last name, first name order"""
+        """Full name of person in last name, first name order."""
         return f"{self.last_name}, {self.first_name}"
 
     @property
     def adressing_name(self) -> str:
-        """Full name of person in format configured for addressing"""
+        """Full name of person in format configured for addressing."""
         if get_site_preferences()["notification__addressing_name_format"] == "last_first":
             return f"{self.last_name}, {self.first_name}"
         elif get_site_preferences()["notification__addressing_name_format"] == "first_last":
@@ -148,11 +149,11 @@ class Person(ExtensibleModel):
 
     @property
     def age(self):
-        """Age of the person at current time"""
+        """Age of the person at current time."""
         return self.age_at(timezone.datetime.now().date())
 
     def age_at(self, today):
-        """Age of the person at a given date and time"""
+        """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
@@ -191,7 +192,7 @@ class Person(ExtensibleModel):
             admin.save()
 
     def auto_select_primary_group(self, pattern: Optional[str] = None, force: bool = False) -> None:
-        """Auto-select the primary group among the groups the person is member of
+        """Auto-select the primary group among the groups the person is member of.
 
         Uses either the pattern passed as argument, or the pattern configured system-wide.
 
@@ -223,7 +224,7 @@ class DummyPerson(Person):
 
 
 class AdditionalField(ExtensibleModel):
-    """An additional field that can be linked to a group"""
+    """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(
@@ -236,7 +237,8 @@ class AdditionalField(ExtensibleModel):
 
 
 class Group(ExtensibleModel):
-    """Any kind of group of persons in a school, including, but not limited
+    """
+    Any kind of group of persons in a school, including, but not limited
     classes, clubs, and the like.
     """
 
@@ -287,7 +289,7 @@ class Group(ExtensibleModel):
 
     @property
     def announcement_recipients(self):
-        """Flat list of all members and owners to fulfill announcement API contract"""
+        """Flat list of all members and owners to fulfill announcement API contract."""
         return list(self.members.all()) + list(self.owners.all())
 
     def __str__(self) -> str:
@@ -329,7 +331,7 @@ class PersonGroupThrough(ExtensibleModel):
 
 
 class Activity(ExtensibleModel):
-    """Activity of a user to trace some actions done in AlekSIS in displayable form"""
+    """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")
@@ -349,7 +351,7 @@ class Activity(ExtensibleModel):
 
 
 class Notification(ExtensibleModel):
-    """Notification to submit to a user"""
+    """Notification to submit to a user."""
 
     sender = models.CharField(max_length=100, verbose_name=_("Sender"))
     recipient = models.ForeignKey(
@@ -382,10 +384,11 @@ class Notification(ExtensibleModel):
 
 
 class AnnouncementQuerySet(models.QuerySet):
-    """Queryset for announcements providing time-based utility functions"""
+    """Queryset for announcements providing time-based utility functions."""
 
     def relevant_for(self, obj: Union[models.Model, models.QuerySet]) -> models.QuerySet:
-        """Get a QuerySet with all announcements relevant for a certain Model (e.g. a Group)
+        """
+        Get a QuerySet with all announcements relevant for a certain Model (e.g. a Group)
         or a set of models in a QuerySet.
         """
         if isinstance(obj, models.QuerySet):
@@ -398,7 +401,7 @@ class AnnouncementQuerySet(models.QuerySet):
         return self.filter(recipients__content_type=ct, recipients__recipient_id__in=pks)
 
     def at_time(self, when: Optional[datetime] = None) -> models.QuerySet:
-        """Get all announcements at a certain time"""
+        """Get all announcements at a certain time."""
         when = when or timezone.datetime.now()
 
         # Get announcements by time
@@ -407,7 +410,7 @@ class AnnouncementQuerySet(models.QuerySet):
         return announcements
 
     def on_date(self, when: Optional[date] = None) -> models.QuerySet:
-        """Get all announcements at a certain date"""
+        """Get all announcements at a certain date."""
         when = when or timezone.datetime.now().date()
 
         # Get announcements by time
@@ -416,14 +419,14 @@ class AnnouncementQuerySet(models.QuerySet):
         return announcements
 
     def within_days(self, start: date, stop: date) -> models.QuerySet:
-        """Get all announcements valid for a set of days"""
+        """Get all announcements valid for a set of days."""
         # Get announcements
         announcements = self.filter(valid_from__date__lte=stop, valid_until__date__gte=start)
 
         return announcements
 
     def for_person(self, person: Person) -> List:
-        """Get all announcements for one person"""
+        """Get all announcements for one person."""
         # Filter by person
         announcements_for_person = []
         for announcement in self:
@@ -434,7 +437,8 @@ class AnnouncementQuerySet(models.QuerySet):
 
 
 class Announcement(ExtensibleModel):
-    """Persistent announcement to display to groups or persons in various places during a
+    """
+    Persistent announcement to display to groups or persons in various places during a
     specific time range.
     """
 
@@ -453,14 +457,15 @@ class Announcement(ExtensibleModel):
 
     @property
     def recipient_persons(self) -> Sequence[Person]:
-        """Return a list of Persons this announcement is relevant for"""
+        """Return a list of Persons this announcement is relevant for."""
         persons = []
         for recipient in self.recipients.all():
             persons += recipient.persons
         return persons
 
     def get_recipients_for_model(self, obj: Union[models.Model]) -> Sequence[models.Model]:
-        """Get all recipients for this announcement
+        """
+        Get all recipients for this announcement
         with a special content type (provided through model)
         """
         ct = ContentType.objects.get_for_model(obj)
@@ -475,7 +480,8 @@ class Announcement(ExtensibleModel):
 
 
 class AnnouncementRecipient(ExtensibleModel):
-    """Generalisation of a recipient for an announcement, used to wrap arbitrary
+    """
+    Generalisation of a recipient for an announcement, used to wrap arbitrary
     objects that can receive announcements.
 
     Contract: Objects to serve as recipient have a property announcement_recipients
@@ -492,7 +498,7 @@ class AnnouncementRecipient(ExtensibleModel):
 
     @property
     def persons(self) -> Sequence[Person]:
-        """Return a list of Persons selected by this recipient object
+        """Return a list of Persons selected by this recipient object.
 
         If the recipient is a Person, return that object. If not, it returns the list
         from the announcement_recipients field on the target model.
@@ -511,7 +517,7 @@ class AnnouncementRecipient(ExtensibleModel):
 
 
 class DashboardWidget(PolymorphicModel, PureDjangoModel):
-    """Base class for dashboard widgets on the index page
+    """Base class for dashboard widgets on the index page.
 
     To implement a widget, add a model that subclasses DashboardWidget, sets the template
     and implements the get_context method to return a dictionary to be passed as context
@@ -561,11 +567,12 @@ class DashboardWidget(PolymorphicModel, PureDjangoModel):
     active = models.BooleanField(blank=True, verbose_name=_("Activate Widget"))
 
     def get_context(self):
-        """Get the context dictionary to pass to the widget template"""
+        """Get the context dictionary to pass to the widget template."""
         raise NotImplementedError("A widget subclass needs to implement the get_context method.")
 
     def get_template(self):
-        """Get the template to render the widget with. Defaults to the template attribute,
+        """
+        Get the template to render the widget with. Defaults to the template attribute,
         but can be overridden to allow more complex template generation scenarios.
         """
         return self.template
@@ -579,7 +586,7 @@ class DashboardWidget(PolymorphicModel, PureDjangoModel):
 
 
 class CustomMenu(ExtensibleModel):
-    """A custom menu to display in the footer"""
+    """A custom menu to display in the footer."""
 
     name = models.CharField(max_length=100, verbose_name=_("Menu ID"), unique=True)
 
@@ -588,7 +595,7 @@ class CustomMenu(ExtensibleModel):
 
     @classmethod
     def get_default(cls, name):
-        """Get a menu by name or create if it does not exist"""
+        """Get a menu by name or create if it does not exist."""
         menu, _ = cls.objects.get_or_create(name=name)
         return menu
 
@@ -598,7 +605,7 @@ class CustomMenu(ExtensibleModel):
 
 
 class CustomMenuItem(ExtensibleModel):
-    """Single item in a custom menu"""
+    """Single item in a custom menu."""
 
     menu = models.ForeignKey(
         CustomMenu, models.CASCADE, verbose_name=_("Menu"), related_name="items"
@@ -618,7 +625,8 @@ class CustomMenuItem(ExtensibleModel):
 
 
 class GroupType(ExtensibleModel):
-    """Descriptive type of a group; used to tag groups and for apps to distinguish
+    """
+    Descriptive type of a group; used to tag groups and for apps to distinguish
     how to display or handle a certain group.
     """
 
@@ -631,7 +639,7 @@ class GroupType(ExtensibleModel):
 
 
 class GlobalPermissions(ExtensibleModel):
-    """Container for global permissions"""
+    """Container for global permissions."""
 
     class Meta:
         managed = False
@@ -648,7 +656,7 @@ class GlobalPermissions(ExtensibleModel):
 
 
 class SitePreferenceModel(PerInstancePreferenceModel, PureDjangoModel):
-    """Preference model to hold pereferences valid for a site"""
+    """Preference model to hold pereferences valid for a site."""
 
     instance = models.ForeignKey(Site, on_delete=models.CASCADE)
 
@@ -657,7 +665,7 @@ class SitePreferenceModel(PerInstancePreferenceModel, PureDjangoModel):
 
 
 class PersonPreferenceModel(PerInstancePreferenceModel, PureDjangoModel):
-    """Preference model to hold pereferences valid for a person"""
+    """Preference model to hold pereferences valid for a person."""
 
     instance = models.ForeignKey(Person, on_delete=models.CASCADE)
 
@@ -666,7 +674,7 @@ class PersonPreferenceModel(PerInstancePreferenceModel, PureDjangoModel):
 
 
 class GroupPreferenceModel(PerInstancePreferenceModel, PureDjangoModel):
-    """Preference model to hold pereferences valid for members of a group"""
+    """Preference model to hold pereferences valid for members of a group."""
 
     instance = models.ForeignKey(Group, on_delete=models.CASCADE)
 
diff --git a/aleksis/core/registries.py b/aleksis/core/registries.py
index 7a0221582b63aa0433b9c6a288ab3d6463fdb502..bccc9e57590d113641ac14534a1bfaabbcc5e1e0 100644
--- a/aleksis/core/registries.py
+++ b/aleksis/core/registries.py
@@ -1,22 +1,22 @@
-"""Custom registries for some preference containers"""
+"""Custom registries for some preference containers."""
 
 from dynamic_preferences.registries import PerInstancePreferenceRegistry
 
 
 class SitePreferenceRegistry(PerInstancePreferenceRegistry):
-    """Registry for preferences valid for a site"""
+    """Registry for preferences valid for a site."""
 
     pass
 
 
 class PersonPreferenceRegistry(PerInstancePreferenceRegistry):
-    """Registry for preferences valid for a person"""
+    """Registry for preferences valid for a person."""
 
     pass
 
 
 class GroupPreferenceRegistry(PerInstancePreferenceRegistry):
-    """Registry for preferences valid for members of a group"""
+    """Registry for preferences valid for members of a group."""
 
     pass
 
diff --git a/aleksis/core/search_indexes.py b/aleksis/core/search_indexes.py
index 397bce3aab0de5ad3a3a20e68774e8eb74ed29c5..7583a774eaadebb1cbfdb35d08dc0ddcfb355eea 100644
--- a/aleksis/core/search_indexes.py
+++ b/aleksis/core/search_indexes.py
@@ -3,12 +3,12 @@ from .util.search import Indexable, SearchIndex
 
 
 class PersonIndex(SearchIndex, Indexable):
-    """Haystack index for searching persons"""
+    """Haystack index for searching persons."""
 
     model = Person
 
 
 class GroupIndex(SearchIndex, Indexable):
-    """Haystack index for searching groups"""
+    """Haystack index for searching groups."""
 
     model = Group
diff --git a/aleksis/core/tasks.py b/aleksis/core/tasks.py
index 09031dff5cee1e8417fcaf94b4d361af08b32ab5..f391fe05bd6dfd82eaf3e84ddb222bf322881b5f 100644
--- a/aleksis/core/tasks.py
+++ b/aleksis/core/tasks.py
@@ -17,7 +17,7 @@ def send_notification(notification: int, resend: bool = False) -> None:
 
 @celery_optional
 def backup_data() -> None:
-    """Backup database and media using django-dbbackup"""
+    """Backup database and media using django-dbbackup."""
     # Assemble command-line options for dbbackup management command
     db_options = "-z " * settings.DBBACKUP_COMPRESS_DB + "-e" * settings.DBBACKUP_ENCRYPT_DB
     media_options = (
diff --git a/aleksis/core/templatetags/dashboard.py b/aleksis/core/templatetags/dashboard.py
index 79cf15f23c851e13576546b8c87e6f0fd29562b2..b2c1541e8fedf08162d795f0224dce9bc7bfbbd6 100644
--- a/aleksis/core/templatetags/dashboard.py
+++ b/aleksis/core/templatetags/dashboard.py
@@ -5,7 +5,7 @@ register = Library()
 
 @register.simple_tag
 def include_widget(widget) -> dict:
-    """Render a template with context from a defined widget"""
+    """Render a template with context from a defined widget."""
     template = loader.get_template(widget.get_template())
     context = widget.get_context()
 
diff --git a/aleksis/core/templatetags/data_helpers.py b/aleksis/core/templatetags/data_helpers.py
index 5bed2efb95843fcca122d6c507ec67e5b55c848a..19f286434db34eb9e74b88950b8d87ee854959ef 100644
--- a/aleksis/core/templatetags/data_helpers.py
+++ b/aleksis/core/templatetags/data_helpers.py
@@ -7,7 +7,7 @@ register = template.Library()
 
 @register.filter
 def get_dict(value: Any, arg: Any) -> Any:
-    """Gets an attribute of an object dynamically from a string name"""
+    """Get an attribute of an object dynamically from a string name."""
     if hasattr(value, str(arg)):
         return getattr(value, arg)
     elif hasattr(value, "keys") and arg in value.keys():
diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py
index 33af811d655e7eb35399f12dfb6ce27efc2ac6d6..d7cbb67f104f11ed8c5179266528fe2d4345a403 100644
--- a/aleksis/core/util/apps.py
+++ b/aleksis/core/util/apps.py
@@ -50,13 +50,13 @@ class AppConfig(django.apps.AppConfig):
 
     @classmethod
     def get_name(cls):
-        """Get name of application package"""
+        """Get name of application package."""
         return getattr(cls, "verbose_name", cls.name)
         # TODO Try getting from distribution if not set
 
     @classmethod
     def get_version(cls):
-        """Get version of application package"""
+        """Get version of application package."""
         try:
             from .. import __version__  # noqa
         except ImportError:
@@ -66,7 +66,7 @@ class AppConfig(django.apps.AppConfig):
 
     @classmethod
     def get_licence(cls) -> Tuple:
-        """Get tuple of licence information of application package"""
+        """Get tuple of licence information of application package."""
         # Get string representation of licence in SPDX format
         licence = getattr(cls, "licence", None)
 
@@ -118,13 +118,13 @@ class AppConfig(django.apps.AppConfig):
 
     @classmethod
     def get_urls(cls):
-        """Get list of URLs for this application package"""
+        """Get list of URLs for this application package."""
         return getattr(cls, "urls", {})
         # TODO Try getting from distribution if not set
 
     @classmethod
     def get_copyright(cls) -> Sequence[Tuple[str, str, str]]:
-        """Get copyright information tuples for application package"""
+        """Get copyright information tuples for application package."""
         copyrights = getattr(cls, "copyright", tuple())
 
         copyrights_processed = []
@@ -152,7 +152,7 @@ class AppConfig(django.apps.AppConfig):
         new_value: Optional[Any] = None,
         **kwargs,
     ) -> None:
-        """Called on every app instance if a dynamic preference changes, and once on startup
+        """Called on every app instance if a dynamic preference changes, and once on startup.
 
         By default, it does nothing.
         """
@@ -168,7 +168,7 @@ class AppConfig(django.apps.AppConfig):
         apps: django.apps.registry.Apps,
         **kwargs,
     ) -> None:
-        """Called on every app instance before its models are migrated
+        """Called on every app instance before its models are migrated.
 
         By default, it does nothing.
         """
@@ -184,7 +184,7 @@ class AppConfig(django.apps.AppConfig):
         apps: django.apps.registry.Apps,
         **kwargs,
     ) -> None:
-        """Called on every app instance after its models have been migrated
+        """Called on every app instance after its models have been migrated.
 
         By default, asks all models to do maintenance on their default data.
         """
@@ -193,7 +193,7 @@ class AppConfig(django.apps.AppConfig):
     def user_logged_in(
         self, sender: type, request: Optional[HttpRequest], user: "User", **kwargs
     ) -> None:
-        """Called after a user logged in
+        """Called after a user logged in.
 
         By default, it does nothing.
         """
@@ -202,7 +202,7 @@ class AppConfig(django.apps.AppConfig):
     def user_logged_out(
         self, sender: type, request: Optional[HttpRequest], user: "User", **kwargs
     ) -> None:
-        """Called after a user logged out
+        """Called after a user logged out.
 
         By default, it does nothing.
         """
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index 0c44061371fcde86e584f612d9ca5c76b97bba67..b24f48c8cbf598ae52377348e93bcd882d3efd4a 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -10,14 +10,13 @@ from uuid import uuid4
 from django.conf import settings
 from django.db.models import Model
 from django.http import HttpRequest
-from django.shortcuts import get_object_or_404
 from django.utils import timezone
 from django.utils.functional import lazy
 from django.shortcuts import get_object_or_404
 
 
 def copyright_years(years: Sequence[int], seperator: str = ", ", joiner: str = "–") -> str:
-    """Takes a sequence of integegers and produces a string with ranges
+    """Takes a sequence of integegers and produces a string with ranges.
 
     >>> copyright_years([1999, 2000, 2001, 2005, 2007, 2008, 2009])
     '1999–2001, 2005, 2007–2009'
@@ -35,7 +34,7 @@ def copyright_years(years: Sequence[int], seperator: str = ", ", joiner: str = "
 
 
 def dt_show_toolbar(request: HttpRequest) -> bool:
-    """Helper to determin if Django debug toolbar should be displayed
+    """Helper to determin if Django debug toolbar should be displayed.
 
     Extends the default behaviour by enabling DJDT for superusers independent
     of source IP.
@@ -67,7 +66,8 @@ def get_app_packages() -> Sequence[str]:
 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.
+    """
+    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.
 
@@ -100,14 +100,16 @@ def merge_app_settings(
 
 
 def get_site_preferences():
-    """Get the preferences manager of the current site"""
+    """Get the preferences manager of the current site."""
     from django.contrib.sites.models import Site  # noqa
 
     return Site.objects.get_current().preferences
 
 
 def lazy_preference(section: str, name: str) -> Callable[[str, str], Any]:
-    """Lazily get a config value from dynamic preferences. Useful to bind preferences
+    """Lazily get a config value from dynamic preferences.
+
+    Useful to bind preferences
     to other global settings to make them available to third-party apps that are not
     aware of dynamic preferences.
     """
@@ -122,7 +124,7 @@ def lazy_preference(section: str, name: str) -> 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"""
+    """Lazily get the URL to a favicon image."""
     def _get_favicon_url(size: int, rel: str) -> Any:
         from favicon.models import Favicon  # noqa
 
@@ -137,7 +139,7 @@ def lazy_get_favicon_url(
 
 
 def is_impersonate(request: HttpRequest) -> bool:
-    """Check whether the user was impersonated by an admin"""
+    """Check whether the user was impersonated by an admin."""
     if hasattr(request, "user"):
         return getattr(request.user, "is_impersonate", False)
     else:
@@ -145,8 +147,9 @@ def is_impersonate(request: HttpRequest) -> bool:
 
 
 def has_person(obj: Union[HttpRequest, Model]) -> bool:
-    """Check wehether a model object has a person attribute linking it to a Person
-    object. The passed object can also be a HttpRequest object, in which case its
+    """Check wehether a model object has a person attribute linking it to a Person object.
+
+    The passed object can also be a HttpRequest object, in which case its
     associated User object is unwrapped and tested.
     """
     if isinstance(obj, HttpRequest):
@@ -186,7 +189,7 @@ def celery_optional(orig: Callable) -> Callable:
 
 
 def path_and_rename(instance, filename: str, upload_to: str = "files") -> str:
-    """Updates path of an uploaded file and renames it to a random UUID in Django FileField"""
+    """Updates path of an uploaded file and renames it to a random UUID in Django FileField."""
     _, ext = os.path.splitext(filename)
 
     # set filename as random string
@@ -200,7 +203,7 @@ def path_and_rename(instance, filename: str, upload_to: str = "files") -> str:
 
 
 def custom_information_processor(request: HttpRequest) -> dict:
-    """Provides custom information in all templates"""
+    """Provides custom information in all templates."""
     from ..models import CustomMenu
 
     return {
@@ -209,14 +212,14 @@ def custom_information_processor(request: HttpRequest) -> dict:
 
 
 def now_tomorrow() -> datetime:
-    """Return current time tomorrow"""
+    """Return current time tomorrow."""
     return timezone.now() + timedelta(days=1)
 
 
 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"""
+    """Get an object by pk, defaulting to None."""
     def get_object(request: HttpRequest, id_: Optional[int] = None) -> Model:
         if id_ is not None:
             return get_object_or_404(model, pk=id_)
diff --git a/aleksis/core/util/messages.py b/aleksis/core/util/messages.py
index 57a137507b48820fafd4199eb3deb18d8c1861c6..bd125d1cb46b54d890dd80850627d06406f153f2 100644
--- a/aleksis/core/util/messages.py
+++ b/aleksis/core/util/messages.py
@@ -8,11 +8,11 @@ from django.http import HttpRequest
 def add_message(
     request: Optional[HttpRequest], level: int, message: str, **kwargs
 ) -> Optional[Any]:
-   """ Add a message to either Django's message framework, if called from a web request,
+    """ Add a message to either Django's message framework, if called from a web request,
     or to the default logger.
 
     Default to DEBUG level.
-   """
+    """
     if request:
         return messages.add_message(request, level, message, **kwargs)
     else:
@@ -20,45 +20,45 @@ def add_message(
 
 
 def debug(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]:
-   """ Add a message to either Django's message framework, if called from a web request,
+    """ Add a message to either Django's message framework, if called from a web request,
     or to the default logger.
 
     Default to DEBUG level.
-   """
+    """
     return add_message(request, messages.DEBUG, message, **kwargs)
 
 
 def info(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]:
-   """ Add a message to either Django's message framework, if called from a web request,
+    """ Add a message to either Django's message framework, if called from a web request,
     or to the default logger.
 
     Default to INFO level.
-   """
+    """
     return add_message(request, messages.INFO, message, **kwargs)
 
 
 def success(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]:
-   """ Add a message to either Django's message framework, if called from a web request,
+    """ Add a message to either Django's message framework, if called from a web request,
     or to the default logger.
 
     Default to SUCCESS level.
-   """
+    """
     return add_message(request, messages.SUCCESS, message, **kwargs)
 
 
 def warning(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]:
-   """ Add a message to either Django's message framework, if called from a web request,
+    """ Add a message to either Django's message framework, if called from a web request,
     or to the default logger.
 
     Default to WARNING level.
-   """
+    """
     return add_message(request, messages.WARNING, message, **kwargs)
 
 
 def error(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]:
-   """ Add a message to either Django's message framework, if called from a web request,
+    """ Add a message to either Django's message framework, if called from a web request,
     or to the default logger.
 
     Default to ERROR level.
-   """
+    """
     return add_message(request, messages.ERROR, message, **kwargs)
diff --git a/aleksis/core/util/notifications.py b/aleksis/core/util/notifications.py
index 9698841d062e8428ba8c922681d665bae77abeb3..f2887dea1644c43eb029ff3f075aadb491fbb2d0 100644
--- a/aleksis/core/util/notifications.py
+++ b/aleksis/core/util/notifications.py
@@ -1,4 +1,4 @@
-"""Utility code for notification system"""
+"""Utility code for notification system."""
 
 from typing import Sequence, Union
 
diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py
index ceb03c68c0619bef76aee1a9439a40bfb9078cc5..f57b55620e2f7d32136f7b96c1fc869e245ca757 100644
--- a/aleksis/core/util/predicates.py
+++ b/aleksis/core/util/predicates.py
@@ -12,24 +12,24 @@ from .core_helpers import has_person as has_person_helper
 
 
 def permission_validator(request: HttpRequest, perm: str) -> bool:
-    """Checks whether the request user has a permission"""
+    """Checks whether the request user has a permission."""
     if request.user:
         return request.user.has_perm(perm)
     return False
 
 
 def check_global_permission(user: User, perm: str) -> bool:
-    """Checks whether a user has a global permission"""
+    """Checks whether a user has a global permission."""
     return ModelBackend().has_perm(user, perm)
 
 
 def check_object_permission(user: User, perm: str, obj: Model) -> bool:
-    """Checks whether a user has a permission on a object"""
+    """Checks whether a user has a permission on a object."""
     return ObjectPermissionBackend().has_perm(user, perm, obj)
 
 
 def has_global_perm(perm: str):
-    """Builds predicate which checks whether a user has a global permission"""
+    """Builds predicate which checks whether a user has a global permission."""
     name = f"has_global_perm:{perm}"
 
     @predicate(name)
@@ -40,7 +40,7 @@ def has_global_perm(perm: str):
 
 
 def has_object_perm(perm: str):
-    """Builds predicate which checks whether a user has a permission on a object"""
+    """Builds predicate which checks whether a user has a permission on a object."""
     name = f"has_global_perm:{perm}"
 
     @predicate(name)
@@ -53,8 +53,8 @@ def has_object_perm(perm: str):
 
 
 def has_any_object(perm: str, klass):
-    """Build predicate which checks whether a user has access
-    to objects with the provided permission
+    """
+    Build predicate which checks whether a user has access to objects with the provided permission.
     """
     name = f"has_any_object:{perm}"
 
@@ -68,25 +68,26 @@ def has_any_object(perm: str, klass):
 
 @predicate
 def has_person(user: User) -> bool:
-    """Predicate which checks whether a user has a linked person"""
+    """Predicate which checks whether a user has a linked person."""
     return has_person_helper(user)
 
 
 @predicate
 def is_current_person(user: User, obj: Model) -> bool:
-    """Predicate which checks if the provided object is the person linked to the user object"""
+    """Predicate which checks if the provided object is the person linked to the user object."""
     return user.person == obj
 
 
 @predicate
 def is_group_owner(user: User, group: Group) -> bool:
-    """Predicate which checks if the user is a owner of the provided group"""
+    """Predicate which checks if the user is a owner of the provided group."""
     return group.owners.filter(owners=user.person).exists()
 
 
 @predicate
 def is_notification_recipient(user: User, obj: Model) -> bool:
-    """Predicate which checks whether the recipient of the
-    notification a user wants to mark read is this user
+    """
+    Predicate which checks whether the recipient of the
+    notification a user wants to mark read is this user.
     """
     return user == obj.recipient.user
diff --git a/aleksis/core/util/sass_helpers.py b/aleksis/core/util/sass_helpers.py
index c28d037e17d0184506f7fb3fd21819fb7cb3aef6..cff75a936f81801eeeae1839bdc8d59eaef739cd 100644
--- a/aleksis/core/util/sass_helpers.py
+++ b/aleksis/core/util/sass_helpers.py
@@ -1,4 +1,4 @@
-"""Helpers for SASS/SCSS compilation"""
+"""Helpers for SASS/SCSS compilation."""
 
 import os
 from glob import glob
@@ -12,7 +12,7 @@ from .core_helpers import get_site_preferences
 
 
 def get_colour(html_colour: str) -> SassColor:
-    """Get a SASS colour object from an HTML colour string"""
+    """Get a SASS colour object from an HTML colour string."""
     rgb = web2hex(html_colour, force_long=True)[1:]
     r, g, b = int(rgb[0:2], 16), int(rgb[2:4], 16), int(rgb[4:6], 16)
 
@@ -20,12 +20,12 @@ def get_colour(html_colour: str) -> SassColor:
 
 
 def get_preference(section: str, name: str) -> str:
-    """Get a preference from dynamic-preferences"""
+    """Get a preference from dynamic-preferences."""
     return get_site_preferences()[f"{section}__{name}"]
 
 
 def clean_scss(*args, **kwargs) -> None:
-    """Unlink compiled CSS (i.e. cache invalidation)"""
+    """Unlink compiled CSS (i.e. cache invalidation)."""
     for source_map in glob(os.path.join(settings.STATIC_ROOT, "*.css.map")):
         try:
             os.unlink(source_map)
diff --git a/aleksis/core/util/search.py b/aleksis/core/util/search.py
index dd8fe793162c9fdde33cdb0803754e47e415dc35..437887ae6412c0e3f8b493908346be8315b69481 100644
--- a/aleksis/core/util/search.py
+++ b/aleksis/core/util/search.py
@@ -12,7 +12,7 @@ else:
 
 
 class SearchIndex(BaseSearchIndex):
-    """ Base class for search indexes on AlekSIS models
+    """ Base class for search indexes on AlekSIS models.
 
     It provides a default document field caleld text and exects
     the related model in the model attribute.
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index cfe22442f3109ba8280abc5360efb9001a931d68..d6387901a1a4e2561471a7d6f3b45cce7b923e6a 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -41,7 +41,7 @@ from .util.core_helpers import objectgetter_optional
 
 @permission_required("core.view_dashboard")
 def index(request: HttpRequest) -> HttpResponse:
-    """Dashboard"""
+    """View for dashboard."""
     context = {}
 
     activities = request.user.person.activities.all()[:5]
@@ -65,12 +65,12 @@ def index(request: HttpRequest) -> HttpResponse:
 
 
 def offline(request: HttpRequest) -> HttpResponse:
-    """Offline message for PWA"""
+    """Offline message for PWA."""
     return render(request, "core/offline.html")
 
 
 def about(request: HttpRequest) -> HttpResponse:
-    """About page listing all apps"""
+    """About page listing all apps."""
     context = {}
 
     context["app_configs"] = list(
@@ -82,7 +82,7 @@ def about(request: HttpRequest) -> HttpResponse:
 
 @permission_required("core.view_persons")
 def persons(request: HttpRequest) -> HttpResponse:
-    """List view listing all persons"""
+    """List view listing all persons."""
     context = {}
 
     # Get all persons
@@ -102,7 +102,7 @@ def persons(request: HttpRequest) -> HttpResponse:
     "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"""
+    """Detail view for one person; defaulting to logged-in person."""
     context = {}
 
     person = objectgetter_optional(Person, "request.user.person", True)(request, id_)
@@ -121,7 +121,7 @@ def person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
 
 @permission_required("core.view_group", fn=objectgetter_optional(Group, None, False))
 def group(request: HttpRequest, id_: int) -> HttpResponse:
-    """Detail view for one group"""
+    """Detail view for one group."""
     context = {}
 
     group = objectgetter_optional(Group, None, False)(request, id_)
@@ -151,7 +151,7 @@ def group(request: HttpRequest, id_: int) -> HttpResponse:
 
 @permission_required("core.view_groups")
 def groups(request: HttpRequest) -> HttpResponse:
-    """List view for listing all groups"""
+    """List view for listing all groups."""
     context = {}
 
     # Get all groups
@@ -167,7 +167,7 @@ def groups(request: HttpRequest) -> HttpResponse:
 
 @permission_required("core.link_persons_accounts")
 def persons_accounts(request: HttpRequest) -> HttpResponse:
-    """View allowing to batch-process linking of users to persons"""
+    """View allowing to batch-process linking of users to persons."""
     context = {}
 
     # Get all persons
@@ -187,7 +187,7 @@ def persons_accounts(request: HttpRequest) -> HttpResponse:
 
 @permission_required("core.assign_child_groups_to_groups")
 def groups_child_groups(request: HttpRequest) -> HttpResponse:
-    """View for batch-processing assignment from child groups to groups"""
+    """View for batch-processing assignment from child groups to groups."""
     context = {}
 
     # Apply filter
@@ -226,7 +226,7 @@ def groups_child_groups(request: HttpRequest) -> HttpResponse:
     "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"""
+    """Edit view for a single person, defaulting to logged-in person."""
     context = {}
 
     person = objectgetter_optional(Person, "request.user.person", True)(request, id_)
@@ -256,7 +256,7 @@ def get_group_by_id(request: HttpRequest, id_: Optional[int] = None):
 
 @permission_required("core.edit_group", fn=objectgetter_optional(Group, None, False))
 def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
-    """View to edit or create a group"""
+    """View to edit or create a group."""
     context = {}
 
     group = objectgetter_optional(Group, None, False)(request, id_)
@@ -284,14 +284,14 @@ def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
 
 @permission_required("core.manage_data")
 def data_management(request: HttpRequest) -> HttpResponse:
-    """View with special menu for data management"""
+    """View with special menu for data management."""
     context = {}
     return render(request, "core/data_management.html", context)
 
 
 @permission_required("core.view_system_status")
 def system_status(request: HttpRequest) -> HttpResponse:
-    """View giving information about the system status"""
+    """View giving information about the system status."""
     context = {}
 
     return render(request, "core/system_status.html", context)
@@ -301,7 +301,7 @@ def system_status(request: HttpRequest) -> HttpResponse:
     "core.mark_notification_as_read", fn=objectgetter_optional(Notification, None, False)
 )
 def notification_mark_read(request: HttpRequest, id_: int) -> HttpResponse:
-    """Mark a notification read"""
+    """Mark a notification read."""
     notification = objectgetter_optional(Notification, None, False)(request, id_)
 
     notification.read = True
@@ -313,7 +313,7 @@ def notification_mark_read(request: HttpRequest, id_: int) -> HttpResponse:
 
 @permission_required("core.view_announcements")
 def announcements(request: HttpRequest) -> HttpResponse:
-    """List view of announcements"""
+    """List view of announcements."""
     context = {}
 
     # Get all announcements
@@ -327,7 +327,7 @@ def announcements(request: HttpRequest) -> HttpResponse:
     "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"""
+    """View to create or edit an announcement."""
     context = {}
 
     announcement = objectgetter_optional(Announcement, None, False)(request, id_)
@@ -357,7 +357,7 @@ def announcement_form(request: HttpRequest, id_: Optional[int] = None) -> HttpRe
     "core.delete_announcement", fn=objectgetter_optional(Announcement, None, False)
 )
 def delete_announcement(request: HttpRequest, id_: int) -> HttpResponse:
-    """View to delete an announcement"""
+    """View to delete an announcement."""
     if request.method == "POST":
         announcement = objectgetter_optional(Announcement, None, False)(request, id_)
         announcement.delete()
@@ -368,7 +368,7 @@ def delete_announcement(request: HttpRequest, id_: int) -> HttpResponse:
 
 @permission_required("core.search")
 def searchbar_snippets(request: HttpRequest) -> HttpResponse:
-    """View to return HTML snippet with searchbar autocompletion results"""
+    """View to return HTML snippet with searchbar autocompletion results."""
     query = request.GET.get("q", "")
     limit = int(request.GET.get("limit", "5"))
 
@@ -379,7 +379,7 @@ def searchbar_snippets(request: HttpRequest) -> HttpResponse:
 
 
 class PermissionSearchView(PermissionRequiredMixin, SearchView):
-    """Wrapper to apply permission to haystack's search view"""
+    """Wrapper to apply permission to haystack's search view."""
     permission_required = "core.search"
 
     def create_response(self):
@@ -395,7 +395,7 @@ def preferences(
     pk: Optional[int] = None,
     section: Optional[str] = None,
 ) -> HttpResponse:
-    """View for changing preferences"""
+    """View for changing preferences."""
     context = {}
 
     # Decide which registry to use and check preferences