From cad2f7373a716750d234842d93849af3014b72ad Mon Sep 17 00:00:00 2001
From: Lucas Brandstaetter <lucas@brandstaetter.tech>
Date: Sun, 26 Nov 2023 19:48:00 +0100
Subject: [PATCH] Format and fix with ruff rules

* Reformat files
* Format Imports
* Fix PL rules
* Fix ERA rules
* Fix RSE rules
* Fix SIM rules
* Fix UP rules
---
 deployment/docker/check_psql.py               |    6 +-
 deployment/local_settings.docker-example.py   |    9 +-
 src/api/permissions.py                        |    3 +-
 src/api/schedule.py                           |  134 +-
 src/api/serializers.py                        |   81 +-
 src/api/tests/__init__.py                     |    2 +-
 src/api/tests/badges/create_redeem_token.py   |   34 +-
 src/api/tests/bbb.py                          |   33 +-
 src/api/tests/map.py                          |    9 +-
 src/api/tests/metrics.py                      |   30 +-
 src/api/tests/permissions.py                  |    6 +-
 src/api/tests/schedule.py                     |  158 ++-
 src/api/tests/workadventure.py                |   25 +-
 src/api/urls.py                               |   22 +-
 src/api/urls_sso.py                           |   19 +-
 src/api/views/__init__.py                     |    1 -
 src/api/views/assemblies.py                   |    6 +-
 src/api/views/badges.py                       |   49 +-
 src/api/views/bbb.py                          |    2 +-
 src/api/views/conferencemember.py             |   15 +-
 src/api/views/events.py                       |   14 +-
 src/api/views/maps.py                         |    9 +-
 src/api/views/metanav.py                      |    4 +-
 src/api/views/metrics.py                      |  148 +-
 src/api/views/mixins.py                       |    2 +-
 src/api/views/rooms.py                        |    2 +-
 src/api/views/schedule.py                     |   38 +-
 src/api/views/sso.py                          |   18 +-
 src/api/views/users.py                        |   62 +-
 src/api/views/workadventure.py                |   52 +-
 src/backoffice/forms.py                       |   51 +-
 src/backoffice/templatetags/c3assemblies.py   |    4 +-
 src/backoffice/tests/__init__.py              |    4 +-
 src/backoffice/tests/assemblies.py            |   13 +-
 src/backoffice/tests/auth.py                  |   29 +-
 src/backoffice/tests/base.py                  |    6 +-
 src/backoffice/urls.py                        |   59 +-
 src/backoffice/views/assemblies.py            |  148 +-
 src/backoffice/views/assemblyteam.py          |  143 +-
 src/backoffice/views/auth.py                  |    2 +-
 src/backoffice/views/badges.py                |    2 +-
 src/backoffice/views/channelteam.py           |    9 +-
 src/backoffice/views/events.py                |   22 +-
 src/backoffice/views/map.py                   |   40 +-
 src/backoffice/views/misc.py                  |   14 +-
 src/backoffice/views/mixins.py                |  296 ++--
 src/backoffice/views/schedules.py             |   10 +-
 src/backoffice/views/users.py                 |   23 +-
 src/backoffice/views/utils.py                 |   14 +-
 src/backoffice/views/vouchers.py              |    3 +-
 src/backoffice/views/wiki.py                  |   23 +-
 src/backoffice/views/workadventure.py         |   63 +-
 src/core/abuse.py                             |   24 +-
 src/core/admin.py                             |  469 ++++---
 src/core/apps.py                              |    1 -
 src/core/base_forms.py                        |    8 +-
 src/core/forms.py                             |    4 +-
 src/core/integrations/__init__.py             |    3 +-
 src/core/integrations/bigbluebutton.py        |   43 +-
 src/core/integrations/workadventure.py        |   26 +-
 .../commands/assembly_contact_mails.py        |    2 +-
 .../commands/bbb_integration_revisit.py       |   10 +-
 .../management/commands/hangar_creation.py    |    4 +-
 src/core/management/commands/housekeeping.py  |    4 +-
 .../commands/import_mapservice_resultfile.py  |    3 +-
 .../management/commands/rerender_markdown.py  |    2 +-
 .../management/commands/sanitize_database.py  |   32 +-
 .../commands/schedule_join_rooms.py           |    2 +-
 src/core/management/commands/serviceusers.py  |    1 +
 src/core/management/commands/stats.py         |   24 +-
 .../management/commands/suggestion_tick.py    |   32 +-
 src/core/markdown.py                          |  131 +-
 src/core/middleware.py                        |    5 +-
 ...4_prepare_primary_keys_badge_badgetoken.py |    2 +-
 src/core/models/__init__.py                   |   65 +-
 src/core/models/assemblies.py                 |  191 ++-
 src/core/models/badges.py                     |    3 +-
 src/core/models/board.py                      |    4 +-
 src/core/models/conference.py                 |  250 ++--
 src/core/models/dereferrer.py                 |    1 +
 src/core/models/events.py                     |  156 +--
 src/core/models/map.py                        |   31 +-
 src/core/models/messages.py                   |   64 +-
 src/core/models/pages.py                      |  117 +-
 src/core/models/rooms.py                      |   56 +-
 src/core/models/schedules.py                  |  177 +--
 src/core/models/shared.py                     |   11 +-
 src/core/models/sso.py                        |    8 +-
 src/core/models/tags.py                       |   28 +-
 src/core/models/ticket.py                     |    4 +-
 src/core/models/users.py                      |  251 ++--
 src/core/models/voucher.py                    |   72 +-
 src/core/models/workadventure.py              |   28 +-
 src/core/schedules/__init__.py                |    1 -
 src/core/schedules/base.py                    |    8 +-
 src/core/schedules/schedulejson.py            |   50 +-
 src/core/schedules/schedulexml.py             |   49 +-
 src/core/search.py                            |   20 +-
 src/core/sso.py                               |   10 +-
 src/core/tests/__init__.py                    |    8 +-
 src/core/tests/assemblies.py                  |   33 +-
 src/core/tests/badges.py                      |    2 +-
 src/core/tests/bigbluebutton.py               |  122 +-
 src/core/tests/events.py                      |   10 +-
 src/core/tests/exportcache.py                 |    2 +-
 src/core/tests/markdown.py                    |    1 -
 src/core/tests/schedules.py                   |   19 +-
 src/core/tests/search.py                      |    8 +-
 src/core/tests/tags.py                        |   38 +-
 src/core/tests/tickets.py                     |   17 +-
 src/core/tests/users.py                       |    4 +-
 src/core/tests/utils.py                       |    8 +-
 src/core/tests/validators.py                  |    4 +-
 src/core/tests/workadventure.py               |   22 +-
 src/core/tokens.py                            |    5 +-
 src/core/translation.py                       |   40 +-
 src/core/utils.py                             |   36 +-
 src/core/validators.py                        |   60 +-
 src/core/views/auth.py                        |    4 +-
 src/hub/settings/base.py                      |  130 +-
 src/hub/settings/build.py                     |    2 +-
 src/hub/settings/default.py                   |   25 +-
 src/hub/settings/dev.py                       |   16 +-
 src/hub/views.py                              |    5 +-
 src/manage.py                                 |    4 +-
 src/plainui/forms.py                          |  113 +-
 src/plainui/jinja2.py                         |   61 +-
 .../management/commands/makemessages.py       |    4 +-
 src/plainui/tests/test_views.py               | 1228 ++++++++++-------
 src/plainui/urls.py                           |    8 +-
 src/plainui/utils.py                          |   25 +-
 src/plainui/views.py                          |  684 ++++-----
 tests/utils.py                                |    1 -
 133 files changed, 3757 insertions(+), 3450 deletions(-)

diff --git a/deployment/docker/check_psql.py b/deployment/docker/check_psql.py
index e542e5c8c..1c2d2e41d 100755
--- a/deployment/docker/check_psql.py
+++ b/deployment/docker/check_psql.py
@@ -9,17 +9,17 @@ except ImportError:
     import psycopg2 as psycopg
 
 
-url = os.getenv("DATABASE_URL")
+url = os.getenv('DATABASE_URL')
 if url is None or url == '':
     print('No DATABASE_URL specified!', file=sys.stderr)
     sys.exit(2)
 
 try:
     if url.startswith('postgis://'):
-        url = 'postgresql://' + url[len('postgis://'):]
+        url = 'postgresql://' + url[len('postgis://') :]
     psycopg.connect(url)
 
 except Exception as err:
     print('ERROR', file=sys.stderr)
     print('    ', err, sep='', file=sys.stderr)
-    exit(1)
+    sys.exit(1)
diff --git a/deployment/local_settings.docker-example.py b/deployment/local_settings.docker-example.py
index a07fb65e1..3a1449f28 100644
--- a/deployment/local_settings.docker-example.py
+++ b/deployment/local_settings.docker-example.py
@@ -1,5 +1,5 @@
 # Dies ist ein Beispiel für eine local_settings.py in einem Docker-Deployment.
-
+# ruff: noqa: ERA001
 IS_ADMIN = True
 IS_API = True
 IS_BACKOFFICE = True
@@ -11,10 +11,7 @@ WORKADVENTURE_URL_SCHEME = 'https://play.{assembly_slug}.at.rc3.world/'  # other
 LOGGING = {
     'version': 1,
     'disable_existing_loggers': False,
-
-    'filters': {
-    },
-
+    'filters': {},
     'formatters': {
         'verbose': {
             'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
@@ -25,7 +22,6 @@ LOGGING = {
             'style': '{',
         },
     },
-
     'handlers': {
         'file': {
             'level': 'INFO',
@@ -45,7 +41,6 @@ LOGGING = {
         #     'class': 'django.utils.log.AdminEmailHandler'
         # },
     },
-
     'loggers': {
         'django': {
             'handlers': ['file', 'console'],
diff --git a/src/api/permissions.py b/src/api/permissions.py
index 5c2c7aae6..431a9b180 100644
--- a/src/api/permissions.py
+++ b/src/api/permissions.py
@@ -1,6 +1,7 @@
+from rest_framework import permissions
+
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
-from rest_framework import permissions
 
 from core.models.assemblies import Assembly
 from core.models.badges import Badge
diff --git a/src/api/schedule.py b/src/api/schedule.py
index 33da86f37..5c9ed35a2 100644
--- a/src/api/schedule.py
+++ b/src/api/schedule.py
@@ -1,40 +1,42 @@
+# ruff: noqa: PLW2901
 import json
-import re
 import logging
-
+import re
 from collections import OrderedDict
 from datetime import datetime, timedelta
+from typing import Optional
 from uuid import UUID
+
 from lxml import etree as ET
-from typing import Optional
-# from xml.etree import cElementTree as ET
 
 from django.conf import settings
 
 from core.models.conference import Conference
-from core.models.rooms import Room
 from core.models.events import Event
+from core.models.rooms import Room
 
 logger = logging.getLogger(__name__)
 
 # template for optimized attribute order
-event_template = OrderedDict({
-    'guid': None,
-    'id': None,
-    'date': None,
-    'start': None,
-    'duration': None,
-    'room': None,
-    'slug': None,
-    'url': None,
-    'title': None,
-    'subtitle': None,
-    'language': None,
-    'track': None,
-    'type': 'other',
-    'abstract': None,
-    'description': None,
-})
+event_template = OrderedDict(
+    {
+        'guid': None,
+        'id': None,
+        'date': None,
+        'start': None,
+        'duration': None,
+        'room': None,
+        'slug': None,
+        'url': None,
+        'title': None,
+        'subtitle': None,
+        'language': None,
+        'track': None,
+        'type': 'other',
+        'abstract': None,
+        'description': None,
+    }
+)
 
 
 class Day:
@@ -82,7 +84,7 @@ class ScheduleEncoder(json.JSONEncoder):
     tz = None
 
     def encode_duration(self, duration: Optional[timedelta]) -> Optional[str]:
-        """ converts a python `timedelta` to the schedule xml timedelta string that represents this timedelta. ([d:]HH:mm) """
+        """converts a python `timedelta` to the schedule xml timedelta string that represents this timedelta. ([d:]HH:mm)"""
 
         if duration is None:
             return None
@@ -91,39 +93,44 @@ class ScheduleEncoder(json.JSONEncoder):
         hours = duration.seconds // 3600
         minutes = (duration.seconds % 3600) // 60
         if days:
-            return f"{days}:{hours:02d}:{minutes:02d}"
-        return f"{hours:02d}:{minutes:02d}"
+            return f'{days}:{hours:02d}:{minutes:02d}'
+        return f'{hours:02d}:{minutes:02d}'
 
     def encode_event(self, event: Event, tz=None):
         start = event.schedule_start.astimezone(tz or self.tz) if event.schedule_start is not None else None
         additional_data = event.additional_data or {}
         legacy_id = additional_data.get('id') or int(re.sub('[^0-9]+', '', str(event.id))[0:6])
         slug = f'{event.conference.slug}-{legacy_id}-{event.slug}'
-        return OrderedDict({
-            **event_template,
-            'id': legacy_id,
-            'description': event.description,  # TODO: if the description also exists in additional_data it is overwritten due to concatination with abstract
-            **additional_data,
-            'slug': slug,
-            'url': event.get_absolute_url(),
-            'guid': event.id,
-            'date': start.isoformat() if start is not None else None,
-            'start': start.strftime('%H:%M') if start is not None else None,
-            'duration': self.encode_duration(event.schedule_duration),
-            'room': event.room.name if event.room is not None else None,
-            'title': event.name,
-            'language': event.language,
-            'track': event.track.name if event.track else None,
-        })
+        return OrderedDict(
+            {
+                **event_template,
+                'id': legacy_id,
+                # TODO: if the description also exists in additional_data it is overwritten due to concatenation with abstract
+                'description': event.description,
+                **additional_data,
+                'slug': slug,
+                'url': event.get_absolute_url(),
+                'guid': event.id,
+                'date': start.isoformat() if start is not None else None,
+                'start': start.strftime('%H:%M') if start is not None else None,
+                'duration': self.encode_duration(event.schedule_duration),
+                'room': event.room.name if event.room is not None else None,
+                'title': event.name,
+                'language': event.language,
+                'track': event.track.name if event.track else None,
+            }
+        )
 
     def encode_day(self, obj: Day):
-        return OrderedDict({
-            'index': obj.day.index,
-            'date': obj.day.start.strftime('%Y-%m-%d'),
-            'day_start': obj.day.start.isoformat(),
-            'day_end': obj.day.end.isoformat(),
-            'rooms': obj.rooms
-        })
+        return OrderedDict(
+            {
+                'index': obj.day.index,
+                'date': obj.day.start.strftime('%Y-%m-%d'),
+                'day_start': obj.day.start.isoformat(),
+                'day_end': obj.day.end.isoformat(),
+                'rooms': obj.rooms,
+            }
+        )
 
     def encode_room(self, obj: RoomDay):
         return obj.events
@@ -131,10 +138,7 @@ class ScheduleEncoder(json.JSONEncoder):
     def transform(self, obj):
         if isinstance(obj, Schedule):
             self.tz = obj.tz
-            return {
-                '$schema': 'https://c3voc.de/schedule/schema.json',
-                'schedule': obj._schedule
-            }
+            return {'$schema': 'https://c3voc.de/schedule/schema.json', 'schedule': obj._schedule}
         if isinstance(obj, Day):
             return self.encode_day(obj)
         if isinstance(obj, RoomDay):
@@ -151,9 +155,10 @@ class ScheduleEncoder(json.JSONEncoder):
 
 
 class Schedule:
-    '''
+    """
     Schedule class with import and export methods
-    '''
+    """
+
     _schedule = None
     tz = None
     conference = None
@@ -163,7 +168,7 @@ class Schedule:
         self.tz = conference.timezone
 
         self._schedule = {
-            'version': datetime.now(self.tz).strftime("%Y-%m-%d %H:%M"),
+            'version': datetime.now(self.tz).strftime('%Y-%m-%d %H:%M'),
             'base_url': settings.PLAINUI_BASE_URL,  # 'https://events.ccc.de/' + conference.slug + '/',
             'conference': {
                 'acronym': conference.slug,
@@ -174,7 +179,7 @@ class Schedule:
                 'timeslot_duration': '00:10',
                 'time_zone_name': self.tz.key if self.tz.key else datetime.now(self.tz).tzname(),
                 'days': [Day(day) for day in conference.days],
-            }
+            },
         }
 
     def __getitem__(self, key):
@@ -206,7 +211,7 @@ class Schedule:
 
     def add_events(self, events):
         for event in events:
-            try:
+            try:  # noqa: SIM105
                 self.add_event(event)
             except Warning:
                 # TODO log event and error
@@ -242,7 +247,7 @@ class Schedule:
         def _set_attrib(tag, k, v):
             if isinstance(v, str):
                 tag.set(k, v)
-            elif isinstance(v, int) or isinstance(v, UUID):
+            elif isinstance(v, (UUID, int)):
                 tag.set(k, str(v))
             elif v is not None:
                 logger.error('unknown attribute type %s=%s', k, v)
@@ -259,7 +264,7 @@ class Schedule:
             elif parent == 'person':
                 node.text = d['public_name']
                 _set_attrib(node, 'id', d['id'])
-            elif isinstance(d, dict) or isinstance(d, OrderedDict):
+            elif isinstance(d, (OrderedDict, dict)):
                 if parent == 'schedule' and 'base_url' in d:
                     d['conference']['base_url'] = d['base_url']
                     del d['base_url']
@@ -315,10 +320,7 @@ class Schedule:
                             continue
                         elif k == 'do_not_record':
                             k = 'recording'
-                            v = OrderedDict([
-                                ('license', recording_license),
-                                ('optout', 'true' if v else 'false')
-                            ])
+                            v = OrderedDict([('license', recording_license), ('optout', 'true' if v else 'false')])
                         if isinstance(v, RoomDay):
                             _set_attrib(node_, 'guid', v.room.id)
                             for event in v.events:
@@ -333,13 +335,9 @@ class Schedule:
                             _to_etree(v, ET.SubElement(node_, k), k)
             else:
                 raise Exception('unknown type', d, parent)
-        # assert isinstance(self._schedule, dict) and len(self._schedule) == 1
-        # encoder = ScheduleEncoder()
-        # schedule_dict = encoder.default(self.json())
 
         root_node = ET.Element('schedule')
-        root_node.set("{http://www.w3.org/2001/XMLSchema-instance}noNamespaceSchemaLocation", "https://c3voc.de/schedule/schema.xsd")
+        root_node.set('{http://www.w3.org/2001/XMLSchema-instance}noNamespaceSchemaLocation', 'https://c3voc.de/schedule/schema.xsd')
         _to_etree(self._schedule, root_node, 'schedule')
 
         return ET.tounicode(root_node, doctype='<?xml version="1.0"?>')
-        # return ET.tostring(root_node, xml_declaration=True, encoding='utf-8', pretty_print = True, )
diff --git a/src/api/serializers.py b/src/api/serializers.py
index fa6856eb0..15310fb7c 100644
--- a/src/api/serializers.py
+++ b/src/api/serializers.py
@@ -1,15 +1,18 @@
-from django.core.exceptions import SuspiciousOperation, ValidationError
+import contextlib
+
 from rest_framework import serializers
 from rest_framework.relations import HyperlinkedIdentityField
 from rest_framework.reverse import reverse
 
+from django.core.exceptions import SuspiciousOperation, ValidationError
+
+from core.models.assemblies import Assembly
 from core.models.badges import Badge, BadgeToken, BadgeTokenTimeConstraint
 from core.models.conference import Conference, ConferenceMember, ConferenceTrack
 from core.models.events import Event
 from core.models.metanavi import MetaNavItem
 from core.models.rooms import Room
 from core.models.users import UserTimelineEntry
-from core.models.assemblies import Assembly
 
 
 class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField):
@@ -21,11 +24,12 @@ class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField):
 
     taken from https://github.com/encode/django-rest-framework/issues/1024
     """
+
     lookup_fields = (('pk', 'pk'),)
 
     def __init__(self, *args, **kwargs):
         self.lookup_fields = kwargs.pop('lookup_fields', self.lookup_fields)
-        super(ParameterisedHyperlinkedIdentityField, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def get_url(self, obj, view_name, request, format):
         """
@@ -46,14 +50,8 @@ class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField):
 
 class ValidatingModelSerializer(serializers.ModelSerializer):
     def validate(self, data):
-        instance = self.Meta.model(**{
-            field: value
-            for field, value in data.items()
-            if field in self.Meta.model._meta.fields
-        })
-        for f, v in {field: value
-                     for field, value in data.items()
-                     if field in self.Meta.model._meta.fields}.items():
+        instance = self.Meta.model(**{field: value for field, value in data.items() if field in self.Meta.model._meta.fields})
+        for f, v in {field: value for field, value in data.items() if field in self.Meta.model._meta.fields}.items():
             getattr(instance, f).set(v)
         try:
             instance.clean()
@@ -81,13 +79,11 @@ class HubModelSerializer(ValidatingModelSerializer):
         self.request_user = request_user = self.context['request'].user
         self.conference_member = None
         if request_user.is_authenticated:
-            try:
+            with contextlib.suppress(ConferenceMember.DoesNotExist):
                 self.conference_member = ConferenceMember.objects.select_related('conference').get(
                     conference__slug=conference_slug,
                     user=request_user,
                 )
-            except ConferenceMember.DoesNotExist:
-                pass
 
         # store if the request's user has staff permissions in the conference (either direct or globally)
         self.is_staff = request_user.is_superuser or request_user.is_staff or (self.conference_member is not None and self.conference_member.is_staff)
@@ -104,18 +100,16 @@ class HubModelSerializer(ValidatingModelSerializer):
 
 
 class ConferenceSerializer(HubModelSerializer):
-    tracks = serializers.SlugRelatedField(
-        many=True,
-        read_only=True,
-        slug_field='slug'
-    )
+    tracks = serializers.SlugRelatedField(many=True, read_only=True, slug_field='slug')
 
     class Meta:
         model = Conference
         fields = [
-            'slug', 'name',
+            'slug',
+            'name',
             'is_public',
-            'start', 'end',
+            'start',
+            'end',
             'registration_deadline',
             'tracks',
         ]
@@ -126,27 +120,19 @@ class ConferenceTrackSerializer(HubModelSerializer):
     class Meta:
         model = ConferenceTrack
         read_only_fields = ['id']
-        fields = [
-            'conference',
-            'slug',
-            'name',
-            'is_public',
-            'id'
-        ]
+        fields = ['conference', 'slug', 'name', 'is_public', 'id']
         staff_only_fields = ['is_public']
 
 
 class AssemblySerializer(HubModelSerializer):
-    conference = serializers.SlugRelatedField(
-        read_only=True,
-        slug_field='slug'
-    )
+    conference = serializers.SlugRelatedField(read_only=True, slug_field='slug')
     parent = serializers.SlugRelatedField(slug_field='slug', queryset=Assembly.objects.none)
 
     events_url = ParameterisedHyperlinkedIdentityField(view_name='api:assembly-events', lookup_fields=(('conference_slug', 'conference'), ('slug', 'assembly')))
     rooms_url = ParameterisedHyperlinkedIdentityField(view_name='api:assembly-rooms', lookup_fields=(('conference_slug', 'conference'), ('slug', 'assembly')))
-    badges_url = ParameterisedHyperlinkedIdentityField(view_name='api:assembly-badges-list-create',
-                                                       lookup_fields=(('conference_slug', 'conference'), ('slug', 'assembly')))
+    badges_url = ParameterisedHyperlinkedIdentityField(
+        view_name='api:assembly-badges-list-create', lookup_fields=(('conference_slug', 'conference'), ('slug', 'assembly'))
+    )
 
     class Meta:
         model = Assembly
@@ -178,11 +164,11 @@ class BadgeSerializer(HubModelSerializer):
     class Meta:
         model = Badge
         exclude = ['issuing_assembly', 'conference', 'issuing_token']
-    badge_token_url = ParameterisedHyperlinkedIdentityField(view_name='api:badge-token-list-create',
-                                                            lookup_fields=(
-                                                                ('issuing_assembly.conference_slug', 'conference'),
-                                                                ('issuing_assembly.slug', 'assembly'),
-                                                                ('pk', 'pk')))
+
+    badge_token_url = ParameterisedHyperlinkedIdentityField(
+        view_name='api:badge-token-list-create',
+        lookup_fields=(('issuing_assembly.conference_slug', 'conference'), ('issuing_assembly.slug', 'assembly'), ('pk', 'pk')),
+    )
 
     def create(self, validated_data):
         issuing_assembly = Assembly.objects.filter(slug=self.context['assembly'])
@@ -223,15 +209,9 @@ class BadgeTokenUpdateSerializer(BadgeTokenSerializer):
 
 
 class RoomSerializer(HubModelSerializer):
-    conference = serializers.SlugRelatedField(
-        read_only=True,
-        slug_field='slug'
-    )
+    conference = serializers.SlugRelatedField(read_only=True, slug_field='slug')
 
-    assembly = serializers.SlugRelatedField(
-        read_only=True,
-        slug_field='slug'
-    )
+    assembly = serializers.SlugRelatedField(read_only=True, slug_field='slug')
 
     links = serializers.StringRelatedField(
         many=True,
@@ -288,7 +268,7 @@ class EventSerializer(HubModelSerializer):
     def update(self, instance, validated_data):
         # is somebody trying to change the event ID?
         if (given_id := validated_data.get('id')) is not None and given_id != instance.id:
-            raise SuspiciousOperation('You cannot update an event\'s id.')
+            raise SuspiciousOperation("You cannot update an event's id.")
 
         # prevent some fields from being updated, no matter what
         for protected_field in ['id', 'conference']:
@@ -312,10 +292,7 @@ class UserTimelineEntrySerializer(ValidatingModelSerializer):
 
 
 class MetaNavItemSerializer(HubModelSerializer):
-    conference = serializers.SlugRelatedField(
-        read_only=True,
-        slug_field='slug'
-    )
+    conference = serializers.SlugRelatedField(read_only=True, slug_field='slug')
 
     class Meta:
         model = MetaNavItem
diff --git a/src/api/tests/__init__.py b/src/api/tests/__init__.py
index e6a579db4..8fb7973a1 100644
--- a/src/api/tests/__init__.py
+++ b/src/api/tests/__init__.py
@@ -6,4 +6,4 @@ from .metrics import *  # noqa: F401, F403
 from .schedule import *  # noqa: F401, F403
 from .workadventure import *  # noqa: F401, F403
 
-__all__ = '*'
+__all__ = ('*',)  # noqa: F405
diff --git a/src/api/tests/badges/create_redeem_token.py b/src/api/tests/badges/create_redeem_token.py
index 9f8ea1f23..125f02f8e 100644
--- a/src/api/tests/badges/create_redeem_token.py
+++ b/src/api/tests/badges/create_redeem_token.py
@@ -1,11 +1,11 @@
 from datetime import datetime
+from http import HTTPStatus
 
-from django.test import TestCase
-from django.urls import reverse
 from rest_framework.authtoken.models import Token
 from zoneinfo import ZoneInfo
 
-from http import HTTPStatus
+from django.test import TestCase
+from django.urls import reverse
 
 from core.models import Assembly, Badge, BadgeToken, Conference, PlatformUser
 
@@ -14,7 +14,9 @@ class CreateRedeemTokenTests(TestCase):
     def setUp(self):
         tz = ZoneInfo('CET')
         self.conf = Conference(
-            slug='conf', name='TestConf', is_public=True,
+            slug='conf',
+            name='TestConf',
+            is_public=True,
             start=datetime(2020, 12, 27, 1, 23, 45, tzinfo=tz),
             end=datetime(2020, 12, 30, 23, 45, 00, tzinfo=tz),
         )
@@ -22,7 +24,11 @@ class CreateRedeemTokenTests(TestCase):
         self.assembly = Assembly(name='TestAssembly', slug='asmbly', conference=self.conf, state_assembly=Assembly.State.PLACED)
         self.assembly.save()
 
-        self.badge = Badge(conference=self.conf, issuing_assembly=self.assembly, name="Test Badge", )
+        self.badge = Badge(
+            conference=self.conf,
+            issuing_assembly=self.assembly,
+            name='Test Badge',
+        )
         self.badge_token = self.badge.reset_token()
 
         self.user = PlatformUser(username='bernd', is_active=True)
@@ -32,27 +38,27 @@ class CreateRedeemTokenTests(TestCase):
         self.token.save()
 
     def test_create_redeem_token_invalid_issuing_token(self):
-        url = reverse('api:badge-token-create-with-token', kwargs={'conference': self.conf.slug, 'assembly': self.assembly, "issuing_token": "invalid"})
-        resp = self.client.post(url, {"issuing_token": "test"})
+        url = reverse('api:badge-token-create-with-token', kwargs={'conference': self.conf.slug, 'assembly': self.assembly, 'issuing_token': 'invalid'})
+        resp = self.client.post(url, {'issuing_token': 'test'})
         self.assertEqual(resp.status_code, 404)
 
     def test_create_redeem_invalid_conference(self):
-        url = reverse('api:badge-token-create-with-token', kwargs={'conference': "invalid", 'assembly': self.assembly, "issuing_token": self.badge_token})
-        resp = self.client.post(url, {"issuing_token": self.badge_token, "badge_class": "single"})
+        url = reverse('api:badge-token-create-with-token', kwargs={'conference': 'invalid', 'assembly': self.assembly, 'issuing_token': self.badge_token})
+        resp = self.client.post(url, {'issuing_token': self.badge_token, 'badge_class': 'single'})
         self.assertEqual(resp.status_code, 404)
 
     def test_create_redeem_token_limited(self):
-        url = reverse('api:badge-token-create-with-token', kwargs={'conference': self.conf.slug, 'assembly': self.assembly, "issuing_token": self.badge_token})
-        resp = self.client.post(url, {"issuing_token": self.badge_token, "redeemable_count": 20})
+        url = reverse('api:badge-token-create-with-token', kwargs={'conference': self.conf.slug, 'assembly': self.assembly, 'issuing_token': self.badge_token})
+        resp = self.client.post(url, {'issuing_token': self.badge_token, 'redeemable_count': 20})
         self.assertEqual(resp.status_code, HTTPStatus.CREATED)
         badge_token = BadgeToken.objects.get(badge=self.badge)
-        self.assertEqual(str(badge_token.token), resp.json()["token"])
+        self.assertEqual(str(badge_token.token), resp.json()['token'])
         self.assertEqual(badge_token.redeemable_count, 20)
 
     def test_create_redeem_token(self):
-        url = reverse('api:badge-token-create-with-token', kwargs={'conference': self.conf.slug, 'assembly': self.assembly, "issuing_token": self.badge_token})
+        url = reverse('api:badge-token-create-with-token', kwargs={'conference': self.conf.slug, 'assembly': self.assembly, 'issuing_token': self.badge_token})
         resp = self.client.post(url, {})
         self.assertEqual(resp.status_code, HTTPStatus.CREATED)
         badge_token = BadgeToken.objects.get(badge=self.badge)
-        self.assertEqual(str(badge_token.token), resp.json()["token"])
+        self.assertEqual(str(badge_token.token), resp.json()['token'])
         self.assertEqual(badge_token.redeemable_count, 0)
diff --git a/src/api/tests/bbb.py b/src/api/tests/bbb.py
index c285cbc3e..8ad086159 100644
--- a/src/api/tests/bbb.py
+++ b/src/api/tests/bbb.py
@@ -1,4 +1,5 @@
 from uuid import uuid4
+
 from django.test import TestCase
 from django.urls import reverse
 
@@ -12,22 +13,32 @@ class BBBTest(TestCase):
         assembly = Assembly(name='TestAssembly', slug='asmbly', conference=conf)
         assembly.save()
         room = Room(
-            conference=conf, assembly=assembly, name='Room 1',
-            room_type=Room.RoomType.BIGBLUEBUTTON, backend_status=Room.BackendStatus.ACTIVE,
-            backend_link=str(uuid4()), backend_data={'close_secret': 'asdf'}
+            conference=conf,
+            assembly=assembly,
+            name='Room 1',
+            room_type=Room.RoomType.BIGBLUEBUTTON,
+            backend_status=Room.BackendStatus.ACTIVE,
+            backend_link=str(uuid4()),
+            backend_data={'close_secret': 'asdf'},
         )
         room.save()
 
-        self.client.get(reverse('api:bbb_meeting_end'), {
-            'meetingID': room.backend_link,
-            'close_secret': 'invalid',
-        })
+        self.client.get(
+            reverse('api:bbb_meeting_end'),
+            {
+                'meetingID': room.backend_link,
+                'close_secret': 'invalid',
+            },
+        )
         room.refresh_from_db()
         self.assertEqual(room.backend_status, Room.BackendStatus.ACTIVE)
 
-        self.client.get(reverse('api:bbb_meeting_end'), {
-            'meetingID': room.backend_link,
-            'close_secret': 'asdf',
-        })
+        self.client.get(
+            reverse('api:bbb_meeting_end'),
+            {
+                'meetingID': room.backend_link,
+                'close_secret': 'asdf',
+            },
+        )
         room.refresh_from_db()
         self.assertEqual(room.backend_status, Room.BackendStatus.INACTIVE)
diff --git a/src/api/tests/map.py b/src/api/tests/map.py
index 29667843b..7b30bb3c4 100644
--- a/src/api/tests/map.py
+++ b/src/api/tests/map.py
@@ -1,19 +1,22 @@
-from datetime import datetime
 import json
+from datetime import datetime
+
 from zoneinfo import ZoneInfo
 
 from django.contrib.gis.geos import Point
 from django.test import TestCase
 from django.urls import reverse
 
-from core.models import Assembly, ConferenceExportCache, Conference, PlatformUser
+from core.models import Assembly, Conference, ConferenceExportCache, PlatformUser
 
 
 class MapTest(TestCase):
     def setUp(self):
         tz = ZoneInfo('CET')
         self.conf = Conference(
-            slug='conf', name='TestConf', is_public=True,
+            slug='conf',
+            name='TestConf',
+            is_public=True,
             start=datetime(2020, 12, 27, 1, 23, 45, tzinfo=tz),
             end=datetime(2020, 12, 30, 23, 45, 00, tzinfo=tz),
         )
diff --git a/src/api/tests/metrics.py b/src/api/tests/metrics.py
index 20e03fe68..6f56c2432 100644
--- a/src/api/tests/metrics.py
+++ b/src/api/tests/metrics.py
@@ -1,15 +1,14 @@
 from uuid import uuid4
 
+from django.contrib.auth import get_user_model
 from django.test import TestCase, override_settings
 from django.urls import reverse
-from django.contrib.auth import get_user_model
 
 from core.models import Assembly, Conference, Room, WorkadventureSession
 from core.models.conference import ConferenceMember
 
 
 class MetricsTest(TestCase):
-
     def test_MetricsView_allow_ip_addresses(self):
         with self.settings(METRICS_SERVER_IPS=['1.2.3.4']):
             resp = self.client.get(reverse('metrics:index'))
@@ -26,7 +25,7 @@ class MetricsTest(TestCase):
             # Our address is not 127.0.0.1 so we should get a `HTTP 200`.
             self.assertEqual(200, resp.status_code)
 
-    @override_settings(METRICS_SERVER_IPS="*")
+    @override_settings(METRICS_SERVER_IPS='*')
     def test_MetricsView(self):
         conf = Conference(slug='conf', name='TestConf', is_public=True)
         conf.save()
@@ -38,24 +37,27 @@ class MetricsTest(TestCase):
         )
         assembly.save()
         room = Room(
-            conference=conf, assembly=assembly, name='Room 1',
-            room_type=Room.RoomType.BIGBLUEBUTTON, backend_status=Room.BackendStatus.ACTIVE,
-            backend_link=str(uuid4()), backend_data={'close_secret': 'asdf'},
+            conference=conf,
+            assembly=assembly,
+            name='Room 1',
+            room_type=Room.RoomType.BIGBLUEBUTTON,
+            backend_status=Room.BackendStatus.ACTIVE,
+            backend_link=str(uuid4()),
+            backend_data={'close_secret': 'asdf'},
         )
         room.save()
-        user = get_user_model()(
-            username="testuser"
-        )
+        user = get_user_model()(username='testuser')
         user.save()
 
-        member = ConferenceMember(
-            conference=conf, active_angel=False, user=user
-        )
+        member = ConferenceMember(conference=conf, active_angel=False, user=user)
         member.save()
 
         wa_room = Room(
-            conference=conf, assembly=assembly, name='Room 2',
-            room_type=Room.RoomType.WORKADVENTURE, backend_status=Room.BackendStatus.ACTIVE,
+            conference=conf,
+            assembly=assembly,
+            name='Room 2',
+            room_type=Room.RoomType.WORKADVENTURE,
+            backend_status=Room.BackendStatus.ACTIVE,
         )
         wa_room.save()
         wa_sess1 = WorkadventureSession.create_for_conference_user(conf, user, wa_room)
diff --git a/src/api/tests/permissions.py b/src/api/tests/permissions.py
index 021da5eee..92fef7d75 100644
--- a/src/api/tests/permissions.py
+++ b/src/api/tests/permissions.py
@@ -1,8 +1,11 @@
 from unittest.mock import Mock
 
+from rest_framework.permissions import BasePermission
+
 from django.contrib.auth.models import AnonymousUser
 from django.test import RequestFactory, TestCase, override_settings
-from rest_framework.permissions import BasePermission
+
+from core.models import Assembly, AssemblyMember, Badge, Conference, ConferenceMember, PlatformUser
 
 from api.permissions import (
     AssemblyPermission,
@@ -16,7 +19,6 @@ from api.permissions import (
     IsReadOnly,
     IsSuperUser,
 )
-from core.models import Assembly, AssemblyMember, Badge, Conference, ConferenceMember, PlatformUser
 
 
 class PermissionTestCase(TestCase):
diff --git a/src/api/tests/schedule.py b/src/api/tests/schedule.py
index 16ce0b4a7..24d394e40 100644
--- a/src/api/tests/schedule.py
+++ b/src/api/tests/schedule.py
@@ -1,21 +1,24 @@
-from datetime import datetime, timedelta
 import json
 import xml.etree.ElementTree as ET
+from datetime import datetime, timedelta
+
+from rest_framework.authtoken.models import Token
 from zoneinfo import ZoneInfo
 
 from django.test import TestCase
 from django.urls import reverse
 from django.utils.timezone import now
-from rest_framework.authtoken.models import Token
 
-from core.models import Assembly, ConferenceExportCache, Conference, Event, PlatformUser, Room
+from core.models import Assembly, Conference, ConferenceExportCache, Event, PlatformUser, Room
 
 
 class ScheduleTest(TestCase):
     def setUp(self):
         tz = ZoneInfo('CET')
         self.conf = Conference(
-            slug='conf', name='TestConf', is_public=True,
+            slug='conf',
+            name='TestConf',
+            is_public=True,
             start=datetime(2020, 12, 27, 1, 23, 45, tzinfo=tz),
             end=datetime(2020, 12, 30, 23, 45, 00, tzinfo=tz),
         )
@@ -110,27 +113,27 @@ class ScheduleTest(TestCase):
         event.save()
 
         update = {
-            "url": "https://fahrplan.events.ccc.de/rc3/2020/Fahrplan/events/11583.html",
-            "id": 11583,
-            "guid": str(event.id),
-            "logo": None,
-            "date": "2020-12-27T12:20:00+01:00",
-            "start": "12:20",
-            "duration": "00:30",
-            "room": "foo room",
-            "slug": "rc3-11583-rc3_eroffnung",
-            "title": "#rC3 Er\u00f6ffnung",
-            "subtitle": "",
-            "track": "Community",
-            "type": "Talk",
-            "language": "de",
-            "abstract": "Willkommen zur ersten und hoffentlich einzigen Remote Chaos Experience!",
-            "description": "",
-            "recording_license": "",
-            "do_not_record": False,
-            "persons": [{"id": 14151, "public_name": "blubbel"}],
-            "links": [],
-            "attachments": []
+            'url': 'https://fahrplan.events.ccc.de/rc3/2020/Fahrplan/events/11583.html',
+            'id': 11583,
+            'guid': str(event.id),
+            'logo': None,
+            'date': '2020-12-27T12:20:00+01:00',
+            'start': '12:20',
+            'duration': '00:30',
+            'room': 'foo room',
+            'slug': 'rc3-11583-rc3_eroffnung',
+            'title': '#rC3 Er\u00f6ffnung',
+            'subtitle': '',
+            'track': 'Community',
+            'type': 'Talk',
+            'language': 'de',
+            'abstract': 'Willkommen zur ersten und hoffentlich einzigen Remote Chaos Experience!',
+            'description': '',
+            'recording_license': '',
+            'do_not_record': False,
+            'persons': [{'id': 14151, 'public_name': 'blubbel'}],
+            'links': [],
+            'attachments': [],
         }
 
         url = reverse('api:event-schedule', kwargs={'conference': self.conf.slug, 'pk': event.pk})
@@ -146,9 +149,7 @@ class ScheduleTest(TestCase):
         self.assertEqual(timedelta(minutes=30), event.schedule_duration)
         self.assertIsNotNone(event.schedule_end)
 
-        update = {
-            "public": True
-        }
+        update = {'public': True}
 
         with self.modify_settings(API_USERS={'append': self.user.username}):
             resp = self.client.post(url, json.dumps(update), content_type='application/json', HTTP_AUTHORIZATION=f'Token {self.token.key}')
@@ -166,35 +167,35 @@ class ScheduleTest(TestCase):
         another_room.save()
 
         update = {
-            "url": "https://fahrplan.events.ccc.de/rc3/2020/Fahrplan/events/11583.html",
-            "id": 11583,
-            "guid": "d9334deb-f183-4aec-9c6c-137741f6ff73",
-            "logo": None,
-            "date": "2020-12-27T12:20:00+01:00",
-            "start": "12:20",
-            "duration": "01:30",
-            "room": "foo room",
-            "room_id": str(another_room.pk),
-            "slug": "rc3-11583-rc3_eroffnung",
-            "title": "#rC3 Er\u00f6ffnung",
-            "subtitle": "",
-            "type": "Talk",
-            "language": "de",
-            "abstract": "Willkommen zur ersten und hoffentlich einzigen Remote Chaos Experience!",
-            "description": "",
-            "recording_license": "",
-            "do_not_record": False,
-            "persons": [{"id": 14151, "public_name": "blubbel"}],
-            "links": [],
-            "attachments": [],
-            "public": False
+            'url': 'https://fahrplan.events.ccc.de/rc3/2020/Fahrplan/events/11583.html',
+            'id': 11583,
+            'guid': 'd9334deb-f183-4aec-9c6c-137741f6ff73',
+            'logo': None,
+            'date': '2020-12-27T12:20:00+01:00',
+            'start': '12:20',
+            'duration': '01:30',
+            'room': 'foo room',
+            'room_id': str(another_room.pk),
+            'slug': 'rc3-11583-rc3_eroffnung',
+            'title': '#rC3 Er\u00f6ffnung',
+            'subtitle': '',
+            'type': 'Talk',
+            'language': 'de',
+            'abstract': 'Willkommen zur ersten und hoffentlich einzigen Remote Chaos Experience!',
+            'description': '',
+            'recording_license': '',
+            'do_not_record': False,
+            'persons': [{'id': 14151, 'public_name': 'blubbel'}],
+            'links': [],
+            'attachments': [],
+            'public': False,
         }
 
         self.assertFalse(Event.objects.filter(pk=update['guid']).exists())
 
         url = reverse('api:event-schedule', kwargs={'conference': self.conf.slug, 'pk': update['guid']})
 
-        with self.modify_settings(API_USERS={'append': self.user.username}), self.assertLogs("api.views.schedule", "WARNING"):
+        with self.modify_settings(API_USERS={'append': self.user.username}), self.assertLogs('api.views.schedule', 'WARNING'):
             resp = self.client.post(url, json.dumps(update), content_type='application/json', HTTP_AUTHORIZATION=f'Token {self.token.key}')
 
         self.assertEqual(201, resp.status_code, f'Unexpected result from POST: {resp.content}')
@@ -209,10 +210,7 @@ class ScheduleTest(TestCase):
 
         self.assertEqual(another_room.pk, event.room_id, 'Expected import to prefer "room_id" over "room".')
 
-        update = {
-            "public": False,
-            "track": "Security"
-        }
+        update = {'public': False, 'track': 'Security'}
 
         with self.modify_settings(API_USERS={'append': self.user.username}):
             resp = self.client.post(url, json.dumps(update), content_type='application/json', HTTP_AUTHORIZATION=f'Token {self.token.key}')
@@ -228,35 +226,35 @@ class ScheduleTest(TestCase):
 
     def testPushInvalidTrack(self):
         update = {
-            "url": "https://fahrplan.events.ccc.de/rc3/2020/Fahrplan/events/11583.html",
-            "id": 11583,
-            "guid": "d9334deb-f183-4aec-9c6c-137741f6ff73",
-            "logo": None,
-            "date": "2020-12-27T12:20:00+01:00",
-            "start": "12:20",
-            "duration": "01:30",
-            "room": "foo room",
-            "slug": "rc3-11583-rc3_eroffnung",
-            "title": "#rC3 Er\u00f6ffnung",
-            "subtitle": "",
-            "type": "Talk",
-            "language": "de",
-            "abstract": "Willkommen zur ersten und hoffentlich einzigen Remote Chaos Experience!",
-            "description": "",
-            "recording_license": "",
-            "do_not_record": False,
-            "persons": [{"id": 14151, "public_name": "blubbel"}],
-            "links": [],
-            "attachments": [],
-            "public": False,
-            "track": "Fnord"
+            'url': 'https://fahrplan.events.ccc.de/rc3/2020/Fahrplan/events/11583.html',
+            'id': 11583,
+            'guid': 'd9334deb-f183-4aec-9c6c-137741f6ff73',
+            'logo': None,
+            'date': '2020-12-27T12:20:00+01:00',
+            'start': '12:20',
+            'duration': '01:30',
+            'room': 'foo room',
+            'slug': 'rc3-11583-rc3_eroffnung',
+            'title': '#rC3 Er\u00f6ffnung',
+            'subtitle': '',
+            'type': 'Talk',
+            'language': 'de',
+            'abstract': 'Willkommen zur ersten und hoffentlich einzigen Remote Chaos Experience!',
+            'description': '',
+            'recording_license': '',
+            'do_not_record': False,
+            'persons': [{'id': 14151, 'public_name': 'blubbel'}],
+            'links': [],
+            'attachments': [],
+            'public': False,
+            'track': 'Fnord',
         }
 
         self.assertFalse(Event.objects.filter(pk=update['guid']).exists())
 
         url = reverse('api:event-schedule', kwargs={'conference': self.conf.slug, 'pk': update['guid']})
 
-        with self.modify_settings(API_USERS={'append': self.user.username}), self.assertLogs("api.views.schedule", "WARNING"):
+        with self.modify_settings(API_USERS={'append': self.user.username}), self.assertLogs('api.views.schedule', 'WARNING'):
             resp = self.client.post(url, json.dumps(update), content_type='application/json', HTTP_AUTHORIZATION=f'Token {self.token.key}')
 
         self.assertEqual(400, resp.status_code, f'Unexpected success result from POST: {resp.content}')
@@ -282,8 +280,8 @@ class ScheduleTest(TestCase):
         for remaining_day in remaining_days:
             self.assertEqual(len(remaining_day), 0)
 
-        room, = day1
-        event1, = room
+        (room,) = day1
+        (event1,) = room
         self.assertEqual(event1.findtext('title'), ev1.name)
         self.assertEqual(event1.findtext('date'), '2020-12-27T01:23:45+01:00')
         self.assertEqual(event1.findtext('start'), '01:23')
diff --git a/src/api/tests/workadventure.py b/src/api/tests/workadventure.py
index 87d0498fb..c329b9b1e 100644
--- a/src/api/tests/workadventure.py
+++ b/src/api/tests/workadventure.py
@@ -1,9 +1,10 @@
 import json
 from pathlib import Path
 
+from rest_framework.authtoken.models import Token
+
 from django.conf import settings
 from django.test import Client, TestCase, override_settings
-from rest_framework.authtoken.models import Token
 
 from core.models import Assembly, Conference, ConferenceMember, PlatformUser, Room
 from core.sso import SSO
@@ -103,7 +104,7 @@ class WorkAdventureMapServiceTestCase(_WorkAdventureTestCase):
             self.assertTrue(all(k in x for x in endpoint_data), f'The field "{k}" is missing on at least one of returned items.')
         self.assertTrue(
             all(x['assembly_url'].startswith('http://test.localhost/') for x in endpoint_data),
-            'The assembly_url field is empty on at least one of returned items.'
+            'The assembly_url field is empty on at least one of returned items.',
         )
 
     def testPush(self):
@@ -139,7 +140,7 @@ class WorkAdventureMapServiceTestCase(_WorkAdventureTestCase):
         room5a.save()
 
         # load import data and fill in assembly and room ids
-        with Path(__file__).with_name("wa_mapservice_import.json").open() as f:
+        with Path(__file__).with_name('wa_mapservice_import.json').open() as f:
             import_json = f.read()
         import_replacements = {
             'ASSEMBLY_1_ID': self.assembly1a.id,
@@ -182,7 +183,7 @@ class WorkAdventureMapServiceTestCase(_WorkAdventureTestCase):
         room2a.refresh_from_db()
         self.assertEqual(Room.BackendStatus.ERROR, room2a.backend_status, 'the previously active room should have error state now')
         room4a.refresh_from_db()
-        self.assertEqual(Room.BackendStatus.NEW, room4a.backend_status, 'the fresh room shouldn\'t have changed status')
+        self.assertEqual(Room.BackendStatus.NEW, room4a.backend_status, "the fresh room shouldn't have changed status")
         self.assertEqual('error', room4a.director_data.get('violation', {}).get('severity', '(not set)').lower())
 
         # fetch complete list again and check what's there
@@ -191,7 +192,7 @@ class WorkAdventureMapServiceTestCase(_WorkAdventureTestCase):
         self.assertEqual('active', x['backend_status'])
 
 
-class WorkAdventureEndpointTestCase(object):  # TODO: enable again -- _WorkAdventureTestCase):
+class WorkAdventureEndpointTestCase:  # TODO: enable again -- _WorkAdventureTestCase):
     def setUp(self):
         super().setUp()
 
@@ -265,8 +266,8 @@ class WorkAdventureEndpointTestCase(object):  # TODO: enable again -- _WorkAdven
 
         self.assertEqual(
             self.room1c.reserve_capacity,
-            data["reserve_capacity"],
-            f'WA endpoint returned room_capacity {data["reserve_capacity"]} when room capacity was {self.room1c.reserve_capacity}'
+            data['reserve_capacity'],
+            f'WA endpoint returned room_capacity {data["reserve_capacity"]} when room capacity was {self.room1c.reserve_capacity}',
         )
         for k in ['room_id', 'assembly_id', 'blocked']:
             self.assertTrue(k in data, f'The field "{k}" is missing on at least one of returned items.')
@@ -300,7 +301,7 @@ class WorkAdventureEndpointTestCase(object):  # TODO: enable again -- _WorkAdven
         self.assertEqual(42, data.get('director_data', {}).get('fnord'))
 
 
-class WorkAdventureBackendTestCase(object):  # TODO: enable again -- _WorkAdventureTestCase):
+class WorkAdventureBackendTestCase:  # TODO: enable again -- _WorkAdventureTestCase):
     def setUp(self):
         super().setUp()
 
@@ -312,9 +313,11 @@ class WorkAdventureBackendTestCase(object):  # TODO: enable again -- _WorkAdvent
         self.service_token = Token(user=self.service_user)
         self.service_token.save()
         self.user_token = SSO.generate_token(self.human_user)
-        self.client.defaults.update({
-            f'HTTP_{settings.SSO_HEADER}': str(self.user_token),
-        })
+        self.client.defaults.update(
+            {
+                f'HTTP_{settings.SSO_HEADER}': str(self.user_token),
+            }
+        )
 
     def testFetch(self):
         resp = self.client.get(
diff --git a/src/api/urls.py b/src/api/urls.py
index 86996c8c6..a0e94b2e6 100644
--- a/src/api/urls.py
+++ b/src/api/urls.py
@@ -1,15 +1,13 @@
-from django.urls import path
-from rest_framework.urlpatterns import format_suffix_patterns
 from rest_framework.authtoken import views as authtoken_views
+from rest_framework.urlpatterns import format_suffix_patterns
 
-from .views import api_root
-from .views import assemblies, badges, bbb, conferencemember, conferences, events, maps, metanav, rooms, schedule, users, workadventure
+from django.urls import path
 
+from .views import api_root, assemblies, badges, bbb, conferencemember, conferences, events, maps, metanav, rooms, schedule, users, workadventure
 
 app_name = 'api'
 urlpatterns = [
     path('', api_root, name='index'),
-
     # user data/stuff
     path('me', users.profile, name='profile'),
     path('badges.zip', users.BadgeExportView.as_view(), name='badge-export'),
@@ -17,7 +15,6 @@ urlpatterns = [
     path('friends', users.friends, name='friends'),
     path('conferences', conferences.ConferenceList.as_view(), name='conference-list'),
     path('timeline', users.UserTimelineList.as_view(), name='timeline-list'),
-
     # conference-specific views
     path('c/<slug:conference>/', conferences.ConferenceDetail.as_view(), name='conference-detail'),
     path('c/<slug:conference>/metanav', metanav.MetaNavView.as_view(), name='conference-metanav'),
@@ -30,8 +27,11 @@ urlpatterns = [
     path('c/<slug:conference>/assembly/<slug:assembly>/rooms', assemblies.ConferenceAssemblyRoomList.as_view(), name='assembly-rooms'),
     path('c/<slug:conference>/assembly/<slug:assembly>/schedule', schedule.AssemblySchedule.as_view(), name='assembly-schedule'),
     path('c/<slug:conference>/assembly/<slug:assembly>/badges', badges.BadgeListCreate.as_view(), name='assembly-badges-list-create'),
-    path('c/<slug:conference>/assembly/<slug:assembly>/issue_redeem_token/<str:issuing_token>',
-         badges.BadgeTokenListCreate.as_view(), name='badge-token-create-with-token'),
+    path(
+        'c/<slug:conference>/assembly/<slug:assembly>/issue_redeem_token/<str:issuing_token>',
+        badges.BadgeTokenListCreate.as_view(),
+        name='badge-token-create-with-token',
+    ),
     path('c/<slug:conference>/assembly/<slug:assembly>/badges/<uuid:pk>', badges.BadgeTokenListCreate.as_view(), name='badge-token-list-create'),
     path('c/<slug:conference>/map/poi.json', maps.PoiExportView.as_view(), name='map-poi'),
     path('c/<slug:conference>/map/assemblies/poi.json', maps.AssembliesPoiExportView.as_view(), name='map-assemblies-poi'),
@@ -44,26 +44,20 @@ urlpatterns = [
     path('c/<slug:conference>/events', events.EventList.as_view(), name='event-list'),
     path('c/<slug:conference>/event/<uuid:pk>/', events.EventDetail.as_view(), name='event-detail'),
     path('c/<slug:conference>/event/<uuid:pk>/schedule', schedule.EventSchedule.as_view(), name='event-schedule'),
-
     # WorkAdventure integration (mapservice)
     path('c/<slug:conference>/workadventure/maps', workadventure.MapServiceView.as_view()),
     path('c/<slug:conference>/workadventure/maps/raw', workadventure.MapServiceView.as_view(), name='wa-mapservice'),
     path('c/<slug:conference>/workadventure/map/<slug:assembly>/<slug:world>', workadventure.MapDetailView.as_view(), name='wa-map-detail'),
     path('c/<slug:conference>/workadventure/map/<slug:assembly>/<slug:world>/<slug:room>', workadventure.MapDetailView.as_view(), name='wa-map-detail'),
-
     # WorkAdventure integration (exneuland)
     path('c/<slug:conference>/workadventure/maps/list', workadventure.MapBackendListView.as_view(), name='wa-backend-maps'),
     path('c/<slug:conference>/workadventure/login/<str:token>', workadventure.RegisterView.as_view(), name='wa-register'),
     path('c/<slug:conference>/workadventure/checkuser/<uuid:uid>', workadventure.UserInfoView.as_view(), name='wa-userinfo'),
-
     path('c/<slug:conference>/workadventure/redeem_badge/<str:token>', workadventure.MapBackendRedeemBadgeTokenView.as_view(), name='wa-badgeredeem'),
-
     path('c/<slug:conference>/workadventure/report/user', workadventure.MapBackendReportUserView.as_view(), name='wa-report-user'),
     path('c/<slug:conference>/workadventure/report/map', workadventure.MapBackendReportUserView.as_view(), name='wa-report-user'),
-
     # integration with other components
     path('c/<slug:conference>/is_angel/<str:username>', conferencemember.AngelView.as_view(), name='user-angel'),
-
     # BBB meeting ended callback
     path('bbb_meeting_end', bbb.MeetingEnded.as_view(), name='bbb_meeting_end'),
 ]
diff --git a/src/api/urls_sso.py b/src/api/urls_sso.py
index 294ec61ec..d0b90462b 100644
--- a/src/api/urls_sso.py
+++ b/src/api/urls_sso.py
@@ -1,21 +1,18 @@
-from django.urls import re_path
 from oauth2_provider import views
 
-from .views import sso as sso_views
+from django.urls import re_path
 
+from .views import sso as sso_views
 
-app_name = "oauth2_provider"
+app_name = 'oauth2_provider'
 urlpatterns = [
     # General Authorization Endpoint
-    re_path(r"^authorize/$", views.AuthorizationView.as_view(), name="authorize"),
-
+    re_path(r'^authorize/$', views.AuthorizationView.as_view(), name='authorize'),
     # provide (or revoke/renew) access tokens
-    re_path(r"^token/$", views.TokenView.as_view(), name="token"),
-    re_path(r"^revoke_token/$", views.RevokeTokenView.as_view(), name="revoke-token"),
-
+    re_path(r'^token/$', views.TokenView.as_view(), name='token'),
+    re_path(r'^revoke_token/$', views.RevokeTokenView.as_view(), name='revoke-token'),
     # check an existing token, needs 'introspection' scope
-    re_path(r"^introspect/$", sso_views.IntrospectTokenView.as_view(), name="introspect"),
-
+    re_path(r'^introspect/$', sso_views.IntrospectTokenView.as_view(), name='introspect'),
     # static page for OOB token transmission
-    re_path(r"^out-of-band-display-token/$", sso_views.OutOfBandDisplayTokenView.as_view(), name="out-of-band-display-token"),
+    re_path(r'^out-of-band-display-token/$', sso_views.OutOfBandDisplayTokenView.as_view(), name='out-of-band-display-token'),
 ]
diff --git a/src/api/views/__init__.py b/src/api/views/__init__.py
index d498b70e1..cf012e6c0 100644
--- a/src/api/views/__init__.py
+++ b/src/api/views/__init__.py
@@ -4,7 +4,6 @@ from rest_framework.reverse import reverse
 
 from core.models.conference import Conference
 
-
 __all__ = [
     'api_root',
 ]
diff --git a/src/api/views/assemblies.py b/src/api/views/assemblies.py
index a975e5970..98c5ec518 100644
--- a/src/api/views/assemblies.py
+++ b/src/api/views/assemblies.py
@@ -1,11 +1,11 @@
 from rest_framework import generics
 
+from core.models.assemblies import Assembly
 from core.models.events import Event
 from core.models.rooms import Room
-from core.models.assemblies import Assembly
 
-from ..serializers import AssemblySerializer, RoomSerializer, EventSerializer
-from .mixins import ConferenceSlugMixin, ConferenceSlugAssemblyMixin
+from ..serializers import AssemblySerializer, EventSerializer, RoomSerializer
+from .mixins import ConferenceSlugAssemblyMixin, ConferenceSlugMixin
 
 
 class ConferenceAssemblyList(ConferenceSlugMixin, generics.ListAPIView):
diff --git a/src/api/views/badges.py b/src/api/views/badges.py
index 2e7260b15..63ad4f1b5 100644
--- a/src/api/views/badges.py
+++ b/src/api/views/badges.py
@@ -1,8 +1,5 @@
 import logging
 
-from django.conf import settings
-from django.http import HttpResponse
-from django.shortcuts import get_object_or_404
 from rest_framework import authentication
 from rest_framework.decorators import api_view
 from rest_framework.exceptions import NotFound
@@ -10,14 +7,19 @@ from rest_framework.generics import ListCreateAPIView
 from rest_framework.response import Response
 from rest_framework.views import APIView
 
-from api.permissions import IsAssemblyManager, IsConferenceService, IsSuperUser, HasIssuingToken
-from api.serializers import BadgeSerializer, BadgeTokenSerializer
-from api.views.mixins import ConferenceSlugAssemblyMixin, ConferenceSlugMixin
+from django.conf import settings
+from django.http import HttpResponse
+from django.shortcuts import get_object_or_404
+
 from core.models.badges import Badge, BadgeToken
 from core.models.conference import Conference
 from core.models.users import PlatformUser
 from core.sso import SSO
 
+from api.permissions import HasIssuingToken, IsAssemblyManager, IsConferenceService, IsSuperUser
+from api.serializers import BadgeSerializer, BadgeTokenSerializer
+from api.views.mixins import ConferenceSlugAssemblyMixin, ConferenceSlugMixin
+
 logger = logging.getLogger(__name__)
 
 
@@ -32,10 +34,7 @@ class BadgeListCreate(ListCreateAPIView):
 
     def get_serializer_context(self):
         context = super().get_serializer_context()
-        context.update({
-            'assembly': self.kwargs.get('assembly'),
-            'conference': self.kwargs.get('conference')
-        })
+        context.update({'assembly': self.kwargs.get('assembly'), 'conference': self.kwargs.get('conference')})
         return context
 
 
@@ -55,23 +54,21 @@ class BadgeTokenListCreate(ConferenceSlugAssemblyMixin, ListCreateAPIView):
             badge = self.kwargs.get('pk')
         else:
             try:
-                badge = Badge.objects.get(issuing_token=self.kwargs.get("issuing_token"), conference__slug=self.kwargs.get("conference")).pk
+                badge = Badge.objects.get(issuing_token=self.kwargs.get('issuing_token'), conference__slug=self.kwargs.get('conference')).pk
             except Badge.DoesNotExist:
-                raise NotFound("Badge matching issuing_token not found")
+                raise NotFound('Badge matching issuing_token not found')
         context = super().get_serializer_context()
-        context.update({
-            'badge': badge
-        })
+        context.update({'badge': badge})
         return context
 
 
 @api_view(['POST'])
 def redeem_badge_token(request, conference, **kwargs):
-    redeem_token = request.POST.get('token', request.data.get("token", None))
+    redeem_token = request.POST.get('token', request.data.get('token', None))
     if redeem_token is None:
         return HttpResponse(status=400)
 
-    username = request.POST.get('username', request.data.get("username", None))
+    username = request.POST.get('username', request.data.get('username', None))
     user = get_object_or_404(PlatformUser, username=username, is_active=True)
 
     # TODO: Proper Access Checking
@@ -107,8 +104,8 @@ class RedeemBadgeMapTokenView(ConferenceSlugMixin, APIView):
         if self.target_user is None or not self.target_user.is_in_conference(self.conference):
             return HttpResponse(status=404)
 
-        redeem_token = request.POST.get('id', request.data.get("id", None))
-        assembly = request.POST.get('assembly', request.data.get("assembly", None))
+        redeem_token = request.POST.get('id', request.data.get('id', None))
+        assembly = request.POST.get('assembly', request.data.get('assembly', None))
         if assembly is None or redeem_token is None:
             return HttpResponse(status=400)
 
@@ -119,10 +116,14 @@ class RedeemBadgeMapTokenView(ConferenceSlugMixin, APIView):
         if badge_token.map_token:
             # Redeem only MAP Tokens
             created = badge_token.redeem(self.target_user, False)
-            return Response({'badge_name': f'{badge_token.badge}',
-                             'badge_image': f'{badge_token.badge.image.url}',
-                             'issuing_assembly': f'{badge_token.badge.issuing_assembly}',
-                             'user': f'{self.target_user.username}',
-                             'created': created})
+            return Response(
+                {
+                    'badge_name': f'{badge_token.badge}',
+                    'badge_image': f'{badge_token.badge.image.url}',
+                    'issuing_assembly': f'{badge_token.badge.issuing_assembly}',
+                    'user': f'{self.target_user.username}',
+                    'created': created,
+                }
+            )
         else:
             return HttpResponse(status=415)
diff --git a/src/api/views/bbb.py b/src/api/views/bbb.py
index 79876416a..08e4f1588 100644
--- a/src/api/views/bbb.py
+++ b/src/api/views/bbb.py
@@ -1,5 +1,5 @@
-from django.shortcuts import get_object_or_404
 from django.http import HttpResponse
+from django.shortcuts import get_object_or_404
 from django.views import View
 
 from core.models import Room
diff --git a/src/api/views/conferencemember.py b/src/api/views/conferencemember.py
index 328a99f1f..22eda299a 100644
--- a/src/api/views/conferencemember.py
+++ b/src/api/views/conferencemember.py
@@ -1,10 +1,11 @@
 import logging
 
-from django.conf import settings
 from rest_framework import authentication, status
 from rest_framework.response import Response
 from rest_framework.views import APIView
 
+from django.conf import settings
+
 from core.models.conference import ConferenceMember
 from core.sso import SSO
 
@@ -64,11 +65,13 @@ class WorkadventureView(ConferenceSlugMixin, APIView):
             return Response({'active': False}, status=status.HTTP_400_BAD_REQUEST)
 
         result = self.target_user.get_avatar_json(self.conference)
-        result.update({
-            'active': self.target_user.is_active,
-            'username': self.target_user.username,
-            'user_id': self.target_user.id,  # TODO: exchange for an ident after rC3
-        })
+        result.update(
+            {
+                'active': self.target_user.is_active,
+                'username': self.target_user.username,
+                'user_id': self.target_user.id,  # TODO: exchange for an ident after rC3
+            }
+        )
         return Response(result)
 
     def post(self, request, *get, format=None, **kwargs):
diff --git a/src/api/views/events.py b/src/api/views/events.py
index 74a2d7f12..a4232805b 100644
--- a/src/api/views/events.py
+++ b/src/api/views/events.py
@@ -1,7 +1,9 @@
-from core.models.events import Event
-from django.shortcuts import get_object_or_404
 from rest_framework import generics
 
+from django.shortcuts import get_object_or_404
+
+from core.models.events import Event
+
 from ..serializers import EventSerializer
 from .mixins import ConferenceSlugMixin
 
@@ -10,14 +12,12 @@ class EventList(ConferenceSlugMixin, generics.ListAPIView):
     serializer_class = EventSerializer
 
     def get_queryset(self, **kwargs):
-        return Event.objects.filter(conference=self.conference).order_by("name")
+        return Event.objects.filter(conference=self.conference).order_by('name')
 
 
 class EventDetail(ConferenceSlugMixin, generics.RetrieveAPIView):
     serializer_class = EventSerializer
 
     def get_object(self, **kwargs):
-        event_id = self.request.resolver_match.kwargs["pk"]
-        return get_object_or_404(
-            Event.objects.conference_accessible(conference=self.conference), pk=event_id
-        )
+        event_id = self.request.resolver_match.kwargs['pk']
+        return get_object_or_404(Event.objects.conference_accessible(conference=self.conference), pk=event_id)
diff --git a/src/api/views/maps.py b/src/api/views/maps.py
index ff9324649..509deb138 100644
--- a/src/api/views/maps.py
+++ b/src/api/views/maps.py
@@ -1,14 +1,15 @@
 import abc
 import json
 
-from django.contrib.gis.gdal import SpatialReference, CoordTransform
 from rest_framework.views import APIView
 
+from django.contrib.gis.gdal import CoordTransform, SpatialReference
+
 from core.models.assemblies import Assembly
-from core.models.map import MapPOI
 from core.models.conference import ConferenceExportCache
-from .mixins import ConferenceSlugMixin
+from core.models.map import MapPOI
 
+from .mixins import ConferenceSlugMixin
 
 _cts = {}  # cache of CoordTransforms (if needed)
 
@@ -31,7 +32,6 @@ class PoiExportView(ConferenceSlugMixin, APIView):
         features = []
         result = {
             'type': 'FeatureCollection',
-            # 'crs': {'type': 'name', 'properties': {'name': f'EPSG:{srid}'}},  # deprecated, not in RFC7946
             'features': features,
         }
 
@@ -80,7 +80,6 @@ class AssembliesExportView(ConferenceSlugMixin, APIView, metaclass=abc.ABCMeta):
         features = []
         result = {
             'type': 'FeatureCollection',
-            # 'crs': {'type': 'name', 'properties': {'name': f'EPSG:{srid}'}},  # deprecated, not in RFC7946
             'features': features,
         }
 
diff --git a/src/api/views/metanav.py b/src/api/views/metanav.py
index 84168b88d..491a16fb1 100644
--- a/src/api/views/metanav.py
+++ b/src/api/views/metanav.py
@@ -2,10 +2,10 @@ import logging
 
 from rest_framework.generics import ListAPIView
 
-from api.serializers import MetaNavItemSerializer
-from api.views.mixins import ConferenceSlugMixin
 from core.models.metanavi import MetaNavItem
 
+from api.serializers import MetaNavItemSerializer
+from api.views.mixins import ConferenceSlugMixin
 
 logger = logging.getLogger(__name__)
 
diff --git a/src/api/views/metrics.py b/src/api/views/metrics.py
index 3f3d06280..4a8722c79 100644
--- a/src/api/views/metrics.py
+++ b/src/api/views/metrics.py
@@ -1,7 +1,8 @@
 from django.conf import settings
 from django.http.response import HttpResponseForbidden
 from django.views.generic.base import TemplateView
-from core.models import Room, Conference, ConferenceMember, PlatformUser, Badge, UserBadge
+
+from core.models import Badge, Conference, ConferenceMember, PlatformUser, Room, UserBadge
 from core.models.assemblies import Assembly
 from core.models.events import Event
 from core.models.ticket import ConferenceMemberTicket
@@ -16,7 +17,7 @@ class MetricsView(TemplateView):
         """
         Only allow IP addresses that are listed in settings.METRICS_SERVER_IPS.
         """
-        remote_addr = request.META.get("REMOTE_ADDR")
+        remote_addr = request.META.get('REMOTE_ADDR')
         if remote_addr in settings.METRICS_SERVER_IPS or '*' in settings.METRICS_SERVER_IPS:
             return super().dispatch(request, *args, **kwargs)
         else:
@@ -39,73 +40,21 @@ class MetricsView(TemplateView):
                     '{user_type="service"}': PlatformUser.objects.filter(user_type=PlatformUser.Type.SERVICE).count(),
                     '{user_type="bot"}': PlatformUser.objects.filter(user_type=PlatformUser.Type.BOT).count(),
                     '{user_type="assembly"}': PlatformUser.objects.filter(user_type=PlatformUser.Type.ASSEMBLY).count(),
-                }
-            },
-            'hub_conference_members': {
-                'help': 'members in the conference',
-                'type': 'counter',
-                'values': {}
-            },
-            'hub_conference_members_staff': {
-                'help': 'staff count',
-                'type': 'counter',
-                'values': {}
-            },
-            'hub_conference_members_themes': {
-                'help': 'used themes by members in the conference',
-                'type': 'gauge',
-                'values': {}
-            },
-            'hub_conference_tickets': {
-                'help': 'registered tickets',
-                'type': 'counter',
-                'values': {}
-            },
-            'hub_conference_assemblies': {
-                'help': 'conference\'s assemblies',
-                'type': 'gauge',
-                'values': {}
-            },
-            'hub_conference_channels': {
-                'help': 'conference\'s channels',
-                'type': 'gauge',
-                'values': {}
-            },
-            'hub_conference_badges_public': {
-                'help': 'conference\'s badges (public)',
-                'type': 'gauge',
-                'values': {}
-            },
-            'hub_conference_badges_hidden': {
-                'help': 'conference\'s badges (non-public)',
-                'type': 'gauge',
-                'values': {}
-            },
-            'hub_conference_badges_accepted': {
-                'help': 'conference\'s badges (accepted/assigned)',
-                'type': 'gauge',
-                'values': {}
-            },
-            'hub_conference_badges_redeemed': {
-                'help': 'conference\'s badges (redeemed, but not accepted yet)',
-                'type': 'gauge',
-                'values': {}
-            },
-            'hub_conference_events': {
-                'help': 'conference\'s events',
-                'type': 'gauge',
-                'values': {}
-            },
-            'hub_conference_rooms': {
-                'help': 'conference\'s rooms',
-                'type': 'gauge',
-                'values': {}
-            },
-            'hub_conference_workadventure_sessions': {
-                'help': 'conference\'s workadventure session',
-                'type': 'gauge',
-                'values': {}
+                },
             },
+            'hub_conference_members': {'help': 'members in the conference', 'type': 'counter', 'values': {}},
+            'hub_conference_members_staff': {'help': 'staff count', 'type': 'counter', 'values': {}},
+            'hub_conference_members_themes': {'help': 'used themes by members in the conference', 'type': 'gauge', 'values': {}},
+            'hub_conference_tickets': {'help': 'registered tickets', 'type': 'counter', 'values': {}},
+            'hub_conference_assemblies': {'help': "conference's assemblies", 'type': 'gauge', 'values': {}},
+            'hub_conference_channels': {'help': "conference's channels", 'type': 'gauge', 'values': {}},
+            'hub_conference_badges_public': {'help': "conference's badges (public)", 'type': 'gauge', 'values': {}},
+            'hub_conference_badges_hidden': {'help': "conference's badges (non-public)", 'type': 'gauge', 'values': {}},
+            'hub_conference_badges_accepted': {'help': "conference's badges (accepted/assigned)", 'type': 'gauge', 'values': {}},
+            'hub_conference_badges_redeemed': {'help': "conference's badges (redeemed, but not accepted yet)", 'type': 'gauge', 'values': {}},
+            'hub_conference_events': {'help': "conference's events", 'type': 'gauge', 'values': {}},
+            'hub_conference_rooms': {'help': "conference's rooms", 'type': 'gauge', 'values': {}},
+            'hub_conference_workadventure_sessions': {'help': "conference's workadventure session", 'type': 'gauge', 'values': {}},
         }
 
         for conference in Conference.objects.filter(is_public=True):
@@ -129,56 +78,59 @@ class MetricsView(TemplateView):
 
             # hub_conference_rooms
             for room_type in Room.RoomType.values:
-                room_count = Room.objects.filter(
-                    conference=conference,
-                    room_type=Room.RoomType(room_type)
-                ).count()
+                room_count = Room.objects.filter(conference=conference, room_type=Room.RoomType(room_type)).count()
                 metrics['hub_conference_rooms']['values'][f'{{conference="{slug}",room_type="{room_type}"}}'] = room_count
 
             # hub_conference_events
             for kind in Event.Kind.values:
-                event_count = Event.objects.filter(
-                    conference=conference,
-                    kind=Event.Kind(kind)
-                ).count()
+                event_count = Event.objects.filter(conference=conference, kind=Event.Kind(kind)).count()
                 metrics['hub_conference_events']['values'][f'{{conference="{slug}",kind="{kind}"}}'] = event_count
 
             # hub_conference_badges_{public,private}
-            for visibility in {Badge.State.HIDDEN, Badge.State.PUBLIC}:
-                metrics['hub_conference_badges_' + visibility]['values'][f'{{conference="{slug}",badge_type="achievement"}}'] = \
-                    Badge.objects.filter(conference=conference, state=visibility).count()
-                metrics['hub_conference_badges_' + visibility]['values'][f'{{conference="{slug}",badge_type="sticker"}}'] = \
-                    Badge.objects.filter(conference=conference, state=visibility).count()
+            for visibility in (Badge.State.HIDDEN, Badge.State.PUBLIC):
+                metrics['hub_conference_badges_' + visibility]['values'][f'{{conference="{slug}",badge_type="achievement"}}'] = Badge.objects.filter(
+                    conference=conference, state=visibility
+                ).count()
+                metrics['hub_conference_badges_' + visibility]['values'][f'{{conference="{slug}",badge_type="sticker"}}'] = Badge.objects.filter(
+                    conference=conference, state=visibility
+                ).count()
 
             # hub_conference_badges_{accepted,redeemed}
             for userbadge_visibility in UserBadge.Visibility.values:
-                metrics['hub_conference_badges_accepted']['values'][f'{{conference="{slug}",visibility="{userbadge_visibility}"}}'] = \
-                    UserBadge.objects.filter(badge__conference=conference, visibility=userbadge_visibility, accepted_by_user=True).count()
-                metrics['hub_conference_badges_redeemed']['values'][f'{{conference="{slug}",visibility="{userbadge_visibility}"}}'] = \
-                    UserBadge.objects.filter(badge__conference=conference, visibility=userbadge_visibility, accepted_by_user=False).count()
+                metrics['hub_conference_badges_accepted']['values'][f'{{conference="{slug}",visibility="{userbadge_visibility}"}}'] = UserBadge.objects.filter(
+                    badge__conference=conference, visibility=userbadge_visibility, accepted_by_user=True
+                ).count()
+                metrics['hub_conference_badges_redeemed']['values'][f'{{conference="{slug}",visibility="{userbadge_visibility}"}}'] = UserBadge.objects.filter(
+                    badge__conference=conference, visibility=userbadge_visibility, accepted_by_user=False
+                ).count()
 
             # hub_conference_members{,_staff}
-            metrics['hub_conference_members']['values'][f'{{conference="{slug}",active_angel="true"}}'] = \
-                ConferenceMember.objects.filter(conference=conference, active_angel=True).count()
-            metrics['hub_conference_members']['values'][f'{{conference="{slug}",active_angel="false"}}'] = \
-                ConferenceMember.objects.filter(conference=conference, active_angel=False).count()
-            metrics['hub_conference_members_staff']['values'][f'{{conference="{slug}"}}'] = \
-                ConferenceMember.objects.filter(conference=conference, is_staff=True).count()
+            metrics['hub_conference_members']['values'][f'{{conference="{slug}",active_angel="true"}}'] = ConferenceMember.objects.filter(
+                conference=conference, active_angel=True
+            ).count()
+            metrics['hub_conference_members']['values'][f'{{conference="{slug}",active_angel="false"}}'] = ConferenceMember.objects.filter(
+                conference=conference, active_angel=False
+            ).count()
+            metrics['hub_conference_members_staff']['values'][f'{{conference="{slug}"}}'] = ConferenceMember.objects.filter(
+                conference=conference, is_staff=True
+            ).count()
 
             # hub_conference_members_themes
             for theme in PlatformUser.Theme.values:
-                metrics['hub_conference_members_themes']['values'][f'{{conference="{slug}",theme="{theme}"}}'] = \
-                    ConferenceMember.objects.filter(conference=conference, user__theme=theme).count()
+                metrics['hub_conference_members_themes']['values'][f'{{conference="{slug}",theme="{theme}"}}'] = ConferenceMember.objects.filter(
+                    conference=conference, user__theme=theme
+                ).count()
 
             # hub_conference_tickets
-            metrics['hub_conference_tickets']['values'][f'{{conference="{slug}"}}'] = \
-                ConferenceMemberTicket.objects.filter(conference=conference).count()
+            metrics['hub_conference_tickets']['values'][f'{{conference="{slug}"}}'] = ConferenceMemberTicket.objects.filter(conference=conference).count()
 
             # hub_conference_workadventure_sessions
-            metrics['hub_conference_workadventure_sessions']['values'][f'{{conference="{slug}",active="true"}}'] = \
-                WorkadventureSession.objects.filter(conference=conference, token=None).count()
-            metrics['hub_conference_workadventure_sessions']['values'][f'{{conference="{slug}",active="false"}}'] = \
+            metrics['hub_conference_workadventure_sessions']['values'][f'{{conference="{slug}",active="true"}}'] = WorkadventureSession.objects.filter(
+                conference=conference, token=None
+            ).count()
+            metrics['hub_conference_workadventure_sessions']['values'][f'{{conference="{slug}",active="false"}}'] = (
                 WorkadventureSession.objects.filter(conference=conference).exclude(token=None).count()
+            )
 
         context['metrics'] = metrics
 
diff --git a/src/api/views/mixins.py b/src/api/views/mixins.py
index a265b67c2..8b99f77aa 100644
--- a/src/api/views/mixins.py
+++ b/src/api/views/mixins.py
@@ -54,7 +54,7 @@ class ConferenceSlugAssemblyMixin(ConferenceSlugMixin):
             try:
                 self._assembly = Assembly.objects.accessible_by_user(self.request.user, self.conference).get(slug=assembly_slug)
             except Assembly.DoesNotExist:
-                if issuing_token := self.kwargs.get("issuing_token", None):
+                if issuing_token := self.kwargs.get('issuing_token', None):
                     try:
                         self._assembly = Badge.objects.get(issuing_token=issuing_token).issuing_assembly
                     except Badge.DoesNotExist:
diff --git a/src/api/views/rooms.py b/src/api/views/rooms.py
index 544c26cf1..b55800b38 100644
--- a/src/api/views/rooms.py
+++ b/src/api/views/rooms.py
@@ -1,12 +1,12 @@
 import logging
 
 from rest_framework import generics
+
 from core.models.rooms import Room
 
 from ..serializers import RoomSerializer
 from .mixins import ConferenceSlugMixin
 
-
 logger = logging.getLogger(__name__)
 
 
diff --git a/src/api/views/schedule.py b/src/api/views/schedule.py
index b2226234f..d2b44ea3f 100644
--- a/src/api/views/schedule.py
+++ b/src/api/views/schedule.py
@@ -1,14 +1,15 @@
 import logging
 from datetime import timedelta
 
-from django.db.models import F
-from django.http import JsonResponse, HttpResponse
-from django.utils.dateparse import parse_datetime
-from django.views.generic import View
 from rest_framework import authentication
 from rest_framework.response import Response
 from rest_framework.views import APIView
 
+from django.db.models import F
+from django.http import HttpResponse, JsonResponse
+from django.utils.dateparse import parse_datetime
+from django.views.generic import View
+
 from core.models.assemblies import Assembly
 from core.models.conference import ConferenceExportCache, ConferenceTrack
 from core.models.events import Event
@@ -58,12 +59,13 @@ class ConferenceSchedule(BaseScheduleView):
         return ''
 
     def get_events(self, **kwargs):
-        return Event.objects \
-            .conference_accessible(conference=self.conference) \
-            .exclude(schedule_duration=None) \
-            .exclude(schedule_duration__lte=timedelta(minutes=5)) \
-            .select_related('track', 'room', 'assembly') \
+        return (
+            Event.objects.conference_accessible(conference=self.conference)
+            .exclude(schedule_duration=None)
+            .exclude(schedule_duration__lte=timedelta(minutes=5))
+            .select_related('track', 'room', 'assembly')
             .order_by(F('assembly__is_official').desc(nulls_last=True), F('room__capacity').desc(nulls_last=True), 'schedule_start')
+        )
 
 
 class AssemblySchedule(BaseScheduleView):
@@ -73,11 +75,12 @@ class AssemblySchedule(BaseScheduleView):
 
     def get_events(self):
         assembly_id = self.request.resolver_match.kwargs.get('assembly')
-        return Event.objects \
-            .conference_accessible(conference=self.conference) \
-            .select_related('track', 'room') \
-            .filter(assembly_id=assembly_id) \
+        return (
+            Event.objects.conference_accessible(conference=self.conference)
+            .select_related('track', 'room')
+            .filter(assembly_id=assembly_id)
             .order_by('room__room_type', F('room__capacity').desc(nulls_last=True), 'room__name', 'schedule_start')
+        )
 
 
 class RoomSchedule(BaseScheduleView):
@@ -87,10 +90,7 @@ class RoomSchedule(BaseScheduleView):
 
     def get_events(self):
         room_id = self.request.resolver_match.kwargs.get('pk')
-        return Event.objects \
-            .conference_accessible(conference=self.conference) \
-            .filter(room_id=room_id) \
-            .order_by('schedule_start')
+        return Event.objects.conference_accessible(conference=self.conference).filter(room_id=room_id).order_by('schedule_start')
 
 
 class EventSchedule(ConferenceSlugMixin, APIView):
@@ -98,9 +98,7 @@ class EventSchedule(ConferenceSlugMixin, APIView):
     permission_classes = [IsApiUserOrReadOnly]
 
     def get(self, request, conference, pk, format=None, **kwargs):
-        event = Event.objects \
-            .accessible_by_user(conference=self.conference, user=self.request.user) \
-            .get(pk=pk)
+        event = Event.objects.accessible_by_user(conference=self.conference, user=self.request.user).get(pk=pk)
         return Response(ScheduleEncoder().encode_event(event, self.conference.timezone))
 
     def post(self, request, conference, pk, format=None, **kwargs):
diff --git a/src/api/views/sso.py b/src/api/views/sso.py
index 80c5e849d..47543108c 100644
--- a/src/api/views/sso.py
+++ b/src/api/views/sso.py
@@ -2,15 +2,14 @@ import calendar
 import json
 import logging
 
+from oauth2_provider.models import get_access_token_model
+from oauth2_provider.views import ClientProtectedScopedResourceView
+
 from django.core.exceptions import ObjectDoesNotExist
 from django.http import HttpResponse
 from django.utils.decorators import method_decorator
-from django.views.generic import TemplateView
 from django.views.decorators.csrf import csrf_exempt
-
-from oauth2_provider.models import get_access_token_model
-from oauth2_provider.views import ClientProtectedScopedResourceView
-
+from django.views.generic import TemplateView
 
 logger = logging.getLogger(__name__)
 
@@ -19,7 +18,7 @@ class OutOfBandDisplayTokenView(TemplateView):
     template_name = 'oauth2_provider/out-of-band-display-token.html'
 
 
-@method_decorator(csrf_exempt, name="dispatch")
+@method_decorator(csrf_exempt, name='dispatch')
 class IntrospectTokenView(ClientProtectedScopedResourceView):
     """
     Implements an endpoint for token introspection based
@@ -35,7 +34,7 @@ class IntrospectTokenView(ClientProtectedScopedResourceView):
     @staticmethod
     def get_token_response(query_user, token_value=None):
         try:
-            token = get_access_token_model().objects.select_related("user", "application").get(token=token_value)
+            token = get_access_token_model().objects.select_related('user', 'application').get(token=token_value)
 
         except ObjectDoesNotExist:
             return HttpResponse(content=json.dumps({'active': False}), status=401, content_type='application/json')
@@ -57,7 +56,6 @@ class IntrospectTokenView(ClientProtectedScopedResourceView):
             'active': True,
             'client_id': token.application.client_id,
             'exp': int(calendar.timegm(token.expires.timetuple())),
-            # 'scope': token.scope,
         }
 
         # prepare user details and preferences
@@ -75,7 +73,7 @@ class IntrospectTokenView(ClientProtectedScopedResourceView):
         :param kwargs:
         :return:
         """
-        return self.get_token_response(request.user, request.GET.get("token", None))
+        return self.get_token_response(request.user, request.GET.get('token', None))
 
     def post(self, request, *args, **kwargs):
         """
@@ -86,4 +84,4 @@ class IntrospectTokenView(ClientProtectedScopedResourceView):
         :param kwargs:
         :return:
         """
-        return self.get_token_response(request.user, request.POST.get("token", None))
+        return self.get_token_response(request.user, request.POST.get('token', None))
diff --git a/src/api/views/users.py b/src/api/views/users.py
index 69699b4a4..5dd37ac3e 100644
--- a/src/api/views/users.py
+++ b/src/api/views/users.py
@@ -2,17 +2,19 @@ import io
 import json
 import zipfile
 
-from django.contrib.auth.mixins import LoginRequiredMixin
-from django.http import HttpResponse
-from django.views.generic import View
 from rest_framework import generics, permissions
 from rest_framework.decorators import api_view
 from rest_framework.response import Response
 
-from ..serializers import UserTimelineEntrySerializer
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.http import HttpResponse
+from django.views.generic import View
+
 from core.models.badges import UserBadge
 from core.models.users import PlatformUser, UserTimelineEntry
 
+from ..serializers import UserTimelineEntrySerializer
+
 
 @api_view(['GET'])
 def profile(request, format=None):
@@ -21,11 +23,13 @@ def profile(request, format=None):
     if not u.is_authenticated:
         u = PlatformUser.get_anonymous_user()
 
-    return Response({
-        'authenticated': u.is_authenticated,
-        'username': u.username,
-        'flags': PlatformUser.get_user_flags(u),
-    })
+    return Response(
+        {
+            'authenticated': u.is_authenticated,
+            'username': u.username,
+            'flags': PlatformUser.get_user_flags(u),
+        }
+    )
 
 
 @api_view(['GET'])
@@ -34,14 +38,19 @@ def friends(request, format=None):
     if not u.is_authenticated:
         return Response([])
 
-    return Response([{
-        'user': c.contact.username,
-        'pending': c.pending,
-        # status may only be shown if that is public or the contact has a allowed sharing to us
-        'status': c.contact.status if c.contact.status_public or (c.reverse_contact_share_status(default=False) and not c.pending) else None,
-        'receive_dms': c.receive_dms,
-        'receive_dm_images': c.receive_dm_images,
-    } for c in u.contacts.all()])
+    return Response(
+        [
+            {
+                'user': c.contact.username,
+                'pending': c.pending,
+                # status may only be shown if that is public or the contact has a allowed sharing to us
+                'status': c.contact.status if c.contact.status_public or (c.reverse_contact_share_status(default=False) and not c.pending) else None,
+                'receive_dms': c.receive_dms,
+                'receive_dm_images': c.receive_dm_images,
+            }
+            for c in u.contacts.all()
+        ]
+    )
 
 
 @api_view(['GET'])
@@ -50,13 +59,18 @@ def badges(request, format=None):
     if not u.is_authenticated:
         return Response([])
 
-    return Response([{
-        'conference': ub.badge.conference.name,
-        'assembly': ub.badge.issuing_assembly.name,
-        'name': ub.badge.name,
-        'image_url': ub.badge.image.url,
-        'is_achievement': ub.badge.is_achievement,
-    } for ub in u.badges.filter(visibility=UserBadge.Visibility.PUBLIC)])
+    return Response(
+        [
+            {
+                'conference': ub.badge.conference.name,
+                'assembly': ub.badge.issuing_assembly.name,
+                'name': ub.badge.name,
+                'image_url': ub.badge.image.url,
+                'is_achievement': ub.badge.is_achievement,
+            }
+            for ub in u.badges.filter(visibility=UserBadge.Visibility.PUBLIC)
+        ]
+    )
 
 
 class BadgeExportView(LoginRequiredMixin, View):
diff --git a/src/api/views/workadventure.py b/src/api/views/workadventure.py
index 9e40b5ee6..57110681c 100644
--- a/src/api/views/workadventure.py
+++ b/src/api/views/workadventure.py
@@ -3,13 +3,14 @@ import logging
 from datetime import datetime
 from time import time
 
+from rest_framework.generics import get_object_or_404
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
 from django.conf import settings
 from django.http import Http404
 from django.urls import NoReverseMatch, reverse
 from django.utils import timezone
-from rest_framework.generics import get_object_or_404
-from rest_framework.response import Response
-from rest_framework.views import APIView
 
 from core.abuse import report_content
 from core.integrations import WorkAdventureIntegration
@@ -106,7 +107,9 @@ class MapServiceView(ConferenceSlugMixin, APIView):
                     skipped[room_id] = 'Non-matching assembly id, skipped.'
                     logging.warning(
                         'Got mapservice update for room id %s with non-matching assembly_id (got %s but expected %s).',
-                        room_id, a_id, room.assembly_id,
+                        room_id,
+                        a_id,
+                        room.assembly_id,
                     )
                     continue
 
@@ -126,7 +129,7 @@ class MapServiceView(ConferenceSlugMixin, APIView):
                     if has_mapinfo and is_published:
                         room.backend_status = Room.BackendStatus.ACTIVE
                         update_fields.append('backend_status')
-                        logging.info("setting WA room %s active per MapService update as we now have publishedSince>0", room_id)
+                        logging.info('setting WA room %s active per MapService update as we now have publishedSince>0', room_id)
 
                 elif room.backend_status in [Room.BackendStatus.ACTIVE]:
                     # if 'publishedSince' is not set any more, we should signal an error
@@ -164,6 +167,7 @@ class MapBackendListView(ConferenceSlugMixin, APIView):
 
     It is queried to fetch all currently active maps.
     """
+
     permission_classes = [IsConferenceService | IsSuperUser]
     required_service_classes = ['wa_backend']
 
@@ -201,15 +205,6 @@ class MapBackendReportView(ConferenceSlugMixin, APIView):
 
 class MapBackendReportUserView(MapBackendReportView):
     def post(self, request, *args, **kwargs):
-        # interface reportUser {
-        #     reportedUserUuid: string;
-        #     reportedUserIPAdress?: string;
-        #     reporterUserUuid: string;
-        #     orgSlug: string;
-        #     worldSlug: string;
-        #     roomSlug: string;
-        #     comment?: string;
-        # }
         reporting_user, data, comment, solution = self.extract_map_report_data(request)
 
         reported_session = WorkadventureSession.objects.filter(conference=self.conference, id=data.get('reportedUserUuid')).first()
@@ -231,18 +226,6 @@ class MapBackendReportUserView(MapBackendReportView):
 
 class MapBackendReportMapView(MapBackendReportView):
     def post(self, request, *args, **kwargs):
-        # interface reportMap {
-        #     mapUrl: string;
-        #     orgSlug: string;
-        #     worldSlug: string;
-        #     roomSlug: string;
-        #     reporterUserUuid: string;
-        #     comment?: string;
-        #     position: {
-        #         x: number:
-        #         y: number;
-        #     };
-        # }
         reporting_user, data, comment, solution = self.extract_map_report_data(request)
 
         report_content(
@@ -274,10 +257,13 @@ class MapBackendRedeemBadgeTokenView(ConferenceSlugMixin, APIView):
 
         created = badge_token.redeem(wa_session.user, False)
         if created:
-            return Response({
-                'msg': f'{badge_token.badge}',
-                'icon': f'{badge_token.badge.image.url}',
-            }, status=201)
+            return Response(
+                {
+                    'msg': f'{badge_token.badge}',
+                    'icon': f'{badge_token.badge.image.url}',
+                },
+                status=201,
+            )
         else:
             return Response(status=204)
 
@@ -323,7 +309,7 @@ class UserInfoView(ConferenceSlugMixin, APIView):
         try:
             wa_session = WorkadventureSession.objects.get(conference=self.conference, pk=uid)
         except WorkadventureSession.DoesNotExist:
-            raise Http404()
+            raise Http404
 
         return Response(wa_session.export_userdata())
 
@@ -354,9 +340,9 @@ class RegisterView(ConferenceSlugMixin, APIView):
             wa_session = WorkadventureSession.objects.get(conference=self.conference, token=token)
             if wa_session.token_expiry < timezone.now():
                 logger.info('WA session %s: token had expired (%s)', wa_session.id, token)
-                raise Http404()
+                raise Http404
         except WorkadventureSession.DoesNotExist:
-            raise Http404()
+            raise Http404
 
         if wa_session.additional_data is None:
             wa_session.additional_data = {}
diff --git a/src/backoffice/forms.py b/src/backoffice/forms.py
index 062b6c3f5..16576eca3 100644
--- a/src/backoffice/forms.py
+++ b/src/backoffice/forms.py
@@ -45,10 +45,14 @@ class ProfileForm(forms.ModelForm):
         fields = [
             'show_name',
             # 'description',
-            'status', 'status_public',
+            'status',
+            'status_public',
             'timezone',
-            'no_animations', 'colorblind', 'high_contrast',
-            'receive_dms', 'receive_dm_images',
+            'no_animations',
+            'colorblind',
+            'high_contrast',
+            'receive_dms',
+            'receive_dm_images',
             'autoaccept_contacts',
         ]
 
@@ -111,7 +115,7 @@ class AssemblyEditForm(TranslatedFieldsForm):
         ]
 
     def __init__(self, *args, staff_access: bool, staff_mode: bool, assembly_staff_access: bool, channel_staff_access: bool, **kwargs):
-        super(AssemblyEditForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
         # configure fields' widget customizations
         self.fields['assembly_link'].widget.attrs['placeholder'] = 'https://'
@@ -159,8 +163,8 @@ class AssemblyEditForm(TranslatedFieldsForm):
             raise ValidationError(_('Assembly__tags__splitwithcomma'))
 
         for tag in split_tags:
-            tag = tag.strip()
-            validate_slug(tag)
+            stripped_tag = tag.strip()
+            validate_slug(stripped_tag)
 
         return tags
 
@@ -367,14 +371,13 @@ class EditAssemblyRoomForm(TranslatedFieldsForm):
             self.fields['backend_status'] = forms.CharField(initial=self.instance.get_backend_status_display(), disabled=True)
         if self.instance.room_type not in Room.TYPES_WITH_CAPACITY:
             del self.fields['capacity']
-        else:
-            if self.instance.room_type == Room.RoomType.BIGBLUEBUTTON:
-                self.fields['capacity'].label = _('Room-bigbluebutton_capacity')
-                self.fields['capacity'].help_text = _('Room-bigbluebutton_capacity__help')
+        elif self.instance.room_type == Room.RoomType.BIGBLUEBUTTON:
+            self.fields['capacity'].label = _('Room-bigbluebutton_capacity')
+            self.fields['capacity'].help_text = _('Room-bigbluebutton_capacity__help')
 
-                if self.instance.backend_status == Room.BackendStatus.ACTIVE:
-                    self.fields['capacity'].help_text += ' ' + gettext('Room-bigbluebutton__activeroom')
-                    self.fields['capacity'].disabled = True
+            if self.instance.backend_status == Room.BackendStatus.ACTIVE:
+                self.fields['capacity'].help_text += ' ' + gettext('Room-bigbluebutton__activeroom')
+                self.fields['capacity'].disabled = True
 
         if self.instance.room_type in [Room.RoomType.HANGAR]:
             self.fields['name'].disabled = True
@@ -386,7 +389,7 @@ class EditAssemblyRoomForm(TranslatedFieldsForm):
 
     def clean_name(self):
         if Room.objects.filter(assembly=self.instance.assembly, name=self.cleaned_data['name']).exclude(pk=self.instance.pk).exists():
-            raise ValidationError(_("Room-name-assembly-unique"))
+            raise ValidationError(_('Room-name-assembly-unique'))
 
         # update slug to be based on the new name iff the name changed.
         if self.instance.name != self.cleaned_data['name']:
@@ -408,10 +411,10 @@ class EditAssemblyRoomForm(TranslatedFieldsForm):
         try:
             capacity = int(capacity)
         except ValueError:
-            raise ValidationError(_("Room-capacity-invalid"))
+            raise ValidationError(_('Room-capacity-invalid'))
 
         if capacity < 0:
-            raise ValidationError(_("Room-capacity-negative"))
+            raise ValidationError(_('Room-capacity-negative'))
         return capacity
 
     def save(self, commit=False):
@@ -466,24 +469,21 @@ class EventForm(TranslatedFieldsForm):
     class Meta:
         model = Event
         fields = ['room', 'name', 'language', 'banner_image', 'is_public', 'abstract', 'description', 'schedule_start', 'schedule_duration']
-        widgets = {
-            'abstract': forms.Textarea(attrs={'rows': 4}),
-            'is_public': forms.HiddenInput()
-        }
+        widgets = {'abstract': forms.Textarea(attrs={'rows': 4}), 'is_public': forms.HiddenInput()}
 
     def __init__(self, *args, conference, rooms=None, assembly=None, owner=None, create=False, publish=False, **kwargs):
         super().__init__(*args, **kwargs)
         self.conference = conference
         self.create = create
         self.sos = not assembly
-        self.event_type_name = _("SoS") if self.sos else _('event')
+        self.event_type_name = _('SoS') if self.sos else _('event')
         self.owner = owner
         self.publish = publish
         if not assembly:
             del self.fields['room']
             del self.fields['banner_image']
             del self.fields['abstract']
-            self.fields["is_public"].initial = True
+            self.fields['is_public'].initial = True
             self.assembly = self.conference.self_organized_sessions_assembly
             self.kind = Event.Kind.SELF_ORGANIZED
         else:
@@ -495,7 +495,7 @@ class EventForm(TranslatedFieldsForm):
         self.fields['schedule_start'].widget.attrs['placeholder'] = _('Event__schedule_start__placeholder')
 
     def clean(self):
-        if self.cleaned_data["schedule_duration"] is None:
+        if self.cleaned_data['schedule_duration'] is None:
             self.instance.schedule_end = None
         self.cleaned_data['is_public'] = not self.instance.is_public if self.publish else self.instance.is_public
         self.instance.conference = self.conference
@@ -527,6 +527,7 @@ class BadgeForm(TranslatedFieldsForm):
     class Meta:
         model = Badge
         fields = ['name', 'state', 'category', 'image', 'description', 'location']
+
     create = False
 
     def __init__(self, *args, conference=None, assembly=None, **kwargs) -> None:
@@ -570,10 +571,10 @@ class BadgeTokenTimeConstraintForm(forms.ModelForm):
         self.fields['date_time_range'].label = _('BadgeTokenTimeConstraint__date_time_range__label')
         self.fields['date_time_range'].widget.widgets[0].attrs['placeholder'] = _('BadgeTokenTimeConstraint__date_time_range__placeholder-start')
         # TODO: value bei Ausgabe richtig setzen, sonst wird existierender Wert nicht mehr angezeigt
-        # self.fields["date_time_range"].widget.widgets[0].input_type = "datetime-local"
+        # self.fields["date_time_range"].widget.widgets[0].input_type = "datetime-local" #  noqa: ERA001
         self.fields['date_time_range'].widget.widgets[1].attrs['placeholder'] = _('BadgeTokenTimeConstraint__date_time_range__placeholder-end')
         # TODO: dito, s.o.
-        # self.fields["date_time_range"].widget.widgets[1].input_type = "datetime-local"
+        # self.fields["date_time_range"].widget.widgets[1].input_type = "datetime-local" #  noqa: ERA001
 
 
 BadgeTokenTimeConstraintFormSet = inlineformset_factory(BadgeToken, BadgeTokenTimeConstraint, form=BadgeTokenTimeConstraintForm, extra=3)
diff --git a/src/backoffice/templatetags/c3assemblies.py b/src/backoffice/templatetags/c3assemblies.py
index f96c7c07a..15e30a999 100644
--- a/src/backoffice/templatetags/c3assemblies.py
+++ b/src/backoffice/templatetags/c3assemblies.py
@@ -1,10 +1,10 @@
-from datetime import datetime, timedelta
 import json
+from datetime import datetime, timedelta
 
 from django.conf import settings
 from django.template.defaulttags import register
-from django.utils.translation import get_language
 from django.utils.timezone import get_current_timezone
+from django.utils.translation import get_language
 
 from core.models.assemblies import Assembly
 
diff --git a/src/backoffice/tests/__init__.py b/src/backoffice/tests/__init__.py
index 89c9be319..3169e36fc 100644
--- a/src/backoffice/tests/__init__.py
+++ b/src/backoffice/tests/__init__.py
@@ -1,5 +1,5 @@
-from .base import *  # noqa: F401, F403
+from .base import *  # noqa: F401, F403, I001
 from .assemblies import *  # noqa: F401, F403
 from .auth import *  # noqa: F401, F403
 
-__all__ = '*'
+__all__ = ('*',)  # noqa: F405
diff --git a/src/backoffice/tests/assemblies.py b/src/backoffice/tests/assemblies.py
index f4740e5db..a931e439e 100644
--- a/src/backoffice/tests/assemblies.py
+++ b/src/backoffice/tests/assemblies.py
@@ -1,20 +1,19 @@
 from django.urls import reverse
 
-from backoffice.tests import BackOfficeTestCase
 from core.models import Assembly, AssemblyLink
 
+from backoffice.tests import BackOfficeTestCase
+
 
 class AssemblyListViewTest(BackOfficeTestCase):
     def setUp(self):
         super().setUp()
-        self.assemblies = [Assembly(slug=a, name=a, is_virtual=True, conference_id=self.conf.id, state_assembly=Assembly.State.ACCEPTED)
-                           for a in ('a1', 'a2', 'a3')]
+        self.assemblies = [
+            Assembly(slug=a, name=a, is_virtual=True, conference_id=self.conf.id, state_assembly=Assembly.State.ACCEPTED) for a in ('a1', 'a2', 'a3')
+        ]
         for a in self.assemblies:
             a.save()
-        self.assembly_links = [
-            AssemblyLink(a=self.assemblies[0], b=self.assemblies[1]),
-            AssemblyLink(a=self.assemblies[0], b=self.assemblies[2])
-        ]
+        self.assembly_links = [AssemblyLink(a=self.assemblies[0], b=self.assemblies[1]), AssemblyLink(a=self.assemblies[0], b=self.assemblies[2])]
         for al in self.assembly_links:
             al.save()
 
diff --git a/src/backoffice/tests/auth.py b/src/backoffice/tests/auth.py
index e6130b06b..429cdb13c 100644
--- a/src/backoffice/tests/auth.py
+++ b/src/backoffice/tests/auth.py
@@ -10,33 +10,35 @@ class PasswordResetTest(BackOfficeTestCase):
     @override_settings(LANGUAGE_CODE='en', AUTH_PASSWORD_VALIDATORS=[])
     def test_password_reset(self):
         resp = self.client.get(reverse('backoffice:password_reset'))
-        self.assertNotContains(resp, "Reset password link invalid")
+        self.assertNotContains(resp, 'Reset password link invalid')
 
     @override_settings(LANGUAGE_CODE='en', AUTH_PASSWORD_VALIDATORS=[])
     def test_invalid_password_reset_link(self):
-        from django.contrib.auth.tokens import default_token_generator
         from datetime import timedelta
+
+        from django.contrib.auth.tokens import default_token_generator
         from django.utils.encoding import force_bytes
         from django.utils.http import urlsafe_base64_encode
+
         uidb = urlsafe_base64_encode(force_bytes(self.user.pk))
         expired_token = default_token_generator._make_token_with_timestamp(
-            self.user, default_token_generator._num_seconds(default_token_generator._now() - timedelta(days=365)), secret=None)
-        expired_resp = self.client.get(reverse('backoffice:password_reset_confirm',
-                                               kwargs={'uidb64': uidb, 'token': expired_token}), follow=True)
+            self.user, default_token_generator._num_seconds(default_token_generator._now() - timedelta(days=365)), secret=None
+        )
+        expired_resp = self.client.get(reverse('backoffice:password_reset_confirm', kwargs={'uidb64': uidb, 'token': expired_token}), follow=True)
         self.assertRedirects(expired_resp, f"{reverse('backoffice:password_reset')}?retry=True")
         self.assertContains(expired_resp, 'Reset password link invalid')
 
-        invalid_session_resp = self.client.get(
-            reverse('backoffice:password_reset_confirm', kwargs={'uidb64': uidb, 'token': 'set-password'}))
+        invalid_session_resp = self.client.get(reverse('backoffice:password_reset_confirm', kwargs={'uidb64': uidb, 'token': 'set-password'}))
         self.assertRedirects(invalid_session_resp, f"{reverse('backoffice:password_reset')}?retry=True")
         self.assertNotIn(INTERNAL_RESET_SESSION_TOKEN, self.client.session)
 
     @override_settings(LANGUAGE_CODE='en', AUTH_PASSWORD_VALIDATORS=[])
     def test_PasswordResetConfirmView(self):
-        from django.utils.encoding import force_bytes
-        from django.utils.http import urlsafe_base64_encode
         from django.contrib.auth.tokens import default_token_generator
         from django.contrib.auth.views import INTERNAL_RESET_SESSION_TOKEN
+        from django.utils.encoding import force_bytes
+        from django.utils.http import urlsafe_base64_encode
+
         self.client.force_login(self.user)
         self.client.cookies = SimpleCookie()
         self.user.set_password('forgotten')
@@ -46,14 +48,15 @@ class PasswordResetTest(BackOfficeTestCase):
 
         resp = self.client.get(reverse('backoffice:password_reset_confirm', kwargs={'uidb64': uidb, 'token': token}))
         self.assertEqual(self.client.session[INTERNAL_RESET_SESSION_TOKEN], token)
-        self.assertRedirects(resp, reverse('backoffice:password_reset_confirm',
-                                           kwargs={'uidb64': uidb, 'token': 'set-password'}))
+        self.assertRedirects(resp, reverse('backoffice:password_reset_confirm', kwargs={'uidb64': uidb, 'token': 'set-password'}))
 
         resp = self.client.post(
-            reverse('backoffice:password_reset_confirm', kwargs={'uidb64': uidb, 'token': 'set-password'}), {
+            reverse('backoffice:password_reset_confirm', kwargs={'uidb64': uidb, 'token': 'set-password'}),
+            {
                 'new_password1': '4',  # chosen by fair dice roll
                 'new_password2': '4',  # guranteed to be random
-            })
+            },
+        )
         self.assertRedirects(resp, reverse('backoffice:password_reset_complete'))
 
         self.user.refresh_from_db()
diff --git a/src/backoffice/tests/base.py b/src/backoffice/tests/base.py
index b868f77e0..5d4ed2f71 100644
--- a/src/backoffice/tests/base.py
+++ b/src/backoffice/tests/base.py
@@ -1,9 +1,9 @@
 import uuid
-from datetime import datetime, UTC
+from datetime import UTC, datetime
 
-from django.test import override_settings, TestCase
+from django.test import TestCase, override_settings
 
-from core.models import Conference, PlatformUser, ConferenceMember
+from core.models import Conference, ConferenceMember, PlatformUser
 
 TEST_CONF_ID = uuid.uuid4()
 
diff --git a/src/backoffice/urls.py b/src/backoffice/urls.py
index 94e89525f..1c8857548 100644
--- a/src/backoffice/urls.py
+++ b/src/backoffice/urls.py
@@ -1,66 +1,44 @@
 from django.urls import path, re_path
-
 from django.views.i18n import set_language
 
-from .views import \
-    assemblies, \
-    assemblyteam, \
-    auth, \
-    badges, \
-    channelteam, \
-    events, \
-    map, \
-    misc, \
-    profile, \
-    schedules, \
-    users, \
-    vouchers, \
-    wiki, \
-    workadventure
-
+from .views import assemblies, assemblyteam, auth, badges, channelteam, events, map, misc, profile, schedules, users, vouchers, wiki, workadventure
 
 app_name = 'backoffice'
 urlpatterns = [
     path('', misc.IndexView.as_view(), name='index'),
-
     path('accounts/profile/', profile.ProfileView.as_view(), name='profile'),
     path('accounts/signup/', auth.RegistrationView.as_view(), name='signup'),
     path('accounts/signup/done', auth.SignupDoneView.as_view(), name='account_activation_sent'),
-
     path('accounts/change-password/', auth.PasswordChangeView.as_view(), name='password_change'),
     path('accounts/change-password/done', auth.PasswordChangeDoneView.as_view(), name='password_change_done'),
-
     path('accounts/reset-password/', auth.PasswordResetView.as_view(), name='password_reset'),
     path('accounts/reset-password/done', auth.PasswordResetDoneView.as_view(), name='password_reset_done'),
     path('accounts/reset-password/confirm/<uidb64>/<token>/', auth.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
     path('accounts/reset-password/complete', auth.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
-
-    re_path(r'^accounts/activate/(?P<uid_b64>[0-9A-Za-z_\-]+)/(?P<channel_id>\d+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,40})/$', auth.RegistrationActivationView.as_view(), name='signup_activate'),  # noqa: E501
-
+    re_path(
+        r'^accounts/activate/(?P<uid_b64>[0-9A-Za-z_\-]+)/(?P<channel_id>\d+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,40})/$',
+        auth.RegistrationActivationView.as_view(),
+        name='signup_activate',
+    ),  # noqa: E501
     path('login', auth.LoginView.as_view(), name='login'),
     path('logout', auth.LogoutView.as_view(), name='logout'),
     path('auth_debug', auth.AuthDebugView.as_view()),
-
     path('conferences', misc.ConferenceSelectionView.as_view(), name='conference_selection'),
-
     path('wiki', wiki.WikiOverviewView.as_view(), name='wiki'),
     path('wiki/namespaces', wiki.NamespaceListView.as_view(), name='wiki-namespaces'),
     path('wiki/pages', wiki.PagesView.as_view(), name='wiki-pages'),
     path('wiki/page/<uuid:pk>', wiki.PageView.as_view(), name='wiki-page-detail'),
     path('wiki/page/<uuid:pk>/delete', wiki.PageDeleteView.as_view(), name='wiki-page-delete'),
     path('wiki/page/<uuid:pk>/delete-revision', wiki.PageRevisionDeleteView.as_view(), name='wiki-page-revision-delete'),
-
     path('assemblies', assemblyteam.AssembliesView.as_view(), name='assemblies'),
     path('assemblies/list/<str:variant>', assemblyteam.AssembliesListsView.as_view(), name='assemblieslist'),
     path('channels', channelteam.ChannelsView.as_view(), name='channels'),
     path('channels/list/<str:variant>', channelteam.ChannelsListView.as_view(), name='channelslist'),
-
     path('assemblyteam/<uuid:pk>', assemblyteam.AssemblyView.as_view(), name='assemblyteam-detail'),
     path('assemblyteam/<uuid:pk>/state', assemblyteam.AssemblyEditStateView.as_view(), name='assemblyteam-editstate'),
     path('assemblyteam/<uuid:pk>/hierarchy', assemblyteam.AssemblyEditHierarchyView.as_view(), name='assemblyteam-edithierarchy'),
     path('assemblyteam/<uuid:pk>/position', assemblyteam.AssemblyEditPlacementView.as_view(), name='assemblyteam-editposition'),
     path('assemblyteam/<uuid:pk>/message', assemblyteam.AssemblyMessageView.as_view(), name='assemblyteam-message'),
-
     path('assembly/create', assemblies.CreateAssemblyView.as_view(), name='assembly-create'),
     path('assembly/<uuid:pk>', assemblies.AssemblyView.as_view(), name='assembly'),
     path('assembly/<uuid:pk>/edit', assemblies.EditAssemblyView.as_view(), name='assembly-edit'),
@@ -70,43 +48,38 @@ urlpatterns = [
     path('assembly/<uuid:pk>/members/add', assemblies.MembersAddView.as_view(), name='assembly-members-add'),
     path('assembly/<uuid:pk>/members/edit/<str:uname>', assemblies.MembersEditView.as_view(), name='assembly-members-edit'),
     path('assembly/<uuid:pk>/vouchers', assemblies.VouchersView.as_view(), name='assembly-vouchers'),
-
     path('assembly/<uuid:assembly>/auth', assemblies.AuthView.as_view(), name='assembly-auth'),
     path('assembly/<uuid:assembly>/auth/app/<int:pk>', assemblies.AuthAppView.as_view(), name='assembly-auth-app'),
     path('assembly/<uuid:assembly>/auth/new_token', assemblies.AuthGetTokenView.as_view(), name='assembly-auth-gettoken'),
-
     path('assembly/<uuid:assembly>/badges', badges.BadgesView.as_view(), name='assembly-badges'),
     path('assembly/<uuid:assembly>/badge/new', badges.CreateBadgeView.as_view(), name='assembly-create-badge'),
     path('assembly/<uuid:assembly>/badge/<uuid:pk>', badges.BadgeView.as_view(), name='assembly-badge'),
     path('assembly/<uuid:assembly>/badge/<uuid:pk>/renew_token', badges.RenewBadgeIssuingTokenView.as_view(), name='assembly-badge-renew'),
     path('assembly/<uuid:assembly>/badge/<uuid:pk>/remove', badges.RemoveBadgeView.as_view(), name='assembly-badge-remove'),
     path('assembly/<uuid:assembly>/badge/<uuid:pk>/award', badges.AwardBadgeView.as_view(), name='assembly-badge-award'),
-    path('assembly/<uuid:assembly>/badge/<uuid:badge>/redeem_token/new',
-         badges.CreateBadgeTokenView.as_view(), name='assembly-badge-create-redeem-token'),
-    path('assembly/<uuid:assembly>/badge/<uuid:badge>/redeem_token/<uuid:pk>',
-         badges.BadgeTokenEditView.as_view(), name='assembly-badge-redeem-token'),
-    path('assembly/<uuid:assembly>/badge/<uuid:badge>/redeem_token/<uuid:pk>/switch',
-         badges.BadgeTokenToggleActiveView.as_view(), name='assembly-badge-toggle-active-redeem-token'),
-
+    path('assembly/<uuid:assembly>/badge/<uuid:badge>/redeem_token/new', badges.CreateBadgeTokenView.as_view(), name='assembly-badge-create-redeem-token'),
+    path('assembly/<uuid:assembly>/badge/<uuid:badge>/redeem_token/<uuid:pk>', badges.BadgeTokenEditView.as_view(), name='assembly-badge-redeem-token'),
+    path(
+        'assembly/<uuid:assembly>/badge/<uuid:badge>/redeem_token/<uuid:pk>/switch',
+        badges.BadgeTokenToggleActiveView.as_view(),
+        name='assembly-badge-toggle-active-redeem-token',
+    ),
     path('assembly/<uuid:assembly>/new_event', events.AssemblyCreateEventView.as_view(), name='assembly-create-event'),
     path('assembly/<uuid:assembly>/events', events.AssemblyEventsView.as_view(), name='assembly-events'),
     path('assembly/<uuid:assembly>/new_room', assemblies.CreateRoomView.as_view(), name='assembly-create-room'),
     path('assembly/<uuid:assembly>/new_project', assemblies.CreateProjectView.as_view(), name='assembly-create-project'),
-
     path('assembly/<uuid:assembly>/e/<uuid:pk>/', events.AssemblyEventView.as_view(), name='assembly-event'),
     path('assembly/<uuid:assembly>/e/<uuid:pk>/remove', events.AssemblyRemoveEventView.as_view(), name='assembly-event-remove'),
     path('assembly/<uuid:assembly>/r/<uuid:pk>/', assemblies.AssemblyRoomView.as_view(), name='assembly-room'),
     path('assembly/<uuid:assembly>/r/<uuid:room>/new_link', assemblies.CreateRoomLinkView.as_view(), name='roomlink-create'),
     path('assembly/<uuid:assembly>/r/<uuid:room>/remove_link', assemblies.RemoveRoomLinkView.as_view(), name='roomlink-remove'),
     path('assembly/<uuid:assembly>/r/<uuid:room>/remove', assemblies.RemoveRoomView.as_view(), name='assembly-remove-room'),
-
     path('map/floors', map.FloorListView.as_view(), name='map-floor-list'),
     path('map/floor/new', map.FloorCreateView.as_view(), name='map-floor-create'),
     path('map/floor/<uuid:pk>', map.FloorUpdateView.as_view(), name='map-floor-edit'),
     path('map/pois', map.POIListView.as_view(), name='map-poi-list'),
     path('map/poi/new', map.POICreateView.as_view(), name='map-poi-create'),
     path('map/poi/<uuid:pk>', map.POIUpdateView.as_view(), name='map-poi-edit'),
-
     path('schedule/', schedules.SchedulesIndexView.as_view(), name='schedules'),
     path('schedule/sources', schedules.ScheduleSourcesListView.as_view(), name='schedulesource-list'),
     path('schedule/source/add', schedules.ScheduleSourcesCreateView.as_view(), name='schedulesource-add'),
@@ -115,12 +88,10 @@ urlpatterns = [
     path('schedule/source/<uuid:pk>/import', schedules.ScheduleSourcesDoImportView.as_view(), name='schedulesource-import'),
     path('schedule/source/<uuid:pk>/update', schedules.ScheduleSourcesUpdateView.as_view(), name='schedulesource-edit'),
     path('schedule/import/<int:pk>', schedules.ScheduleSourceImportDetailView.as_view(), name='schedulesourceimport-detail'),
-
     path('sos/', events.SoSIndexView.as_view(), name='sos'),
     path('sos/new', events.SoSCreateView.as_view(), name='sos-create'),
     path('sos/<uuid:pk>/', events.SosEditView.as_view(), name='sos-edit'),
     path('sos/<uuid:pk>/delete', events.SosDeleteView.as_view(), name='sos-delete'),
-
     path('wa', workadventure.IndexView.as_view(), name='workadventure'),
     path('wa/maps', workadventure.MapsView.as_view(), name='workadventure-map-list'),
     path('wa/map/<uuid:pk>', workadventure.MapView.as_view(), name='workadventure-map-detail'),
@@ -140,7 +111,6 @@ urlpatterns = [
     path('wa/texture/<uuid:pk>/assembly_remove', workadventure.TextureAssemblyRemoveView.as_view(), name='workadventure-texture-assembly-remove'),
     path('wa/texture/<uuid:pk>/user_assign', workadventure.TextureUserAssignView.as_view(), name='workadventure-texture-user-assign'),
     path('wa/texture/<uuid:pk>/user_remove', workadventure.TextureUserRemoveView.as_view(), name='workadventure-texture-user-remove'),
-
     path('users', users.UsersView.as_view(), name='users'),
     path('users/<int:pk>', users.UserView.as_view(), name='user-detail'),
     path('users/<int:pk>/block', users.UserBlockView.as_view(), name='user-block'),
@@ -151,10 +121,7 @@ urlpatterns = [
     path('users/<int:user_id>/board', users.UserBoardEntries.as_view(page=1), name='user-board'),
     path('users/<int:user_id>/board/page<int:page>', users.UserBoardEntries.as_view(), name='user-board-page'),
     path('users/<int:user_id>/board-hide/', users.UserBoardEntriesHide.as_view(), name='user-board-hide'),
-
     path('vouchers', vouchers.VouchersView.as_view(), name='vouchers'),
-
     path('set_language', set_language, name='set_language'),
-
     path('_boom', misc.BoomView.as_view()),
 ]
diff --git a/src/backoffice/views/assemblies.py b/src/backoffice/views/assemblies.py
index 6e93cd3a8..2d7099ec3 100644
--- a/src/backoffice/views/assemblies.py
+++ b/src/backoffice/views/assemblies.py
@@ -1,22 +1,25 @@
 import logging
 from datetime import date
 
+from rest_framework.authtoken.models import Token
+
 from django.conf import settings
 from django.contrib import messages
 from django.core.exceptions import PermissionDenied
-from django.http import HttpResponse, Http404
+from django.http import Http404, HttpResponse
 from django.shortcuts import get_object_or_404, redirect, render
-from django.utils.safestring import mark_safe
-from django.views.generic import TemplateView, View
-from django.views.generic.edit import CreateView, FormView, UpdateView
 from django.urls import reverse
 from django.utils import timezone
 from django.utils.html import format_html
+from django.utils.safestring import mark_safe
 from django.utils.text import format_lazy
-from django.utils.translation import get_language, gettext, gettext_lazy as _, gettext_noop
-from rest_framework.authtoken.models import Token
+from django.utils.translation import get_language, gettext, gettext_noop
+from django.utils.translation import gettext_lazy as _
+from django.views.generic import TemplateView, View
+from django.views.generic.edit import CreateView, FormView, UpdateView
 
-from core.models.assemblies import Assembly, AssemblyMember, AssemblyLink
+from core.integrations import BigBlueButton, Hangar, IntegrationError, WorkAdventure
+from core.models.assemblies import Assembly, AssemblyLink, AssemblyMember
 from core.models.conference import ConferenceExportCache
 from core.models.events import Event
 from core.models.rooms import Room, RoomLink
@@ -24,18 +27,23 @@ from core.models.sso import Application
 from core.models.tags import ConferenceTag
 from core.models.users import PlatformUser
 from core.models.voucher import Voucher
-from core.integrations import BigBlueButton, Hangar, IntegrationError, WorkAdventure
-
-from ..forms import \
-    AssemblyAddApplicationForm, AssemblyAddMemberForm, \
-    AssemblyCreateForm, AssemblyCreateRoomGenericForm, AssemblyCreateRoomBigBlueButtonForm, AssemblyCreateRoomWorkAdventureForm, AssemblyCreateRoomHangarForm, \
-    AssemblyEditForm, \
-    AssemblyMemberEditForm, \
-    CreateAssemblyRoomLinkForm, \
-    EditAssemblyRoomForm, EditAssemblyRoomHangarForm, EditAssemblyRoomWorkAdventureForm
-
-from .mixins import ConferenceMixin, AssemblyMixin
 
+from ..forms import (
+    AssemblyAddApplicationForm,
+    AssemblyAddMemberForm,
+    AssemblyCreateForm,
+    AssemblyCreateRoomBigBlueButtonForm,
+    AssemblyCreateRoomGenericForm,
+    AssemblyCreateRoomHangarForm,
+    AssemblyCreateRoomWorkAdventureForm,
+    AssemblyEditForm,
+    AssemblyMemberEditForm,
+    CreateAssemblyRoomLinkForm,
+    EditAssemblyRoomForm,
+    EditAssemblyRoomHangarForm,
+    EditAssemblyRoomWorkAdventureForm,
+)
+from .mixins import AssemblyMixin, ConferenceMixin
 
 logger = logging.getLogger(__name__)
 
@@ -225,8 +233,8 @@ class EditAssemblyView(AssemblyMixin, UpdateView):
 
         # update tags: go through supplied list of tags
         given_tags = form.cleaned_data.get('tags').split(',')
-        for tag in given_tags:
-            tag = tag.strip()  # type: str
+        for raw_tag in given_tags:
+            tag = raw_tag.strip()  # type: str
             if len(tag) == 0:
                 # skip empty ones
                 continue
@@ -282,7 +290,7 @@ class EditAssemblyView(AssemblyMixin, UpdateView):
 
             if parent_id != assembly.parent_id:
                 if parent is not None and parent.hierarchy == Assembly.Hierarchy.CLUSTER_RESTRICTED:
-                    raise PermissionDenied()
+                    raise PermissionDenied
                 if parent is not None:
                     logger.info(
                         'Assigning assembly "%(assembly_slug)s" (%(assembly_pk)s) to "%(parent_slug)s" (%(parent_pk)s) upon request by <%(user)s>.',
@@ -328,14 +336,19 @@ class EditAssemblyView(AssemblyMixin, UpdateView):
         if settings.DEBUG:
             messages.info(
                 self.request,
-                format_html('<b>DEBUG INFO:</b> <em>changes</em> on assembly "{0}"', assembly.slug) +
-                mark_safe('<table class="table table-border table-striped table-sm"><thead><tr><th>field</th><th>value</th></tr></thead><tbody>') +  # noqa:E501
-                mark_safe(''.join(format_html(
-                    '<tr><td>{0}</td><td>{1}</td></tr>',
-                    k,
-                    format_html('<s>{}</s> {}', *v) if isinstance(v, tuple) else v,
-                ) for k, v in changes.items())) +
-                mark_safe('</tbody></table>')
+                format_html('<b>DEBUG INFO:</b> <em>changes</em> on assembly "{0}"', assembly.slug)
+                + mark_safe('<table class="table table-border table-striped table-sm"><thead><tr><th>field</th><th>value</th></tr></thead><tbody>')  # noqa:E501
+                + mark_safe(
+                    ''.join(
+                        format_html(
+                            '<tr><td>{0}</td><td>{1}</td></tr>',
+                            k,
+                            format_html('<s>{}</s> {}', *v) if isinstance(v, tuple) else v,
+                        )
+                        for k, v in changes.items()
+                    )
+                )
+                + mark_safe('</tbody></table>'),
             )
         assembly.save()
 
@@ -382,11 +395,11 @@ class AssemblyEditChildrenView(AssemblyMixin, View):
         assembly = self.assembly
         # say 'Not Found' if assembly is not a cluster or clusters aren't supported at all
         if not assembly.is_cluster or not self.conference.support_clusters:
-            raise Http404()
+            raise Http404
 
         # bail out if the current user is not associated as a contact
         if not assembly.user_can_manage(self.request.user, staff_can_manage=True):
-            raise PermissionDenied()
+            raise PermissionDenied
 
         return assembly
 
@@ -445,9 +458,11 @@ class AssemblyEditChildrenView(AssemblyMixin, View):
         return redirect('backoffice:assembly-editchildren', pk=assembly.pk)
 
     def get(self, *args, **kwargs):
-        candidates_qs = Assembly.objects.accessible_by_user(conference=self.conference, user=self.request.user). \
-            filter(hierarchy=Assembly.Hierarchy.REGULAR, parent=None). \
-            exclude(state_assembly__in=[Assembly.State.NONE, Assembly.State.REJECTED, Assembly.State.HIDDEN, Assembly.State.PLANNED])
+        candidates_qs = (
+            Assembly.objects.accessible_by_user(conference=self.conference, user=self.request.user)
+            .filter(hierarchy=Assembly.Hierarchy.REGULAR, parent=None)
+            .exclude(state_assembly__in=[Assembly.State.NONE, Assembly.State.REJECTED, Assembly.State.HIDDEN, Assembly.State.PLANNED])
+        )
         candidates = list(candidates_qs.order_by('name'))
 
         context = self.get_context_data()
@@ -464,7 +479,7 @@ class AssemblyEditLinksView(AssemblyMixin, View):
 
         # bail out if the current user is not associated as a contact
         if not assembly.user_can_manage(self.request.user, staff_can_manage=True):
-            raise PermissionDenied()
+            raise PermissionDenied
 
         return assembly
 
@@ -510,17 +525,21 @@ class AssemblyEditLinksView(AssemblyMixin, View):
                     messages.success(request, gettext('assemblyedit_removedlink').format(linked_name=linkee.name))
                     logger.info(
                         'Assembly "%s" (%s): removed link to "%s" (%s) upon request by <%s>',
-                        assembly.slug, assembly.pk,
-                        linkee.slug, linkee.pk,
+                        assembly.slug,
+                        assembly.pk,
+                        linkee.slug,
+                        linkee.pk,
                         request.user.username,
                     )
 
         return redirect('backoffice:assembly-editlinks', pk=assembly.pk)
 
     def get(self, *args, **kwargs):
-        candidates_qs = Assembly.objects.accessible_by_user(conference=self.conference, user=self.request.user). \
-            filter(hierarchy=Assembly.Hierarchy.REGULAR). \
-            exclude(state_assembly__in=[Assembly.State.NONE, Assembly.State.PLANNED, Assembly.State.HIDDEN, Assembly.State.REJECTED])
+        candidates_qs = (
+            Assembly.objects.accessible_by_user(conference=self.conference, user=self.request.user)
+            .filter(hierarchy=Assembly.Hierarchy.REGULAR)
+            .exclude(state_assembly__in=[Assembly.State.NONE, Assembly.State.PLANNED, Assembly.State.HIDDEN, Assembly.State.REJECTED])
+        )
         candidates = list(candidates_qs.order_by('name'))
         context = self.get_context_data()
         context['candidates'] = candidates
@@ -627,9 +646,9 @@ class MembersView(AssemblyMixin, TemplateView):
     assembly_management = True
 
     def get_queryset(self):
-        return AssemblyMember.objects.manageable_by_user_for_assembly(user=self.request.user,
-                                                                      assembly=self.assembly
-                                                                      ).prefetch_related('member__communication_channels')
+        return AssemblyMember.objects.manageable_by_user_for_assembly(user=self.request.user, assembly=self.assembly).prefetch_related(
+            'member__communication_channels'
+        )
 
     def get_context_data(self, *args, **kwargs):
         ctx = super().get_context_data(*args, **kwargs)
@@ -638,11 +657,11 @@ class MembersView(AssemblyMixin, TemplateView):
         return ctx
 
     def post(self, *args, **kwargs):
-        for k in self.request.POST:
-            if '-' not in k:
+        for data_pair in self.request.POST:
+            if '-' not in data_pair:
                 continue
 
-            k, v = k.split('-')
+            k, v = data_pair.split('-')
 
             if k == 'hide':
                 m = self.get_queryset().select_related('member').get(member_id=int(v))
@@ -754,7 +773,7 @@ class MembersAddView(AssemblyMixin, FormView):
                     },
                 )
 
-            else:
+            else:  # noqa: PLR5501
                 if m.member == self.request.user and not self.staff_access:
                     messages.error(self.request, format_lazy(_('Assembly__members__cant_modify_self')))
                 else:
@@ -833,11 +852,14 @@ class AuthView(AssemblyMixin, FormView):
                 authorization_grant_type=data['grant_type'],
             )
             app.save()
-            messages.success(self.request, format_html(
-                '{msg}:<br><strong><code>{secret}</code></strong>',
-                msg=_('Application__newclientsecret'),
-                secret=app.client_secret,
-            ))
+            messages.success(
+                self.request,
+                format_html(
+                    '{msg}:<br><strong><code>{secret}</code></strong>',
+                    msg=_('Application__newclientsecret'),
+                    secret=app.client_secret,
+                ),
+            )
             logger.info(
                 'New OAuth2 app "%(app_name)s" created for assembly %(assembly)s by %(user)s',
                 {'app_name': app.nam, 'assembly': self.assembly, 'user': self.request.user},
@@ -885,7 +907,7 @@ class AuthAppView(AssemblyMixin, UpdateView):
     def get_object(self, *args, **kwargs):
         obj = super().get_object(*args, **kwargs)
         if obj.assembly_id != self.assembly.id:
-            raise Application.DoesNotExist()
+            raise Application.DoesNotExist
         return obj
 
     def form_valid(self, form):
@@ -921,21 +943,21 @@ class CreateRoomView(AssemblyMixin, FormView):
         if self.room_type == Room.RoomType.BIGBLUEBUTTON:
             if BigBlueButton is None or not BigBlueButton.can_create_for_assembly(self.assembly):
                 messages.error(self.request, 'BBB not available')
-                raise RoomNotAvailableError()
+                raise RoomNotAvailableError
 
             return AssemblyCreateRoomBigBlueButtonForm(self.request.POST, assembly=self.assembly)
 
         if self.room_type == Room.RoomType.WORKADVENTURE:
             if WorkAdventure is None or not WorkAdventure.can_create_for_assembly(self.assembly):
                 messages.error(self.request, 'WA not available')
-                raise RoomNotAvailableError()
+                raise RoomNotAvailableError
 
             return AssemblyCreateRoomWorkAdventureForm(self.request.POST, assembly=self.assembly)
 
         if self.room_type == Room.RoomType.HANGAR:
             if Hangar is None or not Hangar.can_create_for_assembly(self.assembly):
                 messages.error(self.request, 'Hangar not available')
-                raise RoomNotAvailableError()
+                raise RoomNotAvailableError
 
             return AssemblyCreateRoomHangarForm(self.request.POST, assembly=self.assembly)
 
@@ -945,7 +967,7 @@ class CreateRoomView(AssemblyMixin, FormView):
         else:
             logger.warning('Unexpected room_type "%s" upon creating new room for %s.', self.room_type, self.assembly)
             messages.warning(self.request, _('internal_error_please_retry'))
-            raise RoomNotAvailableError()
+            raise RoomNotAvailableError
 
     def get_context_data(self, *args, **kwargs):
         ctx = super().get_context_data(*args, **kwargs)
@@ -953,11 +975,13 @@ class CreateRoomView(AssemblyMixin, FormView):
 
         if not self.room_type:
             ctx['rooms_available'] = rooms_available = {k[0]: True for k in Room.RoomType.choices}
-            rooms_available.update({
-                'workadventure': WorkAdventure is not None and WorkAdventure.can_create_for_assembly(self.assembly),
-                'bbb': BigBlueButton is not None and BigBlueButton.can_create_for_assembly(self.assembly),
-                'hangar': Hangar is not None and Hangar.can_create_for_assembly(self.assembly),
-            })
+            rooms_available.update(
+                {
+                    'workadventure': WorkAdventure is not None and WorkAdventure.can_create_for_assembly(self.assembly),
+                    'bbb': BigBlueButton is not None and BigBlueButton.can_create_for_assembly(self.assembly),
+                    'hangar': Hangar is not None and Hangar.can_create_for_assembly(self.assembly),
+                }
+            )
             ctx['support_bbb'] = settings.INTEGRATIONS_BBB
             ctx['support_hangar'] = settings.INTEGRATIONS_HANGAR
             ctx['support_wa'] = settings.INTEGRATIONS_WORKADVENTURE
diff --git a/src/backoffice/views/assemblyteam.py b/src/backoffice/views/assemblyteam.py
index 503704a7f..55affa7ab 100644
--- a/src/backoffice/views/assemblyteam.py
+++ b/src/backoffice/views/assemblyteam.py
@@ -1,27 +1,27 @@
 import csv
-from io import StringIO
 import json
 import logging
+from io import StringIO
 
 from django.contrib import messages
-from django.contrib.gis.geos import Point, MultiPolygon, Polygon
+from django.contrib.gis.geos import MultiPolygon, Point, Polygon
 from django.contrib.postgres.aggregates import StringAgg
-from django.db.models import Q, OuterRef, Subquery
-from django.http import HttpResponse, Http404
+from django.db.models import OuterRef, Q, Subquery
+from django.http import Http404, HttpResponse
+from django.shortcuts import redirect, render
 from django.urls import reverse
-from django.shortcuts import render, redirect
 from django.utils.html import format_html
-from django.utils.translation import gettext, gettext_lazy as _
-from django.views.generic import ListView, View, DetailView
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
+from django.views.generic import DetailView, ListView, View
 
 from core.models import Room
-from core.models.assemblies import Assembly, AssemblyMember, AssemblyLink
+from core.models.assemblies import Assembly, AssemblyLink, AssemblyMember
 from core.models.conference import ConferenceExportCache
 from core.models.users import UserCommunicationChannel
 
-from .mixins import ConferenceMixin, AssemblyMixin
 from ..templatetags.c3assemblies import get_language_item
-
+from .mixins import AssemblyMixin, ConferenceMixin
 
 logger = logging.getLogger(__name__)
 
@@ -32,7 +32,7 @@ class AssemblyTeamMixin(ConferenceMixin):
     active_page = 'assemblies'
     base_view_name = 'backoffice:assemblies'
     list_view_name = 'backoffice:assemblieslist'
-    sidebar_caption = _("nav_assemblies")
+    sidebar_caption = _('nav_assemblies')
     status_field = 'state_assembly'
 
     MODES = {
@@ -53,7 +53,6 @@ class AssemblyTeamMixin(ConferenceMixin):
         lists = []
         context['sidebar'] = {
             'title': self.sidebar_caption,
-            # 'title_link': reverse(self.base_view_name),
             'items': [
                 {
                     'caption': _('Assemblys'),
@@ -69,37 +68,47 @@ class AssemblyTeamMixin(ConferenceMixin):
         }
 
         for m, (q, t) in self.MODES.items():
-            assemblies.append({
-                'mode': m,
-                'caption': t,
-                'count': self.conference.assemblies.filter(q).count(),
-                'link': reverse(self.base_view_name) + '?mode=' + m,
-            })
-
-        lists.append({
-            'caption': 'slug, name, friends & WA',
-            'link': reverse(self.list_view_name, kwargs={'variant': 'slugname'}) + '?mode=accepted',
-            'variant': 'slugname',
-        })
-
-        lists.append({
-            'caption': 'contacts',
-            'link': reverse(self.list_view_name, kwargs={'variant': 'assemblycontacts'}) + '?mode=accepted',
-            'variant': 'assemblycontacts',
-        })
-
-        lists.append({
-            'caption': 'contact mails',
-            'link': reverse(self.list_view_name, kwargs={'variant': 'contactsmail'}) + '?mode=accepted',
-            'variant': 'contactsmail',
-        })
+            assemblies.append(
+                {
+                    'mode': m,
+                    'caption': t,
+                    'count': self.conference.assemblies.filter(q).count(),
+                    'link': reverse(self.base_view_name) + '?mode=' + m,
+                }
+            )
+
+        lists.append(
+            {
+                'caption': 'slug, name, friends & WA',
+                'link': reverse(self.list_view_name, kwargs={'variant': 'slugname'}) + '?mode=accepted',
+                'variant': 'slugname',
+            }
+        )
+
+        lists.append(
+            {
+                'caption': 'contacts',
+                'link': reverse(self.list_view_name, kwargs={'variant': 'assemblycontacts'}) + '?mode=accepted',
+                'variant': 'assemblycontacts',
+            }
+        )
+
+        lists.append(
+            {
+                'caption': 'contact mails',
+                'link': reverse(self.list_view_name, kwargs={'variant': 'contactsmail'}) + '?mode=accepted',
+                'variant': 'contactsmail',
+            }
+        )
 
         if self.conference.additional_fields_schema is not None:
-            lists.append({
-                'caption': 'registration',
-                'link': reverse(self.list_view_name, kwargs={'variant': 'registration'}) + '?mode=accepted',
-                'variant': 'registration',
-            })
+            lists.append(
+                {
+                    'caption': 'registration',
+                    'link': reverse(self.list_view_name, kwargs={'variant': 'registration'}) + '?mode=accepted',
+                    'variant': 'registration',
+                }
+            )
 
         return context
 
@@ -147,9 +156,7 @@ def build_nav_from_assembly(assembly):
         }
 
         if assmbly.is_cluster:
-            me['children'] = [
-                _build_nav_from_assembly(a, way_up=False) for a in assmbly.children.order_by('slug').all()
-            ]
+            me['children'] = [_build_nav_from_assembly(a, way_up=False) for a in assmbly.children.order_by('slug').all()]
             me['expanded'] = True
 
         return [me] if way_up else me
@@ -252,43 +259,54 @@ class AssembliesListsView(AssembliesListMixin, View):
         variant_fields = None
 
         if variant == 'slugname':
+
             def wa_room_status(a):
                 try:
                     r = a.rooms.get(room_type=Room.RoomType.WORKADVENTURE)
-                    return "x" if not r.blocked else "b"
+                    return 'x' if not r.blocked else 'b'
                 except Room.DoesNotExist:
-                    return ""
+                    return ''
                 except Room.MultipleObjectsReturned:
-                    return "+"
+                    return '+'
 
             # all assemblies with slug + name + related assemblies
             qs = tuple(
-                (a.slug, a.name, a.parent, ", ".join(link.b.slug for link in AssemblyLink.objects.filter(a=a)), wa_room_status(a))
-                for a in self.get_queryset()
+                (a.slug, a.name, a.parent, ', '.join(link.b.slug for link in AssemblyLink.objects.filter(a=a)), wa_room_status(a)) for a in self.get_queryset()
             )
             variant_name = 'slug, name, parent, link, wa'
             variant_fields = [_('Assembly__slug'), _('Assembly__name'), _('Assembly__parent'), _('assembly_links'), _('Room__type-workadventure')]
 
         elif variant == 'contactsmail':
             # all assembly contacts' email addresses with duplicates removed
-            qs = UserCommunicationChannel.objects.filter(
-                channel=UserCommunicationChannel.Channel.MAIL,
-                is_verified=True,
-                user_id__in=AssemblyMember.objects.filter(assembly__in=self.get_queryset(), can_manage_assembly=True).values('member_id'),
-            ).values('address').distinct().order_by('address')
+            qs = (
+                UserCommunicationChannel.objects.filter(
+                    channel=UserCommunicationChannel.Channel.MAIL,
+                    is_verified=True,
+                    user_id__in=AssemblyMember.objects.filter(assembly__in=self.get_queryset(), can_manage_assembly=True).values('member_id'),
+                )
+                .values('address')
+                .distinct()
+                .order_by('address')
+            )
 
             variant_name = 'assembly contacts emails'
             variant_fields = [_('UserCommunicationChannel__address')]
 
         elif variant == 'assemblycontacts':
             # all assemblies with their associated contacts
-            user_mails = Subquery(UserCommunicationChannel.objects.filter(
-                user_id=OuterRef('member_id'), is_verified=True, channel=UserCommunicationChannel.Channel.MAIL,
-            ).values('address'))
-            qs = AssemblyMember.objects.filter(assembly__in=self.get_queryset(), can_manage_assembly=True) \
-                .annotate(mail=StringAgg(user_mails, '; ')) \
-                .values_list('assembly__slug', 'is_representative', 'mail') \
+            user_mails = Subquery(
+                UserCommunicationChannel.objects.filter(
+                    user_id=OuterRef('member_id'),
+                    is_verified=True,
+                    channel=UserCommunicationChannel.Channel.MAIL,
+                ).values('address')
+            )
+            qs = (
+                AssemblyMember.objects.filter(assembly__in=self.get_queryset(), can_manage_assembly=True)
+                .annotate(mail=StringAgg(user_mails, '; '))
+                .values_list('assembly__slug', 'is_representative', 'mail')
                 .order_by('assembly__slug', 'mail')
+            )
 
             variant_name = 'contact emails (assembly managers)'
             variant_fields = [_('Assembly__slug'), _('AssemblyMember__is_representative'), _('UserCommunicationChannel__address')]
@@ -431,7 +449,7 @@ class AssemblyEditHierarchyView(SingleAssemblyTeamMixin, View):
 
         # say 'Not Found' if clusters aren't supported at all
         if not self.conference.support_clusters:
-            raise Http404()
+            raise Http404
 
         return assembly
 
@@ -448,8 +466,7 @@ class AssemblyEditHierarchyView(SingleAssemblyTeamMixin, View):
         comment = request.POST.get('comment', '').strip()
 
         # don't allow changing cluster to regular if it has children
-        if value == Assembly.Hierarchy.REGULAR and \
-           assembly.is_cluster and assembly.children.exists():
+        if value == Assembly.Hierarchy.REGULAR and assembly.is_cluster and assembly.children.exists():
             messages.error(request, gettext('assemblyedit_clusterstillhaschildren'))
             return redirect(reverse('backoffice:assemblyteam-edithierarchy', kwargs={'pk': assembly.pk}) + '?value=' + value)
 
diff --git a/src/backoffice/views/auth.py b/src/backoffice/views/auth.py
index 3c5c283a1..e453fbed6 100644
--- a/src/backoffice/views/auth.py
+++ b/src/backoffice/views/auth.py
@@ -5,7 +5,7 @@ from django.http import JsonResponse
 from django.urls import reverse_lazy
 from django.views.generic import TemplateView, View
 
-from core.views import BaseLoginView, BaseRegistrationActivationView, BaseRegistrationView, BasePasswordResetView, BasePasswordResetConfirmView
+from core.views import BaseLoginView, BasePasswordResetConfirmView, BasePasswordResetView, BaseRegistrationActivationView, BaseRegistrationView
 
 from .mixins import ConferenceMixin, PasswordMixin
 
diff --git a/src/backoffice/views/badges.py b/src/backoffice/views/badges.py
index 51a4f4e83..a2121e519 100644
--- a/src/backoffice/views/badges.py
+++ b/src/backoffice/views/badges.py
@@ -90,7 +90,7 @@ class RemoveBadgeView(AssemblyMixin, DeleteView):
     def get_object(self, *args, **kwargs):
         obj = super().get_object(*args, **kwargs)
         if obj.issuing_assembly != self.assembly:
-            raise self.model.DoesNotExist()
+            raise self.model.DoesNotExist
         return obj
 
     def delete(self, *args, **kwargs):
diff --git a/src/backoffice/views/channelteam.py b/src/backoffice/views/channelteam.py
index 1d352b968..bee9d64cf 100644
--- a/src/backoffice/views/channelteam.py
+++ b/src/backoffice/views/channelteam.py
@@ -1,13 +1,14 @@
-from django.utils.translation import gettext_lazy as _
 from django.db.models import Q
+from django.utils.translation import gettext_lazy as _
 
 from core.models.assemblies import Assembly
 
-from .assemblyteam import AssembliesView, AssembliesListsView
+from .assemblyteam import AssembliesListsView, AssembliesView
 
 
 class ChannelsMixin:
-    """ sets options that configure the Assemblies views to work in Channels mode """
+    """sets options that configure the Assemblies views to work in Channels mode"""
+
     MODES = {
         'all': (Q(), _('nav_channels_all')),
         'accepted': (Q(state_channel__in=Assembly.PUBLIC_STATES), _('nav_channels_accepted')),
@@ -19,7 +20,7 @@ class ChannelsMixin:
     active_page = 'channels'
     base_view_name = 'backoffice:channels'
     list_view_name = 'backoffice:channelslist'
-    sidebar_caption = _("nav_channels")
+    sidebar_caption = _('nav_channels')
     status_field = 'state_channel'
 
 
diff --git a/src/backoffice/views/events.py b/src/backoffice/views/events.py
index b3cf344ec..8a0a693d0 100644
--- a/src/backoffice/views/events.py
+++ b/src/backoffice/views/events.py
@@ -8,12 +8,13 @@ from django.utils.translation import gettext_lazy as _
 from django.views.generic import ListView
 from django.views.generic.edit import CreateView, DeleteView, ModelFormMixin, UpdateView
 
-from backoffice.forms import EventForm
 from core.models.assemblies import Assembly
 from core.models.conference import ConferenceExportCache
 from core.models.events import Event
 from core.models.rooms import Room
 
+from backoffice.forms import EventForm
+
 from .mixins import AssemblyMixin, ConferenceMixin
 
 logger = logging.getLogger(__name__)
@@ -46,8 +47,7 @@ class EventFormMixin(ModelFormMixin):
         result = super().form_valid(form)
         if form.publish:
             messages.success(
-                self.request,
-                _('Event__published %(event_id)s %(event_type)s') % {'event_id': form.instance.id, 'event_type': self.event_type_name}
+                self.request, _('Event__published %(event_id)s %(event_type)s') % {'event_id': form.instance.id, 'event_type': self.event_type_name}
             )
         elif form.create:
             messages.success(self.request, _('Event__created %(event_id)s %(event_type)s') % {'event_id': form.instance.id, 'event_type': self.event_type_name})
@@ -66,9 +66,7 @@ class EventPublicationMixin(ModelFormMixin):
         try:
             self.get_object().clean(True)
         except ValidationError as error:
-            context.update({
-                'publication_errors': error.message_dict
-            })
+            context.update({'publication_errors': error.message_dict})
         return context
 
     def get_form_kwargs(self, *args, **kwargs):
@@ -146,7 +144,7 @@ class AssemblyRemoveEventView(AssemblyMixin, DeleteView):
     def get_object(self, *args, **kwargs):
         obj = super().get_object(*args, **kwargs)
         if obj.assembly != self.assembly:
-            raise self.model.DoesNotExist()
+            raise self.model.DoesNotExist
         return obj
 
     def form_valid(self, *args, **kwargs):
@@ -171,14 +169,16 @@ class SoSIndexView(ConferenceMixin, ListView):
         }
 
     def get_queryset(self, *args, **kwargs):
-        return Event.objects.manageable_by_user(user=self.request.user, conference=self.conference) \
-            .filter(kind=Event.Kind.SELF_ORGANIZED).select_related('owner')
+        return (
+            Event.objects.manageable_by_user(user=self.request.user, conference=self.conference).filter(kind=Event.Kind.SELF_ORGANIZED).select_related('owner')
+        )
 
 
 class SosDeleteView(ConferenceMixin, DeleteView):
     def get_queryset(self, *args, **kwargs):
-        return Event.objects.manageable_by_user(user=self.request.user, conference=self.conference) \
-            .filter(kind=Event.Kind.SELF_ORGANIZED).select_related('owner')
+        return (
+            Event.objects.manageable_by_user(user=self.request.user, conference=self.conference).filter(kind=Event.Kind.SELF_ORGANIZED).select_related('owner')
+        )
 
     def delete(self, *args, **kwargs):
         result = super().delete(*args, **kwargs)
diff --git a/src/backoffice/views/map.py b/src/backoffice/views/map.py
index f3c0ce91a..8ac2ed094 100644
--- a/src/backoffice/views/map.py
+++ b/src/backoffice/views/map.py
@@ -6,11 +6,12 @@ from django.utils.html import format_html
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import ListView
 from django.views.generic.detail import SingleObjectTemplateResponseMixin
-from django.views.generic.edit import UpdateView, CreateView, ModelFormMixin
+from django.views.generic.edit import CreateView, ModelFormMixin, UpdateView
 
 from core.models.map import MapFloor, MapPOI
-from .mixins import ConferenceMixin, guess_active_sidebar_item
+
 from ..forms import POIForm
+from .mixins import ConferenceMixin, guess_active_sidebar_item
 
 logger = logging.getLogger(__name__)
 
@@ -24,16 +25,18 @@ class MapAdminMixin(ConferenceMixin):
         ctx['active_page'] = 'map'
         ctx['uses_map'] = True
 
-        floors = [{
-            'caption': f'{floor["name"]} ({floor["index"]})',
-            'link': reverse('backoffice:map-floor-edit', kwargs={'pk': floor["pk"]}),
-        } for floor in MapFloor.objects.filter(conference=self.conference).order_by('index').values('pk', 'name', 'index')]
+        floors = [
+            {
+                'caption': f'{floor["name"]} ({floor["index"]})',
+                'link': reverse('backoffice:map-floor-edit', kwargs={'pk': floor['pk']}),
+            }
+            for floor in MapFloor.objects.filter(conference=self.conference).order_by('index').values('pk', 'name', 'index')
+        ]
 
         pois = []
         poi_count = 0
         ctx['sidebar'] = {
             'title': _('nav_map'),
-            # 'title_link': reverse(self.base_view_name),
             'items': [
                 {
                     'caption': _('MapFloors'),
@@ -51,15 +54,20 @@ class MapAdminMixin(ConferenceMixin):
 
         for poi in MapPOI.objects.filter(conference=self.conference).values('id', 'visible', 'name').iterator():
             poi_count += 1
-            pois.append({
-                'caption': poi['name'] if poi['visible'] else format_html('<s>{}</s>', poi['name']),
-                'link': reverse('backoffice:map-poi-edit', kwargs={'pk': poi['id']}),
-            })
-        pois.insert(0, {
-            'caption': format_html('<i>({all})</i>', all=_('all')),
-            'link': reverse('backoffice:map-poi-list'),
-            'count': poi_count,
-        })
+            pois.append(
+                {
+                    'caption': poi['name'] if poi['visible'] else format_html('<s>{}</s>', poi['name']),
+                    'link': reverse('backoffice:map-poi-edit', kwargs={'pk': poi['id']}),
+                }
+            )
+        pois.insert(
+            0,
+            {
+                'caption': format_html('<i>({all})</i>', all=_('all')),
+                'link': reverse('backoffice:map-poi-list'),
+                'count': poi_count,
+            },
+        )
 
         # try to guess 'active' sidebar item
         guess_active_sidebar_item(self.request, ctx['sidebar']['items'], with_query_string=False)
diff --git a/src/backoffice/views/misc.py b/src/backoffice/views/misc.py
index 788bf322e..bfdc982f0 100644
--- a/src/backoffice/views/misc.py
+++ b/src/backoffice/views/misc.py
@@ -1,6 +1,6 @@
+from django.shortcuts import redirect, render
 from django.views.generic import View
 from django.views.generic.edit import FormView
-from django.shortcuts import redirect, render
 
 from core.models import Assembly, Conference
 
@@ -35,14 +35,16 @@ class IndexView(ConferenceMixin, View):
             myassemblies = None
 
         ctx = self.get_context_data()
-        ctx.update({
-            'active_page': 'home',
-            'myassemblies': myassemblies,
-        })
+        ctx.update(
+            {
+                'active_page': 'home',
+                'myassemblies': myassemblies,
+            }
+        )
 
         return render(self.request, 'backoffice/index.html', ctx)
 
 
 class BoomView(View):
     def get(self, *args, **kwargs):
-        raise Exception("Bazinga! Testing the error handling, are we?")
+        raise Exception('Bazinga! Testing the error handling, are we?')
diff --git a/src/backoffice/views/mixins.py b/src/backoffice/views/mixins.py
index c3b37a8fe..7151e278b 100644
--- a/src/backoffice/views/mixins.py
+++ b/src/backoffice/views/mixins.py
@@ -3,7 +3,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
 from django.core.exceptions import PermissionDenied
 from django.http import HttpRequest
 from django.shortcuts import redirect
-from django.urls import reverse_lazy, reverse
+from django.urls import reverse, reverse_lazy
 from django.utils.translation import gettext_lazy as _
 
 from core.models.assemblies import Assembly
@@ -12,7 +12,6 @@ from core.models.conference import Conference, ConferenceMember
 from core.models.rooms import Room
 from core.models.sso import Application
 
-
 _UNSET = object()
 
 
@@ -66,9 +65,11 @@ class ConferenceMixin(LoginRequiredMixin, PermissionRequiredMixin):
 
     @property
     def is_channel_team(self):
-        return self.conference.support_channels and \
-               self.request.user.is_authenticated and \
-               self.request.user.has_conference_staffpermission(self.conference, 'core.channel_team')
+        return (
+            self.conference.support_channels
+            and self.request.user.is_authenticated
+            and self.request.user.has_conference_staffpermission(self.conference, 'core.channel_team')
+        )
 
     def dispatch(self, request, *args, **kwargs):
         if self.require_conference and self.conference is None:
@@ -88,35 +89,41 @@ class ConferenceMixin(LoginRequiredMixin, PermissionRequiredMixin):
 
         conference_list = Conference.objects.accessible_by_user(self.request.user).all()
 
-        context.update({
-            'LANGUAGES': settings.LANGUAGES,
-            'conference': self.conference,
-            'conferencemember': self.conferencemember,
-            'conferences': conference_list,
-        })
+        context.update(
+            {
+                'LANGUAGES': settings.LANGUAGES,
+                'conference': self.conference,
+                'conferencemember': self.conferencemember,
+                'conferences': conference_list,
+            }
+        )
 
         if self.request.user.is_authenticated:
-            context.update({
-                'has_sos': self.conferencemember is not None,
-                'has_assemblies': self.is_assembly_team,
-                'has_channel': self.is_channel_team,
-                'has_pages': self.request.user.has_conference_staffpermission(self.conference, 'core.static_pages'),
-                'has_map': self.request.user.has_conference_staffpermission(self.conference, 'core.map_edit'),
-                'has_users': self.request.user.has_conference_staffpermission(self.conference, 'core.platformusers'),
-                'has_schedules': self.request.user.has_conference_staffpermission(self.conference, 'core.scheduleadmin'),
-                'has_workadventure':
-                    settings.INTEGRATIONS_WORKADVENTURE and self.request.user.has_conference_staffpermission(self.conference, 'core.workadventure_admin'),
-            })
+            context.update(
+                {
+                    'has_sos': self.conferencemember is not None,
+                    'has_assemblies': self.is_assembly_team,
+                    'has_channel': self.is_channel_team,
+                    'has_pages': self.request.user.has_conference_staffpermission(self.conference, 'core.static_pages'),
+                    'has_map': self.request.user.has_conference_staffpermission(self.conference, 'core.map_edit'),
+                    'has_users': self.request.user.has_conference_staffpermission(self.conference, 'core.platformusers'),
+                    'has_schedules': self.request.user.has_conference_staffpermission(self.conference, 'core.scheduleadmin'),
+                    'has_workadventure': settings.INTEGRATIONS_WORKADVENTURE
+                    and self.request.user.has_conference_staffpermission(self.conference, 'core.workadventure_admin'),
+                }
+            )
         else:
-            context.update({
-                'has_assemblies': False,
-                'has_channel': False,
-                'has_pages': False,
-                'has_map': False,
-                'has_users': False,
-                'has_schedules': False,
-                'has_workadventure': False,
-            })
+            context.update(
+                {
+                    'has_assemblies': False,
+                    'has_channel': False,
+                    'has_pages': False,
+                    'has_map': False,
+                    'has_users': False,
+                    'has_schedules': False,
+                    'has_workadventure': False,
+                }
+            )
 
         return context
 
@@ -172,14 +179,16 @@ class AssemblyMixin(ConferenceMixin):
         if assembly.has_user(self.request.user):
             # don't set self._staff_mode = False here as this would prevent assembly team members to edit their own assemblies
 
-            if not self._staff_access and \
-               assembly.state_assembly in [Assembly.State.NONE, Assembly.State.HIDDEN] and \
-               assembly.state_channel in [Assembly.State.NONE, Assembly.State.HIDDEN]:
-                raise Assembly.DoesNotExist()
+            if (
+                not self._staff_access
+                and assembly.state_assembly in [Assembly.State.NONE, Assembly.State.HIDDEN]
+                and assembly.state_channel in [Assembly.State.NONE, Assembly.State.HIDDEN]
+            ):
+                raise Assembly.DoesNotExist
 
         # neither owner/manager nor assembly team? go away
         elif not self._assembly_staff_access and not self._channels_staff_access:
-            raise PermissionDenied()
+            raise PermissionDenied
 
         self._assembly = assembly
         return assembly
@@ -248,97 +257,132 @@ class AssemblyMixin(ConferenceMixin):
 
         organisation = []
         sidebar.append({'caption': _('backoffice:assembly-organisational-data'), 'children': organisation})
-        organisation.append({
-            'caption': _('backoffice:assembly-basic-data'),
-            'link': reverse('backoffice:assembly-edit', kwargs={'pk': assembly.id}),
-        })
+        organisation.append(
+            {
+                'caption': _('backoffice:assembly-basic-data'),
+                'link': reverse('backoffice:assembly-edit', kwargs={'pk': assembly.id}),
+            }
+        )
 
         if assembly.is_cluster:
-            organisation.append({
-                'caption': 'Sub-Assemblies',
-                'link': reverse('backoffice:assembly-editchildren', kwargs={'pk': assembly.id}),
-            })
-
-        organisation.append({
-            'caption': 'Links',
-            'link': reverse('backoffice:assembly-editlinks', kwargs={'pk': assembly.id}),
-        })
-
-        organisation.append({
-            'caption': _('Assembly__members'),
-            'link': reverse('backoffice:assembly-members', kwargs={'pk': assembly.id}),
-            'count': assembly.members.count(),
-        })
+            organisation.append(
+                {
+                    'caption': 'Sub-Assemblies',
+                    'link': reverse('backoffice:assembly-editchildren', kwargs={'pk': assembly.id}),
+                }
+            )
+
+        organisation.append(
+            {
+                'caption': 'Links',
+                'link': reverse('backoffice:assembly-editlinks', kwargs={'pk': assembly.id}),
+            }
+        )
+
+        organisation.append(
+            {
+                'caption': _('Assembly__members'),
+                'link': reverse('backoffice:assembly-members', kwargs={'pk': assembly.id}),
+                'count': assembly.members.count(),
+            }
+        )
 
         if can_manage:
-            apps = [{
-                'caption': f'{app["name"]}',
-                'link': reverse('backoffice:assembly-auth-app', kwargs={'assembly': assembly.id, 'pk': app['id']}),
-                'classes': [],
-            } for app in Application.objects.filter(assembly=self.assembly).values('id', 'name')]
-            sidebar.append({
-                'caption': _('Assembly__authentication'),
-                'children': apps,
-                'count': len(apps),
-                'link': reverse('backoffice:assembly-auth', kwargs={'assembly': assembly.id}),
-            })
+            apps = [
+                {
+                    'caption': f'{app["name"]}',
+                    'link': reverse('backoffice:assembly-auth-app', kwargs={'assembly': assembly.id, 'pk': app['id']}),
+                    'classes': [],
+                }
+                for app in Application.objects.filter(assembly=self.assembly).values('id', 'name')
+            ]
+            sidebar.append(
+                {
+                    'caption': _('Assembly__authentication'),
+                    'children': apps,
+                    'count': len(apps),
+                    'link': reverse('backoffice:assembly-auth', kwargs={'assembly': assembly.id}),
+                }
+            )
 
             if (voucher_count := assembly.get_voucher_count(with_always_public=True)) is not None:
-                organisation.append({
-                    'caption': _('Vouchers'),
-                    'count': voucher_count,
-                    'link': reverse('backoffice:assembly-vouchers', kwargs={'pk': assembly.id}),
-                })
-
-        rooms = [{
-            'caption': f'{room["name"]} ({room["room_type"]})',
-            'link': reverse('backoffice:assembly-room', kwargs={'assembly': assembly.id, 'pk': room['id']}),
-            'classes': ['blocked'] if room['blocked'] else [],
-        } for room in assembly.rooms.exclude(room_type=Room.RoomType.PROJECT).values('id', 'name', 'room_type', 'blocked')]
-        sidebar.append({
-            'caption': _('backoffice:assembly-rooms'),
-            'children': rooms,
-            'count': len(rooms),
-            'add_link': reverse('backoffice:assembly-create-room', kwargs={'assembly': assembly.id}) if can_manage else None,
-        })
-
-        projects = [{
-            'caption': prj["name"],
-            'link': reverse('backoffice:assembly-room', kwargs={'assembly': assembly.id, 'pk': prj['id']}),
-            'classes': ['blocked'] if prj['blocked'] else [],
-        } for prj in assembly.rooms.filter(room_type=Room.RoomType.PROJECT).values('id', 'name', 'blocked')]
-        sidebar.append({
-            'caption': _('backoffice:assembly-projects'),
-            'children': projects,
-            'count': len(projects),
-            'add_link': reverse('backoffice:assembly-create-project', kwargs={'assembly': assembly.id}) if can_manage else None,
-        })
-
-        events = [{
-            'caption': ev["name"],
-            'link': reverse('backoffice:assembly-event', kwargs={'assembly': assembly.id, 'pk': ev['id']}),
-            'classes': ['blocked'] if not ev['is_public'] else [],
-        } for ev in assembly.events.values('id', 'name', 'is_public')]
-        sidebar.append({
-            'caption': 'Events',
-            'link': reverse('backoffice:assembly-events', kwargs={'assembly': assembly.id}),
-            'children': events,
-            'count': len(events),
-            'add_link': reverse('backoffice:assembly-create-event', kwargs={'assembly': assembly.id}) if can_manage else None,
-        })
-
-        badges = [{
-            'caption': b["name"],
-            'link': reverse('backoffice:assembly-badge', kwargs={'assembly': assembly.id, 'pk': b['id']}),
-            'classes': ['blocked'] if b['state'] == Badge.State.PLANNED else [],
-        } for b in assembly.badges.values('id', 'name', 'state')]
-        sidebar.append({
-            'caption': 'Badges',
-            'link': reverse('backoffice:assembly-badges', kwargs={'assembly': assembly.id}),
-            'children': badges,
-            'count': len(badges),
-            'add_link': reverse('backoffice:assembly-create-badge', kwargs={'assembly': assembly.id}) if can_manage else None,
-        })
+                organisation.append(
+                    {
+                        'caption': _('Vouchers'),
+                        'count': voucher_count,
+                        'link': reverse('backoffice:assembly-vouchers', kwargs={'pk': assembly.id}),
+                    }
+                )
+
+        rooms = [
+            {
+                'caption': f'{room["name"]} ({room["room_type"]})',
+                'link': reverse('backoffice:assembly-room', kwargs={'assembly': assembly.id, 'pk': room['id']}),
+                'classes': ['blocked'] if room['blocked'] else [],
+            }
+            for room in assembly.rooms.exclude(room_type=Room.RoomType.PROJECT).values('id', 'name', 'room_type', 'blocked')
+        ]
+        sidebar.append(
+            {
+                'caption': _('backoffice:assembly-rooms'),
+                'children': rooms,
+                'count': len(rooms),
+                'add_link': reverse('backoffice:assembly-create-room', kwargs={'assembly': assembly.id}) if can_manage else None,
+            }
+        )
+
+        projects = [
+            {
+                'caption': prj['name'],
+                'link': reverse('backoffice:assembly-room', kwargs={'assembly': assembly.id, 'pk': prj['id']}),
+                'classes': ['blocked'] if prj['blocked'] else [],
+            }
+            for prj in assembly.rooms.filter(room_type=Room.RoomType.PROJECT).values('id', 'name', 'blocked')
+        ]
+        sidebar.append(
+            {
+                'caption': _('backoffice:assembly-projects'),
+                'children': projects,
+                'count': len(projects),
+                'add_link': reverse('backoffice:assembly-create-project', kwargs={'assembly': assembly.id}) if can_manage else None,
+            }
+        )
+
+        events = [
+            {
+                'caption': ev['name'],
+                'link': reverse('backoffice:assembly-event', kwargs={'assembly': assembly.id, 'pk': ev['id']}),
+                'classes': ['blocked'] if not ev['is_public'] else [],
+            }
+            for ev in assembly.events.values('id', 'name', 'is_public')
+        ]
+        sidebar.append(
+            {
+                'caption': 'Events',
+                'link': reverse('backoffice:assembly-events', kwargs={'assembly': assembly.id}),
+                'children': events,
+                'count': len(events),
+                'add_link': reverse('backoffice:assembly-create-event', kwargs={'assembly': assembly.id}) if can_manage else None,
+            }
+        )
+
+        badges = [
+            {
+                'caption': b['name'],
+                'link': reverse('backoffice:assembly-badge', kwargs={'assembly': assembly.id, 'pk': b['id']}),
+                'classes': ['blocked'] if b['state'] == Badge.State.PLANNED else [],
+            }
+            for b in assembly.badges.values('id', 'name', 'state')
+        ]
+        sidebar.append(
+            {
+                'caption': 'Badges',
+                'link': reverse('backoffice:assembly-badges', kwargs={'assembly': assembly.id}),
+                'children': badges,
+                'count': len(badges),
+                'add_link': reverse('backoffice:assembly-create-badge', kwargs={'assembly': assembly.id}) if can_manage else None,
+            }
+        )
 
         # try to guess 'active' sidebar item
         guess_active_sidebar_item(self.request, context['sidebar']['items'])
@@ -372,7 +416,7 @@ def guess_active_sidebar_item(request: HttpRequest, sidebar_items: dict, with_qu
             x['children'] = None
 
 
-class PasswordMixin():
+class PasswordMixin:
     def get_context_data(self, *args, **kwargs):
         try:
             context = super().get_context_data(*args, **kwargs)
@@ -380,8 +424,10 @@ class PasswordMixin():
             # super() does not have .get_context_data(), e.g. if it's a plain View
             context = {}
 
-        context.update({
-            'LANGUAGES': settings.LANGUAGES,
-        })
+        context.update(
+            {
+                'LANGUAGES': settings.LANGUAGES,
+            }
+        )
 
         return context
diff --git a/src/backoffice/views/schedules.py b/src/backoffice/views/schedules.py
index f000fc1d3..ed92c9481 100644
--- a/src/backoffice/views/schedules.py
+++ b/src/backoffice/views/schedules.py
@@ -2,7 +2,7 @@ from django.contrib import messages
 from django.shortcuts import redirect
 from django.urls import reverse
 from django.views import View
-from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView, TemplateView
+from django.views.generic import CreateView, DeleteView, DetailView, ListView, TemplateView, UpdateView
 from django.views.generic.detail import SingleObjectMixin
 
 from core.models import ScheduleSource, ScheduleSourceImport
@@ -62,16 +62,16 @@ class ScheduleSourcesDoImportView(ScheduleAdminMixin, SingleObjectMixin, View):
         if src.assembly is not None:
             messages.info(request, f"+ ScheduleSourceImport {job.pk} for '{src.assembly.slug}' ({src.pk})")
         else:
-            messages.info(request, f"+ ScheduleSourceImport {job.pk} for wildcard assembly ({src.pk})")
+            messages.info(request, f'+ ScheduleSourceImport {job.pk} for wildcard assembly ({src.pk})')
 
         try:
             result = job.do_import()
             if result:
-                messages.success(request, f"ScheduleSourceImport {job.pk} succeeded")
+                messages.success(request, f'ScheduleSourceImport {job.pk} succeeded')
             else:
-                messages.warning(request, f"ScheduleSourceImport {job.pk} failed")
+                messages.warning(request, f'ScheduleSourceImport {job.pk} failed')
         except Exception as err:
-            messages.error(request, f"ScheduleSourceImport {job.pk} threw exception: {err}")
+            messages.error(request, f'ScheduleSourceImport {job.pk} threw exception: {err}')
 
         return redirect('backoffice:schedulesourceimport-detail', pk=str(job.pk))
 
diff --git a/src/backoffice/views/users.py b/src/backoffice/views/users.py
index c0423c445..02e6976dd 100644
--- a/src/backoffice/views/users.py
+++ b/src/backoffice/views/users.py
@@ -1,31 +1,29 @@
 import logging
 
+from oauth2_provider.models import AccessToken
+
 from django.conf import settings
 from django.contrib import messages
 from django.contrib.sessions.exceptions import SuspiciousSession
 from django.contrib.sessions.models import Session
-from django.core.mail import EmailMessage
 from django.core.exceptions import ObjectDoesNotExist
+from django.core.mail import EmailMessage
 from django.db.models import F
 from django.shortcuts import redirect, render
 from django.urls import reverse
 from django.utils.translation import gettext
 from django.views import View
-from django.views.generic import DetailView, TemplateView, ListView
+from django.views.generic import DetailView, ListView, TemplateView
 from django.views.generic.detail import SingleObjectMixin
 
-from oauth2_provider.models import AccessToken
-
 from core.integrations import WorkAdventureIntegration
 from core.models import BulletinBoardEntry
 from core.models.conference import ConferenceMember
 from core.models.messages import DirectMessage
 from core.models.users import PlatformUser, UserCommunicationChannel
 
-
 from .mixins import ConferenceMixin
 
-
 logger = logging.getLogger(__name__)
 MAX_ROWS = 42
 
@@ -52,13 +50,14 @@ class UsersView(ConferenceMixin, TemplateView):
             qs = qs.filter(conferences__conference=self.conference)
         qs_base = qs.annotate(is_conference_staff=F('conferences__is_staff'), active_conference_angel=F('conferences__active_angel'))
         if '@' in search_term:
-            direct_matches += list(qs.filter(communication_channels__channel=UserCommunicationChannel.Channel.MAIL,
-                                             communication_channels__address=search_term))
+            direct_matches += list(
+                qs.filter(communication_channels__channel=UserCommunicationChannel.Channel.MAIL, communication_channels__address=search_term)
+            )
         direct_matches += list(qs.filter(username__iexact=search_term))
 
         qs = qs_base.filter(username__icontains=search_term).exclude(pk__in=[direct_match.pk for direct_match in direct_matches])
         qs = qs.order_by('username')
-        results = list(direct_matches) + list(qs[:MAX_ROWS + 1])
+        results = list(direct_matches) + list(qs[: MAX_ROWS + 1])
         more_results = len(results) > MAX_ROWS
 
         if more_results:
@@ -242,7 +241,7 @@ class UserRenameView(ConferenceMixin, DetailView):
         user = self.get_object()
 
         new_name = self.request.POST.get('new_name', '').strip()
-        if new_name == '' or new_name == user.username:
+        if new_name in ('', user.username):
             return self.get(*args, **kwargs)
 
         if PlatformUser.objects.filter(username=new_name):
@@ -323,7 +322,7 @@ class UserMailView(ConferenceMixin, DetailView):
             messages.info(self.request, f'Sent mail to "{user.username}" with subject "{subject}" ({len(addresses)} recipients(s)).')
 
         except ObjectDoesNotExist:
-            messages.error(self.request, "Mail not send because channel not marked to be used for notifications or not verified!")
+            messages.error(self.request, 'Mail not send because channel not marked to be used for notifications or not verified!')
 
         except Exception as e:
             messages.error(self.request, e)
@@ -355,5 +354,5 @@ class UserBoardEntriesHide(ConferenceMixin, View):
     def post(self, request, user_id, **kwargs):
         BulletinBoardEntry.objects.filter(owner=user_id, pk=request.POST['pk']).update(hidden=True)
 
-        messages.success(request, gettext("BulletinBoardEntry--deleted"))
+        messages.success(request, gettext('BulletinBoardEntry--deleted'))
         return redirect(reverse('backoffice:user-board', kwargs={'user_id': user_id}))
diff --git a/src/backoffice/views/utils.py b/src/backoffice/views/utils.py
index af7afe2fb..08b16493d 100644
--- a/src/backoffice/views/utils.py
+++ b/src/backoffice/views/utils.py
@@ -25,11 +25,13 @@ def extend_context(request, context, conference=None):
         conference = get_conference(request)
     conference_list = Conference.objects.accessible_by_user(request.user).all()
 
-    context.update({
-        'LANGUAGES': settings.LANGUAGES,
-        'conference': conference,
-        'conferences': conference_list,
-        'has_pages': request.user.has_pages(conference) if request.user.is_authenticated else False,
-    })
+    context.update(
+        {
+            'LANGUAGES': settings.LANGUAGES,
+            'conference': conference,
+            'conferences': conference_list,
+            'has_pages': request.user.has_pages(conference) if request.user.is_authenticated else False,
+        }
+    )
 
     return context
diff --git a/src/backoffice/views/vouchers.py b/src/backoffice/views/vouchers.py
index c49edc477..7a184b693 100644
--- a/src/backoffice/views/vouchers.py
+++ b/src/backoffice/views/vouchers.py
@@ -8,7 +8,6 @@ from core.models import VoucherEntry
 
 from .mixins import ConferenceMixin
 
-
 logger = logging.getLogger(__name__)
 MAX_ROWS = 42
 
@@ -30,7 +29,7 @@ class VouchersView(ConferenceMixin, TemplateView):
 
         direct_matches = []
         qs = VoucherEntry.objects.filter(content__icontains=search_term)
-        results = list(direct_matches) + list(qs[:MAX_ROWS + 1])
+        results = list(direct_matches) + list(qs[: MAX_ROWS + 1])
         more_results = len(results) > MAX_ROWS
 
         if more_results:
diff --git a/src/backoffice/views/wiki.py b/src/backoffice/views/wiki.py
index ed9dda4fd..8d0aa7268 100644
--- a/src/backoffice/views/wiki.py
+++ b/src/backoffice/views/wiki.py
@@ -1,17 +1,17 @@
-from datetime import datetime
 import json
+from datetime import datetime
 
 from django.contrib import messages
 from django.db import models
 from django.db.models import OuterRef
 from django.db.models.expressions import F
 from django.db.models.functions import JSONObject
-from django.views.generic import ListView, TemplateView
-from django.views.generic.edit import UpdateView, DeleteView
 from django.urls import reverse, reverse_lazy
 from django.utils.translation import gettext_lazy as _
+from django.views.generic import ListView, TemplateView
+from django.views.generic.edit import DeleteView, UpdateView
 
-from core.models import StaticPage, StaticPageRevision, StaticPageNamespace
+from core.models import StaticPage, StaticPageNamespace, StaticPageRevision
 
 from ..forms import StaticPageEditForm
 from .mixins import ConferenceMixin, guess_active_sidebar_item
@@ -89,11 +89,14 @@ class PagesView(WikiAdminMixin, ListView):
     template_name = 'backoffice/wiki_page_list.html'
 
     def get_queryset(self, *args, **kwargs):
-        last_revision_details = StaticPageRevision.objects.filter(page=OuterRef('pk'), pk=OuterRef('public_revision'))\
-            .values(data=LastRevisionJSONObject(author=F('author__username'), timestamp=F('timestamp')))[0:1]
-        return StaticPage.objects.accessible_by_user(conference=self.conference, user=self.request.user, language=None)\
-            .order_by('slug', 'language')\
+        last_revision_details = StaticPageRevision.objects.filter(page=OuterRef('pk'), pk=OuterRef('public_revision')).values(
+            data=LastRevisionJSONObject(author=F('author__username'), timestamp=F('timestamp'))
+        )[0:1]
+        return (
+            StaticPage.objects.accessible_by_user(conference=self.conference, user=self.request.user, language=None)
+            .order_by('slug', 'language')
             .annotate(last_revision_details=last_revision_details)
+        )
 
 
 class PageView(WikiAdminMixin, UpdateView):
@@ -130,7 +133,7 @@ class PageDeleteView(WikiAdminMixin, DeleteView):
 
     def form_valid(self, form):
         res = super().form_valid(form)
-        messages.success(self.request, _("StaticPage--deleted"))
+        messages.success(self.request, _('StaticPage--deleted'))
         return res
 
 
@@ -147,5 +150,5 @@ class PageRevisionDeleteView(WikiAdminMixin, DeleteView):
 
     def form_valid(self, form):
         res = super().form_valid(form)
-        messages.success(self.request, _("StaticPageRevision--deleted"))
+        messages.success(self.request, _('StaticPageRevision--deleted'))
         return res
diff --git a/src/backoffice/views/workadventure.py b/src/backoffice/views/workadventure.py
index baa65f135..8a13ebe84 100644
--- a/src/backoffice/views/workadventure.py
+++ b/src/backoffice/views/workadventure.py
@@ -5,24 +5,22 @@ from uuid import UUID
 from django.conf import settings
 from django.contrib import messages
 from django.core.exceptions import ValidationError
+from django.db.models import Q
 from django.http import HttpResponseRedirect
 from django.shortcuts import redirect, render
 from django.urls import reverse
 from django.utils.html import format_html
-from django.views.generic import CreateView, DetailView, ListView, TemplateView, UpdateView, View, DeleteView
-from django.views.generic.detail import SingleObjectMixin
-
 from django.utils.translation import gettext_lazy as _
-from django.db.models import Q
+from django.views.generic import CreateView, DeleteView, DetailView, ListView, TemplateView, UpdateView, View
+from django.views.generic.detail import SingleObjectMixin
 
 from core.integrations.workadventure import WorkAdventureIntegration
-from core.models import PlatformUser, ConferenceMember, Assembly
+from core.models import Assembly, ConferenceMember, PlatformUser
 from core.models.rooms import Room
 from core.models.workadventure import WorkadventureSession, WorkadventureTexture
 
-from .mixins import ConferenceMixin, guess_active_sidebar_item
 from ..forms import CreateWorkadventureTextureForm
-
+from .mixins import ConferenceMixin, guess_active_sidebar_item
 
 logger = logging.getLogger(__name__)
 MAX_ROWS = 42
@@ -43,7 +41,7 @@ class WorkAdventureAdminMixin(ConferenceMixin):
 
     def get_context_data(self, *args, **kwargs):
         if not settings.INTEGRATIONS_WORKADVENTURE:
-            messages.warning(self.request, "WorkAdventure integration NOT active!")
+            messages.warning(self.request, 'WorkAdventure integration NOT active!')
 
         context = super().get_context_data(*args, **kwargs)
         context['active_page'] = 'workadventure'
@@ -69,16 +67,18 @@ class WorkAdventureAdminMixin(ConferenceMixin):
                     'link': reverse('backoffice:workadventure-texture-list'),
                     'expanded': False,
                     'count': self.conference.workadventure_textures.count(),
-                }
+                },
             ],
         }
 
         for m, (q, t) in self.BACKEND_STATUS.items():
-            maps.append({
-                'caption': t,
-                'count': self.conference.rooms.filter(q).count(),
-                'link': reverse('backoffice:workadventure-map-list') + '?mode=' + m,
-            })
+            maps.append(
+                {
+                    'caption': t,
+                    'count': self.conference.rooms.filter(q).count(),
+                    'link': reverse('backoffice:workadventure-map-list') + '?mode=' + m,
+                }
+            )
 
         # try to guess 'active' sidebar item
         guess_active_sidebar_item(self.request, context['sidebar']['items'], with_query_string=True)
@@ -218,17 +218,24 @@ class MapSyncView(WorkAdventureMapMixin, SingleObjectMixin, View):
             res = WorkAdventureIntegration.trigger_map_synchronization(room, force)
             msg = format_html(
                 '<strong>sync room {id} (force={force}):</strong><br><pre class="small">{res}</pre>',
-                id=room.pk, force=force, res=json.dumps(res, indent=2),
+                id=room.pk,
+                force=force,
+                res=json.dumps(res, indent=2),
             )
             if res.get('_errors'):
                 messages.error(request, msg)
             else:
                 messages.success(request, msg)
         except Exception as err:
-            messages.error(request, format_html(
-                '<strong>sync room {id} (force={force}):</strong><br><pre class="small">{err}</pre>',
-                id=room.pk, force=force, err=err,
-            ))
+            messages.error(
+                request,
+                format_html(
+                    '<strong>sync room {id} (force={force}):</strong><br><pre class="small">{err}</pre>',
+                    id=room.pk,
+                    force=force,
+                    err=err,
+                ),
+            )
 
         return redirect('backoffice:workadventure-map-detail', pk=room.pk)
 
@@ -290,7 +297,8 @@ class SessionPushDataView(SessionView):
             res = WorkAdventureIntegration.push_userinfo_session(obj)
             msg = format_html(
                 '<strong>push userinfo {id} to backend:</strong><br><pre class="small">{res}</pre>',
-                id=obj.pk, res=json.dumps(res, indent=2),
+                id=obj.pk,
+                res=json.dumps(res, indent=2),
             )
             if res.get('_errors'):
                 messages.error(request, msg)
@@ -312,7 +320,8 @@ class SessionDeleteView(SessionView):
                     res = WorkAdventureIntegration.terminate_session(obj)
                     msg = format_html(
                         '<strong>termination of WA session {id} in backend:</strong><br><pre class="small">{res}</pre>',
-                        id=obj_id, res=json.dumps(res, indent=2),
+                        id=obj_id,
+                        res=json.dumps(res, indent=2),
                     )
                     if res.get('_errors'):
                         messages.error(request, msg)
@@ -328,10 +337,14 @@ class SessionDeleteView(SessionView):
                     messages.success(request, 'WA session deletion failure (' + obj_id + ')')
 
             except Exception as err:
-                messages.error(request, format_html(
-                    '<strong>delete session: general failure</strong> ({id})<br><pre class="small">{err}</pre>',
-                    err=err, id=obj_id,
-                ))
+                messages.error(
+                    request,
+                    format_html(
+                        '<strong>delete session: general failure</strong> ({id})<br><pre class="small">{err}</pre>',
+                        err=err,
+                        id=obj_id,
+                    ),
+                )
 
         else:
             messages.warning(request, 'session was already deleted.')
diff --git a/src/core/abuse.py b/src/core/abuse.py
index d92df660c..14f4216b4 100644
--- a/src/core/abuse.py
+++ b/src/core/abuse.py
@@ -1,17 +1,19 @@
+import contextlib
+
 from django.contrib.sites.shortcuts import get_current_site
 from django.core.mail import send_mail
 from django.template import loader
-from django.urls import reverse, NoReverseMatch
+from django.urls import NoReverseMatch, reverse
 from django.utils.translation import gettext_lazy as _
 
-
 REPORT_CATEGORIES = {
-    'abuse': (_("abuse_report_category-abuse"), 'Abuse', 'abuse@cccv.de'),
-    'content': (_("abuse_report_category-content"), 'Content', 'report@cccv.de'),
-    'person': (_("abuse_report_category-person"), 'Person', 'report@cccv.de'),
-    'tech': (_("abuse_report_category-tech"), 'Technical', 'hub@cccv.de'),
-    # 'map': (_("abuse_report_category-map"), 'Map', 'world@cccv.de'),
-    'unknown': (_("abuse_report_category-misc"), 'Unknown', 'report@cccv.de'),
+    'abuse': (_('abuse_report_category-abuse'), 'Abuse', 'abuse@cccv.de'),
+    'content': (_('abuse_report_category-content'), 'Content', 'report@cccv.de'),
+    'person': (_('abuse_report_category-person'), 'Person', 'report@cccv.de'),
+    'tech': (_('abuse_report_category-tech'), 'Technical', 'hub@cccv.de'),
+    # TODO: Make this depended on whether WA is enabled
+    # 'map': (_("abuse_report_category-map"), 'Map', 'world@cccv.de'),# noqa: ERA001
+    'unknown': (_('abuse_report_category-misc'), 'Unknown', 'report@cccv.de'),
 }
 
 
@@ -40,12 +42,10 @@ def report_content(request, reporter, category, reported_content, problem_messag
         'message2': proposed_solution,
     }
 
-    try:
+    with contextlib.suppress(NoReverseMatch):
         context['reporter_profile'] = reverse('plainui:user_by_uuid', kwargs={'uuid': str(reporter.uuid)})
-    except NoReverseMatch:
-        pass
 
-    subject = "New %s Report" % (readable_category,)
+    subject = f'New {readable_category} Report'
     body = loader.render_to_string(EMAIL_TEMPLATE_NAME, context)
 
     send_mail(
diff --git a/src/core/admin.py b/src/core/admin.py
index 21a378a78..223482025 100644
--- a/src/core/admin.py
+++ b/src/core/admin.py
@@ -8,23 +8,47 @@ from django.contrib.gis.admin import GISModelAdmin
 from django.db.models import F
 from django.utils.translation import gettext_lazy as _
 
-from .models import \
-    BulletinBoardEntry, \
-    Conference, ConferenceMember, ConferenceNavigationItem, ConferenceTag, ConferenceTrack, \
-    DereferrerStats, \
-    Event, EventAttachment, EventParticipant, \
-    PlatformUser, \
-    Room, RoomLink, \
-    Assembly, AssemblyLink, AssemblyMember, AssemblyLogEntry, \
-    MapFloor, MapPOI, \
-    MetaNavItem, \
-    Badge, BadgeCategory, BadgeToken, BadgeTokenTimeConstraint, \
-    ScheduleSource, ScheduleSourceImport, ScheduleSourceMapping, \
-    StaticPage, StaticPageRevision, StaticPageNamespace, \
-    TagItem, \
-    UserCommunicationChannel, UserContact, UserBadge, UserDereferrerAllowlist, \
-    Voucher, VoucherEntry, \
-    WorkadventureSession, WorkadventureTexture
+from .models import (
+    Assembly,
+    AssemblyLink,
+    AssemblyLogEntry,
+    AssemblyMember,
+    Badge,
+    BadgeCategory,
+    BadgeToken,
+    BadgeTokenTimeConstraint,
+    BulletinBoardEntry,
+    Conference,
+    ConferenceMember,
+    ConferenceNavigationItem,
+    ConferenceTag,
+    ConferenceTrack,
+    DereferrerStats,
+    Event,
+    EventAttachment,
+    EventParticipant,
+    MapFloor,
+    MapPOI,
+    MetaNavItem,
+    PlatformUser,
+    Room,
+    RoomLink,
+    ScheduleSource,
+    ScheduleSourceImport,
+    ScheduleSourceMapping,
+    StaticPage,
+    StaticPageNamespace,
+    StaticPageRevision,
+    TagItem,
+    UserBadge,
+    UserCommunicationChannel,
+    UserContact,
+    UserDereferrerAllowlist,
+    Voucher,
+    VoucherEntry,
+    WorkadventureSession,
+    WorkadventureTexture,
+)
 
 logger = logging.getLogger(__name__)
 
@@ -57,21 +81,21 @@ class ArrayFieldEntryFilter(FieldListFilter):
         # assemble available options
         v = self.value()
         yield {
-            "selected": v is None or v == '',
-            "query_string": changelist.get_query_string(remove=[self.parameter_name]),
-            "display": _("All"),
+            'selected': v is None or v == '',
+            'query_string': changelist.get_query_string(remove=[self.parameter_name]),
+            'display': _('All'),
         }
         for value in sorted(values, key=lambda x: x.upper()):
             yield {
-                "selected": v == value,
-                "query_string": changelist.get_query_string({self.parameter_name: value}),
-                "display": value,
+                'selected': v == value,
+                'query_string': changelist.get_query_string({self.parameter_name: value}),
+                'display': value,
             }
 
     def queryset(self, request, queryset):
         if query := self.value():
             qs_filter = {
-                f"{self.field_path}__contains": query.split(','),
+                f'{self.field_path}__contains': query.split(','),
             }
             queryset = queryset.filter(**qs_filter)
         return queryset
@@ -108,22 +132,22 @@ class UserAssemblyMemberInline(admin.TabularInline):
 class UserFavoriteEventInline(admin.TabularInline):
     model = Event.favorited_by.through
     extra = 0
-    verbose_name = _("PlatformUser__favorite_event")
-    verbose_name_plural = _("PlatformUser__favorite_events")
+    verbose_name = _('PlatformUser__favorite_event')
+    verbose_name_plural = _('PlatformUser__favorite_events')
 
 
 class UserFavoriteAssemblyInline(admin.TabularInline):
     model = Assembly.favorited_by.through
     extra = 0
-    verbose_name = _("PlatformUser__favorite_assembly")
-    verbose_name_plural = _("PlatformUser__favorite_assemblies")
+    verbose_name = _('PlatformUser__favorite_assembly')
+    verbose_name_plural = _('PlatformUser__favorite_assemblies')
 
 
 class UserPersonalCalendarEventInline(admin.TabularInline):
     model = Event.in_personal_calendar.through
     extra = 0
-    verbose_name = _("PlatformUser__calendar_event")
-    verbose_name_plural = _("PlatformUser__calendar_events")
+    verbose_name = _('PlatformUser__calendar_event')
+    verbose_name_plural = _('PlatformUser__calendar_events')
 
 
 class PlatformUserAdmin(UserAdmin):
@@ -140,7 +164,7 @@ class PlatformUserAdmin(UserAdmin):
         ('Accessibility', {'fields': ('theme', 'no_animations', 'colorblind', 'high_contrast', 'tag_ignorelist')}),
         ('Disturbance Settings', {'fields': ('receive_dms', 'receive_dm_images', 'receive_audio', 'receive_video', 'autoaccept_contacts')}),
         ('Permissions', {'fields': ('is_active', 'shadow_banned', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
-        ('Notifications', {'fields': ('admin_notification', )}),
+        ('Notifications', {'fields': ('admin_notification',)}),
         ('Important dates', {'fields': ('last_login', 'date_joined')}),
         ('Security', {'fields': ('allow_reset_non_primary',)}),
     )
@@ -218,23 +242,29 @@ class ConferenceNavigationItemAdmin(admin.ModelAdmin):
         return qs.order_by('conference__name', F('parent__index').asc(nulls_first=True), 'index')
 
     fieldsets = (
-        ('Organisation', {
-            'fields': ['conference', 'parent', 'index'],
-        }),
-        ('Data', {
-            'fields': [
-                'is_visible',
-                'icon',
-                ('label_de', 'label_en'),
-                ('title_de', 'title_en'),
-                'url',
-            ],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['conference', 'parent', 'index'],
+            },
+        ),
+        (
+            'Data',
+            {
+                'fields': [
+                    'is_visible',
+                    'icon',
+                    ('label_de', 'label_en'),
+                    ('title_de', 'title_en'),
+                    'url',
+                ],
+            },
+        ),
     )
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
-        if db_field.name == "parent":
-            kwargs["queryset"] = ConferenceNavigationItem.objects.filter(parent=None).order_by('index')
+        if db_field.name == 'parent':
+            kwargs['queryset'] = ConferenceNavigationItem.objects.filter(parent=None).order_by('index')
 
         return super().formfield_for_foreignkey(db_field, request, **kwargs)
 
@@ -254,12 +284,18 @@ class ConferenceTrackAdmin(admin.ModelAdmin):
     readonly_fields = ['conference']
 
     fieldsets = (
-        ('Organisation', {
-            'fields': ['conference', 'is_public'],
-        }),
-        ('Data', {
-            'fields': ['slug', 'name', 'banner_image'],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['conference', 'is_public'],
+            },
+        ),
+        (
+            'Data',
+            {
+                'fields': ['slug', 'name', 'banner_image'],
+            },
+        ),
     )
 
     def get_readonly_fields(self, request, obj=None, **kwargs):
@@ -328,18 +364,30 @@ class AssemblyAdmin(GISModelAdmin):
     inlines = [TagsInline, BadgeInline, AssemblyLinkInline, AssemblyMemberInline, AssemblyLogEntryInline]
 
     fieldsets = (
-        ('Organisation', {
-            'fields': ['id', 'conference', 'state_assembly', 'state_channel', 'hierarchy', 'parent', 'is_official'],
-        }),
-        ('Data', {
-            'fields': ['is_physical', 'is_virtual', 'is_remote', 'slug', 'name', 'description', 'assembly_link', 'banner_image'],
-        }),
-        ('Registration', {
-            'fields': ['registration_details'],
-        }),
-        ('Location', {
-            'fields': ['assembly_location', 'location_point', 'location_boundaries'],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['id', 'conference', 'state_assembly', 'state_channel', 'hierarchy', 'parent', 'is_official'],
+            },
+        ),
+        (
+            'Data',
+            {
+                'fields': ['is_physical', 'is_virtual', 'is_remote', 'slug', 'name', 'description', 'assembly_link', 'banner_image'],
+            },
+        ),
+        (
+            'Registration',
+            {
+                'fields': ['registration_details'],
+            },
+        ),
+        (
+            'Location',
+            {
+                'fields': ['assembly_location', 'location_point', 'location_boundaries'],
+            },
+        ),
     )
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
@@ -356,12 +404,18 @@ class AssemblyAdmin(GISModelAdmin):
     def get_fieldsets(self, request, obj=None, **kwargs):
         if obj is None:
             return [
-                ('Organisation', {
-                    'fields': ['id', 'conference', 'state_assembly', 'state_channel', 'hierarchy', 'is_official'],
-                }),
-                ('Data', {
-                    'fields': ['is_physical', 'is_virtual', 'is_remote', 'slug', 'name'],
-                }),
+                (
+                    'Organisation',
+                    {
+                        'fields': ['id', 'conference', 'state_assembly', 'state_channel', 'hierarchy', 'is_official'],
+                    },
+                ),
+                (
+                    'Data',
+                    {
+                        'fields': ['is_physical', 'is_virtual', 'is_remote', 'slug', 'name'],
+                    },
+                ),
             ]
         return super().get_fieldsets(request, obj, **kwargs)
 
@@ -382,16 +436,25 @@ class AssemblyLogEntryAdmin(admin.ModelAdmin):
     search_fields = ['assembly__slug', 'assembly__name', 'comment', 'changes']
     readonly_fields = ['timestamp']
     fieldsets = [
-        ('Organisation', {
-            'fields': ['timestamp', 'assembly'],
-        }),
-        ('Data', {
-            'fields': [
-                ('kind', 'user',),
-                'comment',
-                'changes',
-            ],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['timestamp', 'assembly'],
+            },
+        ),
+        (
+            'Data',
+            {
+                'fields': [
+                    (
+                        'kind',
+                        'user',
+                    ),
+                    'comment',
+                    'changes',
+                ],
+            },
+        ),
     ]
 
 
@@ -403,12 +466,18 @@ class MapFloorAdmin(admin.ModelAdmin):
     ordering = ['conference', 'index']
 
     fieldsets = (
-        ('Organisation', {
-            'fields': ['id', 'conference'],
-        }),
-        ('Data', {
-            'fields': ['index', ('name_de', 'name_en')],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['id', 'conference'],
+            },
+        ),
+        (
+            'Data',
+            {
+                'fields': ['index', ('name_de', 'name_en')],
+            },
+        ),
     )
 
     def get_readonly_fields(self, request, obj=None, **kwargs):
@@ -426,17 +495,24 @@ class MapPOIAdmin(GISModelAdmin):
     search_fields = ['name', 'description']
 
     fieldsets = (
-        ('Organisation', {
-            'fields': ['id', 'conference'],
-        }),
-        ('Data', {
-            'fields': ['visible', 'is_official',
-                       ('name_de', 'name_en'),
-                       ('description_de', 'description_en')],
-        }),
-        ('Location', {
-            'fields': ['location_floor', 'location_point'],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['id', 'conference'],
+            },
+        ),
+        (
+            'Data',
+            {
+                'fields': ['visible', 'is_official', ('name_de', 'name_en'), ('description_de', 'description_en')],
+            },
+        ),
+        (
+            'Location',
+            {
+                'fields': ['location_floor', 'location_point'],
+            },
+        ),
     )
 
     def get_readonly_fields(self, request, obj=None, **kwargs):
@@ -454,7 +530,9 @@ class BadgeAdmin(admin.ModelAdmin):
     model = Badge
 
     fields = ['name', 'issuing_assembly', 'state', 'category', 'description', 'location', 'image', 'issuing_token']
-    readonly_fields = ['issuing_assembly',]
+    readonly_fields = [
+        'issuing_assembly',
+    ]
     list_display = ['__str__', 'issuing_assembly', 'category']
     list_display_links = ['__str__']
 
@@ -462,8 +540,8 @@ class BadgeAdmin(admin.ModelAdmin):
 class BadgeTokenTimeConstraintInline(admin.TabularInline):
     model = BadgeTokenTimeConstraint
     fields = ['date_time_range']
-    verbose_name = _("BadgeToken__badge_token_time_constraint")
-    verbose_name_plural = _("BadgeToken__badge_token_time_constraints")
+    verbose_name = _('BadgeToken__badge_token_time_constraint')
+    verbose_name_plural = _('BadgeToken__badge_token_time_constraints')
 
 
 class BadgeTokenAdmin(admin.ModelAdmin):
@@ -480,6 +558,7 @@ class BadgeTokenAdmin(admin.ModelAdmin):
 
     def valid(self, obj):
         return obj.valid
+
     valid.boolean = True
 
     def time_constraints_list(self, obj):
@@ -567,15 +646,24 @@ class EventAdmin(admin.ModelAdmin):
     readonly_fields = ['id', 'conference', 'get_is_imported']
 
     fieldsets = (
-        ('Organisation', {
-            'fields': ['id', 'conference', 'track', 'assembly', 'room', 'kind', 'is_public'],
-        }),
-        ('Schedule', {
-            'fields': ['schedule_start', 'schedule_duration', 'get_is_imported'],
-        }),
-        ('Data', {
-            'fields': ['name', 'slug', 'language', 'description', 'banner_image', 'additional_data'],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['id', 'conference', 'track', 'assembly', 'room', 'kind', 'is_public'],
+            },
+        ),
+        (
+            'Schedule',
+            {
+                'fields': ['schedule_start', 'schedule_duration', 'get_is_imported'],
+            },
+        ),
+        (
+            'Data',
+            {
+                'fields': ['name', 'slug', 'language', 'description', 'banner_image', 'additional_data'],
+            },
+        ),
     )
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
@@ -596,12 +684,18 @@ class EventAdmin(admin.ModelAdmin):
     def get_fieldsets(self, request, obj=None, **kwargs):
         if obj is None:
             return [
-                ('Organisation', {
-                    'fields': ['id', 'conference', 'assembly'],
-                }),
-                ('Data', {
-                    'fields': ['name'],
-                }),
+                (
+                    'Organisation',
+                    {
+                        'fields': ['id', 'conference', 'assembly'],
+                    },
+                ),
+                (
+                    'Data',
+                    {
+                        'fields': ['name'],
+                    },
+                ),
             ]
         return super().get_fieldsets(request, obj, **kwargs)
 
@@ -639,15 +733,24 @@ class RoomAdmin(admin.ModelAdmin):
     ordering = ('-conference__id', F('assembly__is_official').desc(nulls_last=True), 'assembly__name', F('capacity').desc(nulls_last=True), 'name')
 
     fieldsets = (
-        ('Organisation', {
-            'fields': ['id', 'conference', 'assembly', 'is_public_fahrplan', 'blocked', 'reserve_capacity'],
-        }),
-        ('Data', {
-            'fields': ['name', 'room_type', 'capacity', 'occupants', 'description'],
-        }),
-        ('Backend', {
-            'fields': ['backend_link', 'backend_link_branch', 'backend_status', 'backend_data', 'director_data'],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['id', 'conference', 'assembly', 'is_public_fahrplan', 'blocked', 'reserve_capacity'],
+            },
+        ),
+        (
+            'Data',
+            {
+                'fields': ['name', 'room_type', 'capacity', 'occupants', 'description'],
+            },
+        ),
+        (
+            'Backend',
+            {
+                'fields': ['backend_link', 'backend_link_branch', 'backend_status', 'backend_data', 'director_data'],
+            },
+        ),
     )
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
@@ -665,12 +768,18 @@ class RoomAdmin(admin.ModelAdmin):
     def get_fieldsets(self, request, obj=None, **kwargs):
         if obj is None:
             return [
-                ('Organisation', {
-                    'fields': ['id', 'conference', 'assembly', 'blocked'],
-                }),
-                ('Data', {
-                    'fields': ['name', 'room_type', 'backend_link', 'capacity'],
-                }),
+                (
+                    'Organisation',
+                    {
+                        'fields': ['id', 'conference', 'assembly', 'blocked'],
+                    },
+                ),
+                (
+                    'Data',
+                    {
+                        'fields': ['name', 'room_type', 'backend_link', 'capacity'],
+                    },
+                ),
             ]
         return super().get_fieldsets(request, obj, **kwargs)
 
@@ -706,15 +815,24 @@ class StaticPageAdmin(admin.ModelAdmin):
     readonly_fields = ['id', 'conference', 'language', 'body_html', 'search_content']
 
     fieldsets = (
-        ('Organisation', {
-            'fields': ['id', 'conference', 'slug', 'language'],
-        }),
-        ('Configuration', {
-            'fields': ['public_revision', 'protection', 'privacy', 'remove_html', 'sanitize_html'],
-        }),
-        ('Data', {
-            'fields': ['title', ('search_content', 'body_html')],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['id', 'conference', 'slug', 'language'],
+            },
+        ),
+        (
+            'Configuration',
+            {
+                'fields': ['public_revision', 'protection', 'privacy', 'remove_html', 'sanitize_html'],
+            },
+        ),
+        (
+            'Data',
+            {
+                'fields': ['title', ('search_content', 'body_html')],
+            },
+        ),
     )
 
     def get_inline_instances(self, request, obj=None, **kwargs):
@@ -739,15 +857,24 @@ class StaticPageNamespaceAdmin(admin.ModelAdmin):
     readonly_fields = ['id', 'conference']
 
     fieldsets = (
-        ('Organisation', {
-            'fields': ['id', 'conference'],
-        }),
-        ('Namespace', {
-            'fields': ['prefix', 'groups'],
-        }),
-        ('Upstream', {
-            'fields': ['upstream_url', 'upstream_image_base_url'],
-        }),
+        (
+            'Organisation',
+            {
+                'fields': ['id', 'conference'],
+            },
+        ),
+        (
+            'Namespace',
+            {
+                'fields': ['prefix', 'groups'],
+            },
+        ),
+        (
+            'Upstream',
+            {
+                'fields': ['upstream_url', 'upstream_image_base_url'],
+            },
+        ),
     )
 
     def get_readonly_fields(self, request, obj=None, **kwargs):
@@ -797,6 +924,7 @@ class VoucherEntryAdmin(admin.ModelAdmin):
 
     def is_assigned(self, instance):
         return instance.assigned is not None
+
     is_assigned.boolean = True
 
 
@@ -862,24 +990,33 @@ class MetaNavItemAdmin(admin.ModelAdmin):
     readonly_fields = ['pk', 'conference', 'graphic_light_current', 'graphic_dark_current']
 
     fieldsets = [
-        ('Metadata', {
-            'fields': ['pk', 'conference', 'slug'],
-        }),
-        ('Display', {
-            'fields': [
-                'index',
-                'visible',
-                'enabled',
-                ('title_de', 'title_en'),
-            ],
-        }),
-        ('Item', {
-            'fields': [
-                'url',
-                ('graphic_light_current', 'graphic_light'),
-                ('graphic_dark_current', 'graphic_dark'),
-            ],
-        }),
+        (
+            'Metadata',
+            {
+                'fields': ['pk', 'conference', 'slug'],
+            },
+        ),
+        (
+            'Display',
+            {
+                'fields': [
+                    'index',
+                    'visible',
+                    'enabled',
+                    ('title_de', 'title_en'),
+                ],
+            },
+        ),
+        (
+            'Item',
+            {
+                'fields': [
+                    'url',
+                    ('graphic_light_current', 'graphic_light'),
+                    ('graphic_dark_current', 'graphic_dark'),
+                ],
+            },
+        ),
     ]
 
     def get_readonly_fields(self, request, obj=None, **kwargs):
@@ -890,11 +1027,13 @@ class MetaNavItemAdmin(admin.ModelAdmin):
 
     def graphic_light_current(self, obj: MetaNavItem):
         return obj.get_graphic_light_as_html()
+
     graphic_light_current.allow_tags = True
     graphic_light_current.caption = _('MetaNavItem__graphic_light')
 
     def graphic_dark_current(self, obj: MetaNavItem):
         return obj.get_graphic_dark_as_html(default_to_light=False)
+
     graphic_dark_current.allow_tags = True
     graphic_dark_current.verbose_name = _('MetaNavItem__graphic_dark')
 
diff --git a/src/core/apps.py b/src/core/apps.py
index 08c8a22a9..9e5e1e9e8 100644
--- a/src/core/apps.py
+++ b/src/core/apps.py
@@ -2,7 +2,6 @@ import logging
 
 from django.apps import AppConfig
 
-
 logger = logging.getLogger(__name__)
 
 
diff --git a/src/core/base_forms.py b/src/core/base_forms.py
index bbea55ed3..4c5fe68dc 100644
--- a/src/core/base_forms.py
+++ b/src/core/base_forms.py
@@ -1,7 +1,7 @@
 from django.forms import ModelForm
 from modeltranslation.fields import build_localized_fieldname
 from modeltranslation.settings import AVAILABLE_LANGUAGES
-from modeltranslation.translator import translator, NotRegistered
+from modeltranslation.translator import NotRegistered, translator
 
 
 class TranslatedFieldsForm(ModelForm):
@@ -19,7 +19,11 @@ class TranslatedFieldsForm(ModelForm):
 
         fields_to_translate = set(translation_options.get_field_names())
 
-        for attr in ('fields', 'exclude', 'localized_fields',):
+        for attr in (
+            'fields',
+            'exclude',
+            'localized_fields',
+        ):
             fields = getattr(meta, attr, None)
             if fields:
                 new_fields = []
diff --git a/src/core/forms.py b/src/core/forms.py
index bae3f6827..1f5c32531 100644
--- a/src/core/forms.py
+++ b/src/core/forms.py
@@ -3,14 +3,14 @@ from smtplib import SMTPException
 from typing import Any
 
 from django.conf import settings
-from django.db.models import Q
 from django.contrib.auth import forms as auth_forms
-from django.contrib.auth.tokens import default_token_generator
 from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
 from django.contrib.auth.forms import PasswordResetForm as ContribPasswordResetForm
+from django.contrib.auth.tokens import default_token_generator
 from django.contrib.sites.shortcuts import get_current_site
 from django.core.exceptions import ValidationError
 from django.core.mail import EmailMultiAlternatives
+from django.db.models import Q
 from django.forms import ChoiceField, EmailField, EmailInput
 from django.template import loader
 from django.utils.encoding import force_bytes
diff --git a/src/core/integrations/__init__.py b/src/core/integrations/__init__.py
index c6d4856c1..ab02f4673 100644
--- a/src/core/integrations/__init__.py
+++ b/src/core/integrations/__init__.py
@@ -1,7 +1,8 @@
 from django.conf import settings
+
 from .bigbluebutton import BigBlueButtonIntegration
-from .rc3hangar import HangarIntegration
 from .error import IntegrationError
+from .rc3hangar import HangarIntegration
 from .workadventure import WorkAdventureIntegration
 
 if settings.BIGBLUEBUTTON_API_URL is not None:
diff --git a/src/core/integrations/bigbluebutton.py b/src/core/integrations/bigbluebutton.py
index ffe772d11..bbc8f7c90 100644
--- a/src/core/integrations/bigbluebutton.py
+++ b/src/core/integrations/bigbluebutton.py
@@ -1,21 +1,21 @@
-from hashlib import sha1
 import logging
-from random import SystemRandom
-import requests
 import string
+from hashlib import sha1
+from random import SystemRandom
 from typing import Dict, Union
-from urllib.parse import urlencode, urljoin, quote
+from urllib.parse import quote, urlencode, urljoin
 from uuid import uuid4
 from xml.etree import ElementTree as ET
 
+import requests
+
 from django.utils.translation import gettext as _
 
+from core.models import BackendMixin, Event, PlatformUser, Room
 from core.models.assemblies import Assembly, AssemblyMember
-from core.models import BackendMixin, Event, Room, PlatformUser
 
 from .error import IntegrationError
 
-
 logger = logging.getLogger(__name__)
 PASSWORD_CHARS = string.ascii_letters + string.digits
 
@@ -24,16 +24,16 @@ def _params_to_str(data: Dict[str, Union[str, int, bool]]):
     res = {}
     for k, v in data.items():
         if isinstance(v, bool):
-            v = 'true' if v else 'false'
+            v = 'true' if v else 'false'  # noqa: PLW2901
         elif isinstance(v, int):
-            v = str(v)
+            v = str(v)  # noqa: PLW2901
         else:
             assert isinstance(v, str), f'{v!r} is no string!'
         res[k] = v
     return res
 
 
-class BigBlueButtonIntegration(object):
+class BigBlueButtonIntegration:
     """
     This class talks with a BigBlueButton server's API.
 
@@ -68,14 +68,14 @@ class BigBlueButtonIntegration(object):
             return resp
 
         if resp.status_code != 200:
-            logger.warning("Request for resource %r failed with status code %r", resource, resp.status_code)
-            raise IntegrationError(_("Request failed"))
+            logger.warning('Request for resource %r failed with status code %r', resource, resp.status_code)
+            raise IntegrationError(_('Request failed'))
 
         try:
             resp = ET.fromstring(resp.text)
         except UnicodeDecodeError:
-            logger.exception("Response decoding failed")
-            raise IntegrationError(_("Invalid Response"))
+            logger.exception('Response decoding failed')
+            raise IntegrationError(_('Invalid Response'))
 
         return resp
 
@@ -137,7 +137,8 @@ class BigBlueButtonIntegration(object):
                 f'    <module name="presentation">'
                 f'        <document url="{self._initial_presentation_url}" />'
                 f'    </module>'
-                f'</modules>')
+                f'</modules>'
+            )
 
         try:
             result = self._send_request('create', params, post_body=request_body)
@@ -153,8 +154,8 @@ class BigBlueButtonIntegration(object):
             message = result.find('message')
             if message is not None:
                 message = message.text
-            logger.warning("Request to create Room %s failed. Message: %r", room, message)
-            raise IntegrationError(_("Request failed"))
+            logger.warning('Request to create Room %s failed. Message: %r', room, message)
+            raise IntegrationError(_('Request failed'))
 
         room.backend_link = result.find('meetingID').text
         room.backend_status = Room.BackendStatus.ACTIVE
@@ -196,8 +197,8 @@ class BigBlueButtonIntegration(object):
             message = result.find('message')
             if message is not None:
                 message = message.text
-            logger.warning("Request to delete room %s failed. Message: %r", room, message)
-            raise IntegrationError(_("Request failed"))
+            logger.warning('Request to delete room %s failed. Message: %r', room, message)
+            raise IntegrationError(_('Request failed'))
 
         room.backend_status = Room.BackendStatus.INACTIVE
         room.save(update_fields=['backend_status'])
@@ -213,11 +214,11 @@ class BigBlueButtonIntegration(object):
         retcode = resp.find('returncode')
         if retcode is not None and retcode.text != 'SUCCESS':
             message_key = resp.find('messageKey')
-            if message_key is not None and (message_key.text == 'notFound' or message_key.text == 'invalidMeetingIdentifier'):
+            if message_key is not None and (message_key.text in ('notFound', 'invalidMeetingIdentifier')):
                 room.backend_status = Room.BackendStatus.INACTIVE
             else:
-                logger.warning("getMeetingInfo request for room %r failed with message %r", room, None if message_key is None else message_key.text)
-                raise IntegrationError(_("Request failed"))
+                logger.warning('getMeetingInfo request for room %r failed with message %r', room, None if message_key is None else message_key.text)
+                raise IntegrationError(_('Request failed'))
 
         try:
             room.occupants = int(resp.find('participantCount').text)
diff --git a/src/core/integrations/workadventure.py b/src/core/integrations/workadventure.py
index dd5a3c3c7..094bed765 100644
--- a/src/core/integrations/workadventure.py
+++ b/src/core/integrations/workadventure.py
@@ -1,9 +1,11 @@
+import contextlib
 import logging
 from json import JSONDecodeError
 
+import requests
+
 from django.conf import settings
 from django.utils.translation import gettext_lazy as _
-import requests
 
 from core.models.assemblies import Assembly
 from core.models.conference import Conference
@@ -96,9 +98,9 @@ class WorkAdventureIntegration:
         return {conference.slug: maps}
 
     @staticmethod
-    def _send_request_for_conference(url_template, url_auth, conference: Conference,
-                                     method='POST', content=None, data=None,
-                                     url_replacements=None, no_verify=False, expect_json=True):
+    def _send_request_for_conference(
+        url_template, url_auth, conference: Conference, method='POST', content=None, data=None, url_replacements=None, no_verify=False, expect_json=True
+    ):
         if url_template is None or url_template == '':
             logging.warning('URL is not defined.')
             return None, 'No PUSH url configured.'
@@ -117,7 +119,7 @@ class WorkAdventureIntegration:
             r = session.request(method, url, json=data, data=content.encode('utf-8') if content is not None else None)
             if not r.ok:
                 logging.error('Request to %s failed with %s: %s', url, r.status_code, r.text)
-                errmsg = 'Request got response {code}: {msg}'.format(code=r.status_code, msg=r.text)
+                errmsg = f'Request got response {r.status_code}: {r.text}'
                 try:
                     return r.json(), errmsg
                 except JSONDecodeError:
@@ -125,7 +127,7 @@ class WorkAdventureIntegration:
 
         except Exception as err:
             logging.exception('Request to %s failed.', url)
-            return None, 'Request failed: {msg}'.format(msg=str(err))
+            return None, f'Request failed: {err!s}'
 
         if r.status_code == 204:
             # server said OK but 'No Content'
@@ -142,7 +144,7 @@ class WorkAdventureIntegration:
         try:
             maps = WorkAdventureIntegration.assemble_wa_backend_maplist(conference)
         except Exception as err:
-            result['_errors'].append('Failed to assemble maps for conference {confslug}: {msg}'.format(confslug=conference.slug, msg=str(err)))
+            result['_errors'].append(f'Failed to assemble maps for conference {conference.slug}: {err!s}')
             logging.exception('WA map assembly for export failed.')
             return result
         result['maps'] = len(maps)
@@ -282,10 +284,8 @@ class WorkAdventureIntegration:
 
             # summarize violations
             violation = director_data.get('violation', {})
-            try:
+            with contextlib.suppress(ValueError):
                 linter_timestamp = unix2timestamp(int(violation.get('violationCheck')))
-            except ValueError:
-                pass
             linter_commit = violation.get('violationCommitHash')
             if not violation:
                 linter_status = 'success'
@@ -303,7 +303,7 @@ class WorkAdventureIntegration:
                 linter_results = violation.get('linterRes', {}).get('mapLints', {})
                 linter_missingassets = violation.get('linterRes', {}).get('missingAssets', [])
                 linter_missingentrypoints = violation.get('linterRes', {}).get('missingDeps', [])
-                linter_exitgraph = violation.get('linterRes', {}).get('exitGraph', "")
+                linter_exitgraph = violation.get('linterRes', {}).get('exitGraph', '')
         return {
             'wa_published': {
                 'commitHash': publish_commit if has_mapinfo else None,
@@ -320,6 +320,6 @@ class WorkAdventureIntegration:
                 'timestamp': linter_timestamp,
                 'missingAssets': linter_missingassets,
                 'missingEntrypoints': linter_missingentrypoints,
-                'exitGraph': linter_exitgraph
-            }
+                'exitGraph': linter_exitgraph,
+            },
         }
diff --git a/src/core/management/commands/assembly_contact_mails.py b/src/core/management/commands/assembly_contact_mails.py
index 361e5a037..4caf3905b 100644
--- a/src/core/management/commands/assembly_contact_mails.py
+++ b/src/core/management/commands/assembly_contact_mails.py
@@ -6,7 +6,7 @@ from ...models.users import UserCommunicationChannel
 
 
 class Command(BaseCommand):
-    help = 'List all assemblies\' contacts\' verified e-mail addresses'
+    help = "List all assemblies' contacts' verified e-mail addresses"
 
     def add_arguments(self, parser):
         parser.add_argument('conf_slug', help='conference slug')
diff --git a/src/core/management/commands/bbb_integration_revisit.py b/src/core/management/commands/bbb_integration_revisit.py
index af4041322..dd92c61ce 100644
--- a/src/core/management/commands/bbb_integration_revisit.py
+++ b/src/core/management/commands/bbb_integration_revisit.py
@@ -3,14 +3,16 @@ import time
 from django.core.management.base import BaseCommand
 from django.db import transaction
 
-from core.models import Conference, Room
 from core.integrations import BigBlueButton, IntegrationError
+from core.models import Conference, Room
 
 
 class Command(BaseCommand):
     def add_arguments(self, parser):
         parser.add_argument(
-            '-a', '--all', action='store_true',
+            '-a',
+            '--all',
+            action='store_true',
             help='Revisit all Rooms, not just failing ones',
         )
 
@@ -30,7 +32,7 @@ class Command(BaseCommand):
                         BigBlueButton.create_room(room)
                         room.save()
                 except IntegrationError as e:
-                    print(f"Refreshing room {room!s} failed: {e!s}")
+                    print(f'Refreshing room {room!s} failed: {e!s}')
 
             elif revisit_active and room.backend_status in {Room.BackendStatus.ACTIVE, Room.BackendStatus.FULL}:
                 n_healthy += 1
@@ -39,7 +41,7 @@ class Command(BaseCommand):
                         BigBlueButton.room_status(room)
                         room.save()
                 except IntegrationError as e:
-                    print(f"Refreshing room {room!s} failed: {e!s}")
+                    print(f'Refreshing room {room!s} failed: {e!s}')
 
         run_time = time.time() - start
         print(f"Revisited {n_fail} failing rooms {f'and {n_healthy} healthy ones'} in {run_time} Seconds.")
diff --git a/src/core/management/commands/hangar_creation.py b/src/core/management/commands/hangar_creation.py
index a718cb650..201952c69 100644
--- a/src/core/management/commands/hangar_creation.py
+++ b/src/core/management/commands/hangar_creation.py
@@ -12,7 +12,7 @@ def create_hangar(room: Room):
     username = room.assembly.slug
     cmd = ['ssh', '-i', settings.HANGAR_KEY, settings.HANGAR_HOST, settings.HANGAR_CMD, username]
 
-    result = subprocess.run(cmd, capture_output=True, timeout=42, encoding='utf-8')
+    result = subprocess.run(cmd, capture_output=True, timeout=42, encoding='utf-8', check=False)
     if result.returncode != 0:
         room.backend_data = {
             'timestamp': timezone.now().strftime('%Y-%m-%d %H:%M:%S'),
@@ -61,7 +61,7 @@ class Command(BaseCommand):
                 contacts = create_hangar(room)
 
                 msg_subject = f'Hangar "{room.assembly.slug}"'
-                msg_text = '''Your hangar has been created. Please visit it in the Maschinenraum for access details.'''
+                msg_text = """Your hangar has been created. Please visit it in the Maschinenraum for access details."""
 
                 for c in contacts:
                     c.notify_user(msg_subject, msg_text, details_link=reverse('backoffice:assembly-room', kwargs={'assembly': room.assembly_id, 'pk': room.id}))
diff --git a/src/core/management/commands/housekeeping.py b/src/core/management/commands/housekeeping.py
index a1645d065..8e7ab72ca 100644
--- a/src/core/management/commands/housekeeping.py
+++ b/src/core/management/commands/housekeeping.py
@@ -15,8 +15,8 @@ class Command(BaseCommand):
     def add_arguments(self, parser):
         parser.add_argument('--forever', action='store_true', help='repeat the housekeeping forever (until Ctrl+C is pressed)')
         parser.add_argument('--forever-delay', type=int, default=300, help='seconds to wait between housekeeping runs')
-        parser.add_argument('--skip-schedule-imports', action='store_true', help='don\'t do schedule imports')
-        parser.add_argument('--skip-wiki-imports', action='store_true', help='don\'t import wiki namespaces from upstream')
+        parser.add_argument('--skip-schedule-imports', action='store_true', help="don't do schedule imports")
+        parser.add_argument('--skip-wiki-imports', action='store_true', help="don't import wiki namespaces from upstream")
 
     def _housekeeping_directmessages(self):
         # clear all direct messages which are after their expiry date
diff --git a/src/core/management/commands/import_mapservice_resultfile.py b/src/core/management/commands/import_mapservice_resultfile.py
index f55d27c4b..4c9956521 100644
--- a/src/core/management/commands/import_mapservice_resultfile.py
+++ b/src/core/management/commands/import_mapservice_resultfile.py
@@ -4,11 +4,12 @@ import json
 from django.core.management.base import BaseCommand
 
 from api.views.workadventure import MapServiceView
+
 from ...models import Conference
 
 
 class Command(BaseCommand):
-    help = 'import the mapservice\'s result file directly (as it would have been sent via the maps endpoint)'
+    help = "import the mapservice's result file directly (as it would have been sent via the maps endpoint)"
 
     def add_arguments(self, parser):
         parser.add_argument('conference_slug', help='slug of the conference')
diff --git a/src/core/management/commands/rerender_markdown.py b/src/core/management/commands/rerender_markdown.py
index 77a0c86e7..9dce899de 100644
--- a/src/core/management/commands/rerender_markdown.py
+++ b/src/core/management/commands/rerender_markdown.py
@@ -1,7 +1,7 @@
 from django.core.management.base import BaseCommand
 
-from core.models import Event, Room, Assembly, ConferenceMember, StaticPage
 from core.markdown import render_markdown_ex, store_relationships
+from core.models import Assembly, ConferenceMember, Event, Room, StaticPage
 
 
 class Command(BaseCommand):
diff --git a/src/core/management/commands/sanitize_database.py b/src/core/management/commands/sanitize_database.py
index 23e2fffd8..d151568ea 100644
--- a/src/core/management/commands/sanitize_database.py
+++ b/src/core/management/commands/sanitize_database.py
@@ -1,16 +1,22 @@
 from django.core.management.base import BaseCommand
 
-from ...models import \
-    Assembly, AssemblyMember, \
-    ConferenceMember, ConferenceMemberTicket, ConferenceTag, ConferenceTrack, \
-    DirectMessage, \
-    Event, \
-    PlatformUser, \
-    StaticPage, \
-    TagItem, \
-    UserBadge, \
-    UserCommunicationChannel, \
-    WorkadventureSession, UserDereferrerAllowlist
+from ...models import (
+    Assembly,
+    AssemblyMember,
+    ConferenceMember,
+    ConferenceMemberTicket,
+    ConferenceTag,
+    ConferenceTrack,
+    DirectMessage,
+    Event,
+    PlatformUser,
+    StaticPage,
+    TagItem,
+    UserBadge,
+    UserCommunicationChannel,
+    UserDereferrerAllowlist,
+    WorkadventureSession,
+)
 
 
 class Command(BaseCommand):
@@ -86,10 +92,6 @@ class Command(BaseCommand):
         print('WorkadventureSession: ', end='', flush=True)
         print_delete_stat(WorkadventureSession.objects.all().delete())
 
-        # from plainui.models import BulletinBoardEntry
-        # print('BulletinBoardEntry: ', end='', flush=True)
-        # print_delete_stat(BulletinBoardEntry.objects.all().delete())  # cascade via PlatformUser
-
         print('UserDereferrerAllowlist: ', end='', flush=True)
         print_delete_stat(UserDereferrerAllowlist.objects.all().delete())
 
diff --git a/src/core/management/commands/schedule_join_rooms.py b/src/core/management/commands/schedule_join_rooms.py
index 100e10c8f..a9d4b9cd6 100644
--- a/src/core/management/commands/schedule_join_rooms.py
+++ b/src/core/management/commands/schedule_join_rooms.py
@@ -1,7 +1,7 @@
 from django.core.management.base import BaseCommand, CommandError
 from django.db import transaction
 
-from ...models import Room, Event, ScheduleSourceMapping, RoomLink
+from ...models import Event, Room, RoomLink, ScheduleSourceMapping
 
 
 class Command(BaseCommand):
diff --git a/src/core/management/commands/serviceusers.py b/src/core/management/commands/serviceusers.py
index a898830b6..2d857bf11 100644
--- a/src/core/management/commands/serviceusers.py
+++ b/src/core/management/commands/serviceusers.py
@@ -1,4 +1,5 @@
 from rest_framework.authtoken.models import Token
+
 from django.core.management.base import BaseCommand
 
 from ...models.users import PlatformUser
diff --git a/src/core/management/commands/stats.py b/src/core/management/commands/stats.py
index 84f09ee9b..08368fe71 100644
--- a/src/core/management/commands/stats.py
+++ b/src/core/management/commands/stats.py
@@ -8,15 +8,21 @@ class Command(BaseCommand):
         for conf in Conference.objects.all():
             print('#', conf.slug, '(' + conf.name + ')')
 
-            print('- assemblies:',
-                  Assembly.objects.filter(conference=conf).count(), 'total,',
-                  Assembly.objects.filter(conference=conf, state__in=Assembly.PUBLIC_STATES).count(), 'accepted',
-                  )
-
-            print('- events:',
-                  Event.objects.filter(conference=conf).count(), 'total,',
-                  Event.objects.filter(conference=conf, is_public=True).count(), 'visible',
-                  )
+            print(
+                '- assemblies:',
+                Assembly.objects.filter(conference=conf).count(),
+                'total,',
+                Assembly.objects.filter(conference=conf, state__in=Assembly.PUBLIC_STATES).count(),
+                'accepted',
+            )
+
+            print(
+                '- events:',
+                Event.objects.filter(conference=conf).count(),
+                'total,',
+                Event.objects.filter(conference=conf, is_public=True).count(),
+                'visible',
+            )
 
             print('- rooms:', Room.objects.filter(conference=conf).count(), 'total')
             for rt in Room.BACKEND_ROOMTYPES:
diff --git a/src/core/management/commands/suggestion_tick.py b/src/core/management/commands/suggestion_tick.py
index 87336d54d..534ef0dbc 100644
--- a/src/core/management/commands/suggestion_tick.py
+++ b/src/core/management/commands/suggestion_tick.py
@@ -1,10 +1,10 @@
+import time
+
 from django.core.management.base import BaseCommand
-from django.db import transaction, connection
+from django.db import connection, transaction
 
 from core.models import Assembly, Conference
 
-import time
-
 
 class Command(BaseCommand):
     def handle(self, *args, **options):
@@ -12,30 +12,36 @@ class Command(BaseCommand):
 
         start_time = time.time()
         with transaction.atomic(), connection.cursor() as cursor:
-            cursor.execute('''
+            cursor.execute(
+                """
                     SELECT e.id, lk.likes
                     FROM core_event AS e INNER JOIN core_eventlikecount AS lk ON e.id = lk.event1_id AND e.id=lk.event2_id
                     WHERE e.conference_id = %s AND is_public
-            ''', [conf.pk])
+            """,
+                [conf.pk],
+            )
             for row in cursor.fetchall():
                 if row[1] == 0:
-                    cursor.execute("UPDATE core_eventlikecount SET like_ratio=0 WHERE event1_id=%s", [row[0]])
+                    cursor.execute('UPDATE core_eventlikecount SET like_ratio=0 WHERE event1_id=%s', [row[0]])
                 else:
-                    cursor.execute("UPDATE core_eventlikecount SET like_ratio=likes*1000/%s WHERE event1_id=%s", [row[1], row[0]])
+                    cursor.execute('UPDATE core_eventlikecount SET like_ratio=likes*1000/%s WHERE event1_id=%s', [row[1], row[0]])
         ts = time.time() - start_time
-        print("suggestion_tick (events) run took %f Seconds for Conference %s." % (ts, conf))
+        print(f'suggestion_tick (events) run took {ts:f} Seconds for Conference {conf}.')
 
         start_time = time.time()
         with transaction.atomic(), connection.cursor() as cursor:
-            cursor.execute(f'''
+            cursor.execute(
+                f"""
                     SELECT a.id, lk.likes
                     FROM core_assembly AS a INNER JOIN core_assemblylikecount AS lk ON a.id = lk.assembly1_id AND a.id=lk.assembly2_id
                     WHERE a.conference_id = %s AND state IN ({','.join(['%s'] * len(Assembly.PUBLIC_STATES))})
-            ''', [conf.pk, *Assembly.PUBLIC_STATES])
+            """,
+                [conf.pk, *Assembly.PUBLIC_STATES],
+            )
             for row in cursor.fetchall():
                 if row[1] == 0:
-                    cursor.execute("UPDATE core_assemblylikecount SET like_ratio=0 WHERE assembly1_id=%s", [row[0]])
+                    cursor.execute('UPDATE core_assemblylikecount SET like_ratio=0 WHERE assembly1_id=%s', [row[0]])
                 else:
-                    cursor.execute("UPDATE core_assemblylikecount SET like_ratio=likes*1000/%s WHERE assembly1_id=%s", [row[1], row[0]])
+                    cursor.execute('UPDATE core_assemblylikecount SET like_ratio=likes*1000/%s WHERE assembly1_id=%s', [row[1], row[0]])
         ts = time.time() - start_time
-        print("suggestion_tick (events) run took %f Seconds for Conference %s." % (ts, conf))
+        print(f'suggestion_tick (events) run took {ts:f} Seconds for Conference {conf}.')
diff --git a/src/core/markdown.py b/src/core/markdown.py
index bd2e27b93..9381fb40f 100644
--- a/src/core/markdown.py
+++ b/src/core/markdown.py
@@ -1,20 +1,21 @@
+import html
+import re
 from typing import Optional
-from urllib.parse import urlparse, quote
+from urllib.parse import quote, urlparse
 
 import bleach
-import html
-import re
 import mistletoe
-from mistletoe.html_renderer import HTMLRenderer
 from mistletoe.block_token import BlockToken
-from mistletoe.span_token import Link, AutoLink, SpanToken, tokenize_inner
-from modeltranslation.fields import build_localized_fieldname
-from modeltranslation.settings import AVAILABLE_LANGUAGES
+from mistletoe.html_renderer import HTMLRenderer
+from mistletoe.span_token import AutoLink, Link, SpanToken, tokenize_inner
+
 from django.conf import settings
 from django.db.models import Model
+from django.urls import NoReverseMatch, reverse
 from django.utils.safestring import mark_safe
 from django.utils.text import slugify
-from django.urls import reverse, NoReverseMatch
+from modeltranslation.fields import build_localized_fieldname
+from modeltranslation.settings import AVAILABLE_LANGUAGES
 
 from .models import conference
 from .utils import scheme_and_netloc_from_url, url_in_allowlist
@@ -70,7 +71,7 @@ class AlertBlock(BlockToken):
         self.alert_type = match[0]
         self.alert_caption = match[1].strip()[1:-1] if match[1] else None
 
-        super().__init__("".join(match[2]), tokenize_inner)
+        super().__init__(''.join(match[2]), tokenize_inner)
 
     @classmethod
     def start(cls, line):
@@ -80,7 +81,7 @@ class AlertBlock(BlockToken):
     def read(cls, lines):
         first_line = cls.pattern.match(next(lines))
         line_buffer = []
-        while (next_line := lines.peek()) is not None and next_line.startswith("  "):
+        while (next_line := lines.peek()) is not None and next_line.startswith('  '):
             line_buffer.append(next(lines))
 
         return first_line.group(1), first_line.group(2), line_buffer
@@ -96,17 +97,17 @@ class MyHtmlRenderer(HTMLRenderer):
 
     @staticmethod
     def render_html_span(token):
-        if token.content.startswith("<!--"):
+        if token.content.startswith('<!--'):
             # HTML comments can be rendered as-is
             return token.content
         return html.escape(token.content)
 
     def render_alert_block(self, token: AlertBlock) -> str:
-        result = f"<div class=\"mx-1 my-3 alert alert-{token.alert_type}\">"
+        result = f'<div class="mx-1 my-3 alert alert-{token.alert_type}">'
         if token.alert_caption:
-            result += f"<p class=\"fw-bold\">{token.alert_caption}</p>"
+            result += f'<p class="fw-bold">{token.alert_caption}</p>'
         result += self.render_inner(token)
-        result += "</div>\n"
+        result += '</div>\n'
         return result
 
     def __init__(self, conf: 'conference.Conference', result: 'RenderResult', derefer_allowlist: bool = True, *extras, **kwargs):
@@ -143,7 +144,7 @@ class MyHtmlRenderer(HTMLRenderer):
         template = '<a href="{target}"{title}{link_class}>{inner}</a>'
         target = self.escape_url(token.target)
         if token.title:
-            title = ' title="{}"'.format(html.escape(token.title))
+            title = f' title="{html.escape(token.title)}"'
         else:
             title = ''
         inner = self.render_inner(token)
@@ -158,7 +159,7 @@ class MyHtmlRenderer(HTMLRenderer):
 
         template = '<a href="{target}"{link_class}>{inner}</a>'
         if token.mailto:
-            target = 'mailto:{}'.format(token.target)
+            target = f'mailto:{token.target}'
         else:
             target = self.escape_url(token.target)
         inner = self.render_inner(token)
@@ -219,7 +220,7 @@ class MyHtmlRenderer(HTMLRenderer):
 class MySanitizedHtmlRenderer(MyHtmlRenderer):
     @staticmethod
     def render_html_block(token):
-        if token.content.startswith("<!--"):
+        if token.content.startswith('<!--'):
             # HTML comments can be rendered as-is
             return token.content
         return html.escape(token.content)
@@ -250,9 +251,9 @@ class RenderResult:
         self.linked_urls |= other_result.linked_urls
 
 
-def render_markdown_ex(conf: 'conference.Conference', markup: str,
-                       allow_embedded_html: bool = False, sanitize_html: bool = True,
-                       dont_derefer_allowlist: bool = False) -> RenderResult:
+def render_markdown_ex(
+    conf: 'conference.Conference', markup: str, allow_embedded_html: bool = False, sanitize_html: bool = True, dont_derefer_allowlist: bool = False
+) -> RenderResult:
     # remove HTML tags embedded in markdown, unless requested otherwise
     renderer = MyHtmlRenderer if allow_embedded_html else MySanitizedHtmlRenderer
 
@@ -263,17 +264,41 @@ def render_markdown_ex(conf: 'conference.Conference', markup: str,
 
     if sanitize_html:
         cleaner = bleach.Cleaner(
-            tags=[*[
-                'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'dl',
-                'table', 'thead', 'tbody', 'th', 'td', 'tr', 'hr', 'p', 'pre',
-                'img', 'code', 'div', 'span'
-            ], *list(bleach.sanitizer.ALLOWED_TAGS)],
+            tags=[
+                *[
+                    'br',
+                    'h1',
+                    'h2',
+                    'h3',
+                    'h4',
+                    'h5',
+                    'h6',
+                    'ul',
+                    'ol',
+                    'li',
+                    'dl',
+                    'table',
+                    'thead',
+                    'tbody',
+                    'th',
+                    'td',
+                    'tr',
+                    'hr',
+                    'p',
+                    'pre',
+                    'img',
+                    'code',
+                    'div',
+                    'span',
+                ],
+                *list(bleach.sanitizer.ALLOWED_TAGS),
+            ],
             attributes={
                 '*': ['class', 'id'],
                 'img': ['src', 'alt'],
                 **bleach.sanitizer.ALLOWED_ATTRIBUTES,
             },
-            protocols=['http', 'https', 'mailto', 'ftp', 'ftps']
+            protocols=['http', 'https', 'mailto', 'ftp', 'ftps'],
         )
         rendered_markup = cleaner.clean(rendered_markup)
 
@@ -281,39 +306,45 @@ def render_markdown_ex(conf: 'conference.Conference', markup: str,
     return result
 
 
-def render_markdown(conf: 'conference.Conference', markup: str,
-                    allow_embedded_html: bool = False, sanitize_html: bool = True,
-                    dont_derefer_allowlist: bool = False) -> str:
-    render_result = render_markdown_ex(conf, markup,
-                                       allow_embedded_html=allow_embedded_html, sanitize_html=sanitize_html,
-                                       dont_derefer_allowlist=dont_derefer_allowlist)
+def render_markdown(
+    conf: 'conference.Conference', markup: str, allow_embedded_html: bool = False, sanitize_html: bool = True, dont_derefer_allowlist: bool = False
+) -> str:
+    render_result = render_markdown_ex(
+        conf, markup, allow_embedded_html=allow_embedded_html, sanitize_html=sanitize_html, dont_derefer_allowlist=dont_derefer_allowlist
+    )
     return render_result.document
 
 
 def store_relationships(conf, link_source, render_result: RenderResult):
     from django.contrib.contenttypes.models import ContentType
+
     from .models import MarkdownMeta
     from .models.markdown import MetaTypes
 
     src_type = ContentType.objects.get_for_model(link_source)
     src_object_id = link_source.pk
 
-    MarkdownMeta.objects.filter(src_type=src_type, src_object_id=src_object_id) \
-        .exclude(dst_type=MetaTypes.USER_SLUG, dst_object__in=render_result.linked_user_slugs) \
-        .exclude(dst_type=MetaTypes.PAGE_SLUG, dst_object__in=render_result.linked_page_slugs)
+    MarkdownMeta.objects.filter(src_type=src_type, src_object_id=src_object_id).exclude(
+        dst_type=MetaTypes.USER_SLUG, dst_object__in=render_result.linked_user_slugs
+    ).exclude(dst_type=MetaTypes.PAGE_SLUG, dst_object__in=render_result.linked_page_slugs)
 
     MarkdownMeta.objects.bulk_create(
-        [MarkdownMeta(src_type=src_type, src_object_id=src_object_id, dst_type=MetaTypes.USER_SLUG, dst_object=linked_user_slug)
-            for linked_user_slug in render_result.linked_user_slugs] +
-        [MarkdownMeta(src_type=src_type, src_object_id=src_object_id, dst_type=MetaTypes.PAGE_SLUG, dst_object=linked_page_slug)
-            for linked_page_slug in render_result.linked_page_slugs],
-        ignore_conflicts=True
+        [
+            MarkdownMeta(src_type=src_type, src_object_id=src_object_id, dst_type=MetaTypes.USER_SLUG, dst_object=linked_user_slug)
+            for linked_user_slug in render_result.linked_user_slugs
+        ]
+        + [
+            MarkdownMeta(src_type=src_type, src_object_id=src_object_id, dst_type=MetaTypes.PAGE_SLUG, dst_object=linked_page_slug)
+            for linked_page_slug in render_result.linked_page_slugs
+        ],
+        ignore_conflicts=True,
     )
 
 
 def refresh_linking_markdown(conf: 'conference.Conference', link_target):
     from django.contrib.contenttypes.models import ContentType
-    from .models import ConferenceMember, StaticPage, Event, Room, Assembly, BulletinBoardEntry, MarkdownMeta
+
+    from .models import Assembly, BulletinBoardEntry, ConferenceMember, Event, MarkdownMeta, Room, StaticPage
     from .models.markdown import MetaTypes
 
     if isinstance(link_target, StaticPage):
@@ -323,7 +354,7 @@ def refresh_linking_markdown(conf: 'conference.Conference', link_target):
         dst_type = MetaTypes.USER_SLUG
         dst_object = link_target.user.slug
     else:
-        raise Exception("Unsupported link_target type %r" % (type(link_target)))
+        raise Exception('Unsupported link_target type %r' % (type(link_target)))
 
     relateds_by_type = {}
     for src_type_id, src_object_id in MarkdownMeta.objects.filter(dst_type=dst_type, dst_object=dst_object).values_list('src_type_id', 'src_object_id'):
@@ -333,7 +364,9 @@ def refresh_linking_markdown(conf: 'conference.Conference', link_target):
         static_page.body_html = render_markdown(conf, static_page.body)
         static_page.save()
 
-    for conference_member in ConferenceMember.objects.filter(conference=conf, uuid__in=relateds_by_type.get(ContentType.objects.get_for_model(ConferenceMember).pk, [])):  # noqa: E501
+    for conference_member in ConferenceMember.objects.filter(
+        conference=conf, uuid__in=relateds_by_type.get(ContentType.objects.get_for_model(ConferenceMember).pk, [])
+    ):  # noqa: E501
         conference_member.save(update_fields=['description'])
 
     for event in Event.objects.filter(conference=conf, pk__in=relateds_by_type.get(ContentType.objects.get_for_model(Event).pk, [])):
@@ -345,15 +378,15 @@ def refresh_linking_markdown(conf: 'conference.Conference', link_target):
     for assembly in Assembly.objects.filter(conference=conf, pk__in=relateds_by_type.get(ContentType.objects.get_for_model(Assembly).pk, [])):
         assembly.save(update_fields=['description'])
 
-    for assembly in BulletinBoardEntry.objects.filter(conference=conf, pk__in=relateds_by_type.get(ContentType.objects.get_for_model(BulletinBoardEntry).pk, [])):  # noqa: E501
+    for assembly in BulletinBoardEntry.objects.filter(
+        conference=conf, pk__in=relateds_by_type.get(ContentType.objects.get_for_model(BulletinBoardEntry).pk, [])
+    ):  # noqa: E501
         assembly.save(update_fields=['description'])
 
 
-def compile_translated_markdown_fields(obj: Model,
-                                       conf: 'conference.Conference',
-                                       field_name: str,
-                                       dst_obj: Optional[Model] = None,
-                                       dst_field_name: Optional[str] = None) -> RenderResult:
+def compile_translated_markdown_fields(
+    obj: Model, conf: 'conference.Conference', field_name: str, dst_obj: Optional[Model] = None, dst_field_name: Optional[str] = None
+) -> RenderResult:
     if dst_obj is None:
         dst_obj = obj
     if dst_field_name is None:
diff --git a/src/core/middleware.py b/src/core/middleware.py
index cd0eabb6b..f7a8ff02d 100644
--- a/src/core/middleware.py
+++ b/src/core/middleware.py
@@ -5,7 +5,6 @@ from django.conf import settings
 from django.contrib import messages
 from django.utils import timezone
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -36,11 +35,11 @@ class SetRemoteAddrMiddleware:
             try:
                 real_ip = request.META[lookup_header]
             except KeyError:
-                logger.debug("Could not load client ip from %r in request from %r. Misconfigured Proxy?", lookup_header, request.META['REMOTE_ADDR'])
+                logger.debug('Could not load client ip from %r in request from %r. Misconfigured Proxy?', lookup_header, request.META['REMOTE_ADDR'])
             else:
                 # HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs. The
                 # client's IP will be the first one.
-                real_ip = real_ip.split(",")[0].strip()
+                real_ip = real_ip.split(',')[0].strip()
                 m = V6_WITH_V4_PREFIX_REGEX.match(real_ip)
                 if m:
                     real_ip = m.group(1)
diff --git a/src/core/migrations/0114_prepare_primary_keys_badge_badgetoken.py b/src/core/migrations/0114_prepare_primary_keys_badge_badgetoken.py
index 4242b2dfb..e0b2c80c1 100644
--- a/src/core/migrations/0114_prepare_primary_keys_badge_badgetoken.py
+++ b/src/core/migrations/0114_prepare_primary_keys_badge_badgetoken.py
@@ -26,6 +26,6 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='badge',
             name='id',
-            field=models.CharField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID',  max_length=36),
+            field=models.CharField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID', max_length=36),
         ),
     ]
diff --git a/src/core/models/__init__.py b/src/core/models/__init__.py
index d5622d7ed..f9086f16d 100644
--- a/src/core/models/__init__.py
+++ b/src/core/models/__init__.py
@@ -1,43 +1,70 @@
 from .assemblies import Assembly, AssemblyLikeCount, AssemblyLink, AssemblyLogEntry, AssemblyMember
 from .badges import Badge, BadgeCategory, BadgeToken, BadgeTokenTimeConstraint, UserBadge
-from .conference import Conference, ConferenceExportCache, ConferenceMember, ConferenceNavigationItem, ConferenceTrack
-from .dereferrer import UserDereferrerAllowlist, DereferrerStats
 from .board import BulletinBoardEntry
+from .conference import Conference, ConferenceExportCache, ConferenceMember, ConferenceNavigationItem, ConferenceTrack
+from .dereferrer import DereferrerStats, UserDereferrerAllowlist
 from .events import Event, EventAttachment, EventLikeCount, EventParticipant
-from .pages import StaticPage, StaticPageRevision, StaticPageNamespace
-from .markdown import MarkdownMeta
 from .map import MapFloor, MapPOI
+from .markdown import MarkdownMeta
 from .messages import DirectMessage
 from .metanavi import MetaNavItem
+from .pages import StaticPage, StaticPageNamespace, StaticPageRevision
 from .rooms import Room, RoomLink
 from .schedules import ScheduleSource, ScheduleSourceImport, ScheduleSourceMapping
 from .shared import BackendMixin
 from .sso import Application
 from .tags import ConferenceTag, TagItem
 from .ticket import ConferenceMemberTicket
-from .users import UserCommunicationChannel, UserContact, PlatformUser
+from .users import PlatformUser, UserCommunicationChannel, UserContact
 from .voucher import Voucher, VoucherEntry
 from .workadventure import WorkadventureSession, WorkadventureTexture
 
 __all__ = [
     'Application',
-    'Assembly', 'AssemblyLikeCount', 'AssemblyLink', 'AssemblyLogEntry', 'AssemblyMember',
-    'BackendMixin', 'Badge', 'BadgeCategory', 'BadgeToken', 'BadgeTokenTimeConstraint', 'BulletinBoardEntry',
-    'Conference', 'ConferenceExportCache',
-    'ConferenceMember', 'ConferenceMemberTicket',
+    'Assembly',
+    'AssemblyLikeCount',
+    'AssemblyLink',
+    'AssemblyLogEntry',
+    'AssemblyMember',
+    'BackendMixin',
+    'Badge',
+    'BadgeCategory',
+    'BadgeToken',
+    'BadgeTokenTimeConstraint',
+    'BulletinBoardEntry',
+    'Conference',
+    'ConferenceExportCache',
+    'ConferenceMember',
+    'ConferenceMemberTicket',
     'ConferenceNavigationItem',
-    'ConferenceTag', 'ConferenceTrack',
-    'DereferrerStats', 'DirectMessage',
-    'Event', 'EventAttachment', 'EventLikeCount', 'EventParticipant',
-    'MapFloor', 'MapPOI',
+    'ConferenceTag',
+    'ConferenceTrack',
+    'DereferrerStats',
+    'DirectMessage',
+    'Event',
+    'EventAttachment',
+    'EventLikeCount',
+    'EventParticipant',
+    'MapFloor',
+    'MapPOI',
     'MetaNavItem',
     'PlatformUser',
-    'Room', 'RoomLink',
-    'ScheduleSource', 'ScheduleSourceImport', 'ScheduleSourceMapping',
-    'StaticPage', 'StaticPageRevision', 'StaticPageNamespace',
+    'Room',
+    'RoomLink',
+    'ScheduleSource',
+    'ScheduleSourceImport',
+    'ScheduleSourceMapping',
+    'StaticPage',
+    'StaticPageRevision',
+    'StaticPageNamespace',
     'MarkdownMeta',
     'TagItem',
-    'UserContact', 'UserCommunicationChannel', 'UserBadge', 'UserDereferrerAllowlist',
-    'Voucher', 'VoucherEntry',
-    'WorkadventureSession', 'WorkadventureTexture'
+    'UserContact',
+    'UserCommunicationChannel',
+    'UserBadge',
+    'UserDereferrerAllowlist',
+    'Voucher',
+    'VoucherEntry',
+    'WorkadventureSession',
+    'WorkadventureTexture',
 ]
diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py
index 0476d8866..54872d949 100644
--- a/src/core/models/assemblies.py
+++ b/src/core/models/assemblies.py
@@ -12,10 +12,12 @@ from django.db import models
 from django.db.models import Q
 from django.template.loader import render_to_string
 from django.urls import reverse
-from django.utils.html import escape as html_escape, format_html
 from django.utils.functional import cached_property
+from django.utils.html import escape as html_escape
+from django.utils.html import format_html
 from django.utils.safestring import mark_safe
-from django.utils.translation import gettext, gettext_lazy as _
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
 
 from core.validators import FileSizeValidator, ImageDimensionValidator
 
@@ -74,8 +76,11 @@ class AssemblyManager(models.Manager):
         return qs
 
     def conference_accessible(self, conference: Conference):
-        return self.get_queryset().filter(conference=conference) \
+        return (
+            self.get_queryset()
+            .filter(conference=conference)
             .filter(Q(state_assembly__in=self.model.PUBLIC_STATES) | Q(state_channel__in=self.model.PUBLIC_STATES))
+        )
 
     def manageable_by_user(self, user: PlatformUser, conference: Conference, staff_can_manage=True):
         assert user is not None
@@ -108,7 +113,7 @@ class AssemblyManager(models.Manager):
         return qs
 
 
-def get_banner_file_name(instance: "Assembly", filename: str):
+def get_banner_file_name(instance: 'Assembly', filename: str):
     return str(Path(str(instance.id)).joinpath('banner', filename))
 
 
@@ -141,18 +146,10 @@ class Assembly(TaggedItemMixin, models.Model):
 
     id = models.UUIDField(default=uuid4, primary_key=True, editable=False)
     conference = ConferenceReference(related_name='assemblies')
-    slug = models.SlugField(
-        help_text=_('Assembly__slug__help'),
-        verbose_name=_('Assembly__slug'))
-    name = models.CharField(
-        max_length=200,
-        help_text=_('Assembly__name__help'),
-        verbose_name=_('Assembly__name'))
-
-    is_official = models.BooleanField(
-        default=False,
-        help_text=_('Assembly__is_official__help'),
-        verbose_name=_('Assembly__is_official'))
+    slug = models.SlugField(help_text=_('Assembly__slug__help'), verbose_name=_('Assembly__slug'))
+    name = models.CharField(max_length=200, help_text=_('Assembly__name__help'), verbose_name=_('Assembly__name'))
+
+    is_official = models.BooleanField(default=False, help_text=_('Assembly__is_official__help'), verbose_name=_('Assembly__is_official'))
 
     banner_image_height = models.PositiveIntegerField(blank=True, null=True)
     banner_image_width = models.PositiveIntegerField(blank=True, null=True)
@@ -184,89 +181,71 @@ class Assembly(TaggedItemMixin, models.Model):
         ],
     )
 
-    description = models.TextField(
-        blank=True, null=True,
-        help_text=_('Assembly__description__help'),
-        verbose_name=_('Assembly__description'))
+    description = models.TextField(blank=True, null=True, help_text=_('Assembly__description__help'), verbose_name=_('Assembly__description'))
     description_html = models.TextField(blank=True, null=True)
     registration_details = models.TextField(
-        blank=True, null=True,
-        help_text=_('Assembly__registration_details__help'),
-        verbose_name=_('Assembly__registration_details'))
+        blank=True, null=True, help_text=_('Assembly__registration_details__help'), verbose_name=_('Assembly__registration_details')
+    )
 
-    registration_data = models.JSONField(
-        blank=True, null=True,
-        help_text=_('Assembly__registration_data__help'),
-        verbose_name=_('Assembly__registration_data'))
+    registration_data = models.JSONField(blank=True, null=True, help_text=_('Assembly__registration_data__help'), verbose_name=_('Assembly__registration_data'))
 
     state_assembly = models.CharField(
-        max_length=20, default=State.NONE, choices=State.choices,
-        verbose_name=_('Assembly__state_assembly'),
-        help_text=_('Assembly__state_assembly__help'))
+        max_length=20, default=State.NONE, choices=State.choices, verbose_name=_('Assembly__state_assembly'), help_text=_('Assembly__state_assembly__help')
+    )
     state_channel = models.CharField(
-        max_length=20, default=State.NONE, choices=State.choices,
-        verbose_name=_('Assembly__state_channel'),
-        help_text=_('Assembly__state_channel__help'))
+        max_length=20, default=State.NONE, choices=State.choices, verbose_name=_('Assembly__state_channel'), help_text=_('Assembly__state_channel__help')
+    )
 
     hierarchy = models.CharField(
-        max_length=20, default=Hierarchy.REGULAR, choices=Hierarchy.choices,
-        help_text=_('Assembly__hierarchy__help'),
-        verbose_name=_('Assembly__hierarchy'))
+        max_length=20, default=Hierarchy.REGULAR, choices=Hierarchy.choices, help_text=_('Assembly__hierarchy__help'), verbose_name=_('Assembly__hierarchy')
+    )
     parent = models.ForeignKey(
-        'self', blank=True, null=True, related_name='+', on_delete=models.PROTECT,
-        verbose_name=_('Assembly__parent'),
-        help_text=_('Assembly__parent__help'))
+        'self', blank=True, null=True, related_name='+', on_delete=models.PROTECT, verbose_name=_('Assembly__parent'), help_text=_('Assembly__parent__help')
+    )
 
     technical_user = models.ForeignKey(
         PlatformUser,
-        blank=True, null=True,
-        related_name='+', on_delete=models.PROTECT,
+        blank=True,
+        null=True,
+        related_name='+',
+        on_delete=models.PROTECT,
         verbose_name=_('Assembly__technical_user'),
-        help_text=_('Assembly__technical_user__help'))
-
-    is_virtual = models.BooleanField(
-        blank=True, null=True,
-        verbose_name=_('Assembly__is_virtual'),
-        help_text=_('Assembly__is_virtual__help'))
-    is_physical = models.BooleanField(
-        blank=True, null=True,
-        verbose_name=_('Assembly__is_physical'),
-        help_text=_('Assembly__is_physical__help'))
-    is_remote = models.BooleanField(
-        blank=True, null=True,
-        verbose_name=_('Assembly__is_remote'),
-        help_text=_('Assembly__is_remote__help'))
-
-    assembly_location = models.TextField(
-        blank=True, null=True,
-        verbose_name=_('Assembly__location'),
-        help_text=_('Assembly__location__help'))
-    assembly_link = models.URLField(
-        blank=True, null=True,
-        verbose_name=_('Assembly__link'),
-        help_text=_('Assembly__link__help'))
+        help_text=_('Assembly__technical_user__help'),
+    )
+
+    is_virtual = models.BooleanField(blank=True, null=True, verbose_name=_('Assembly__is_virtual'), help_text=_('Assembly__is_virtual__help'))
+    is_physical = models.BooleanField(blank=True, null=True, verbose_name=_('Assembly__is_physical'), help_text=_('Assembly__is_physical__help'))
+    is_remote = models.BooleanField(blank=True, null=True, verbose_name=_('Assembly__is_remote'), help_text=_('Assembly__is_remote__help'))
+
+    assembly_location = models.TextField(blank=True, null=True, verbose_name=_('Assembly__location'), help_text=_('Assembly__location__help'))
+    assembly_link = models.URLField(blank=True, null=True, verbose_name=_('Assembly__link'), help_text=_('Assembly__link__help'))
     public_contact = models.EmailField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('Assembly__public_contact__help'),
         verbose_name=_('Assembly__public_contact'),
     )
 
     location_floor = models.ForeignKey(
         MapFloor,
-        blank=True, null=True,
-        related_name='assemblies', on_delete=models.PROTECT,
+        blank=True,
+        null=True,
+        related_name='assemblies',
+        on_delete=models.PROTECT,
         help_text=_('Assembly__location_floor__help'),
         verbose_name=_('Assembly__location_floor'),
     )
 
     location_point = gis_models.PointField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('Assembly__location_point__help'),
         verbose_name=_('Assembly__location_point'),
     )
 
     location_boundaries = gis_models.MultiPolygonField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('Assembly__location_boundaries__help'),
         verbose_name=_('Assembly__location_boundaries'),
     )
@@ -277,21 +256,20 @@ class Assembly(TaggedItemMixin, models.Model):
         verbose_name=_('Assembly__created'),
     )
     last_update_assembly = models.DateTimeField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('Assembly__last_update_assembly__help'),
         verbose_name=_('Assembly__last_update_assembly'),
     )
     last_update_staff = models.DateTimeField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('Assembly__last_update_staff__help'),
         verbose_name=_('Assembly__last_update_staff'),
     )
 
     favorited_by = models.ManyToManyField(
-        PlatformUser,
-        related_name='favorite_assemblies',
-        help_text=_('Assembly__favorited_by__help'),
-        verbose_name=_('Assembly__favorited_by')
+        PlatformUser, related_name='favorite_assemblies', help_text=_('Assembly__favorited_by__help'), verbose_name=_('Assembly__favorited_by')
     )
 
     PUBLIC_STATES = [
@@ -433,9 +411,11 @@ class Assembly(TaggedItemMixin, models.Model):
 
     @property
     def basedata_readonly(self):
-        return \
-            self.state_assembly not in [Assembly.State.NONE, Assembly.State.PLANNED, Assembly.State.REGISTERED] or \
-            self.state_channel not in [Assembly.State.NONE, Assembly.State.PLANNED, Assembly.State.REGISTERED]
+        return self.state_assembly not in [Assembly.State.NONE, Assembly.State.PLANNED, Assembly.State.REGISTERED] or self.state_channel not in [
+            Assembly.State.NONE,
+            Assembly.State.PLANNED,
+            Assembly.State.REGISTERED,
+        ]
 
     @cached_property
     def sorted_tags(self):
@@ -522,6 +502,7 @@ class Assembly(TaggedItemMixin, models.Model):
 
     def get_voucher_count(self, with_always_public: bool = True) -> Optional[int]:
         from .voucher import Voucher
+
         entries = Voucher.objects.for_assembly(self, include_public_ones=False).count()
 
         # if we shall not pay respect to Vouchers available to everyone, just return the number here
@@ -718,10 +699,7 @@ class AssemblyMember(models.Model):
         verbose_name=_('AssemblyMember__is_technical_contact'),
     )
 
-    show_public = models.BooleanField(
-        default=True,
-        help_text=_('AssemblyMember__show_public__help'),
-        verbose_name=_('AssemblyMember__show_public'))
+    show_public = models.BooleanField(default=True, help_text=_('AssemblyMember__show_public__help'), verbose_name=_('AssemblyMember__show_public'))
 
     @property
     def roles(self):
@@ -756,10 +734,9 @@ class AssemblyMember(models.Model):
 
 class AssemblyLikeCount(models.Model):
     class Meta:
-        indexes = [
-            models.Index(fields=['assembly1', 'like_ratio'])
-        ]
+        indexes = [models.Index(fields=['assembly1', 'like_ratio'])]
         unique_together = [('assembly1', 'assembly2')]
+
     assembly1 = models.ForeignKey(Assembly, related_name='suggestions', on_delete=models.CASCADE)
     assembly2 = models.ForeignKey(Assembly, related_name='+', on_delete=models.CASCADE)
     likes = models.IntegerField()
@@ -783,35 +760,45 @@ class AssemblyLogEntry(models.Model):
         """Logged activity by an assembly member."""
 
     assembly = models.ForeignKey(
-        Assembly, related_name='logentries', on_delete=models.CASCADE,
-        help_text=_('AssemblyLogEntry__assembly__help'), verbose_name=_('AssemblyLogEntry__assembly'),
+        Assembly,
+        related_name='logentries',
+        on_delete=models.CASCADE,
+        help_text=_('AssemblyLogEntry__assembly__help'),
+        verbose_name=_('AssemblyLogEntry__assembly'),
     )
     user = models.ForeignKey(
         PlatformUser,
-        blank=True, null=True, on_delete=models.SET_NULL,
-        help_text=_('AssemblyLogEntry__user__help'), verbose_name=_('AssemblyLogEntry__user'),
+        blank=True,
+        null=True,
+        on_delete=models.SET_NULL,
+        help_text=_('AssemblyLogEntry__user__help'),
+        verbose_name=_('AssemblyLogEntry__user'),
     )
     timestamp = models.DateTimeField(
-        auto_now_add=True, editable=False,
-        help_text=_('AssemblyLogEntry__timestamp__help'), verbose_name=_('AssemblyLogEntry__timestamp'),
+        auto_now_add=True,
+        editable=False,
+        help_text=_('AssemblyLogEntry__timestamp__help'),
+        verbose_name=_('AssemblyLogEntry__timestamp'),
     )
     kind = models.CharField(
-        max_length=20, choices=Kind.choices,
-        help_text=_('AssemblyLogEntry__kind__help'), verbose_name=_('AssemblyLogEntry__kind'),
+        max_length=20,
+        choices=Kind.choices,
+        help_text=_('AssemblyLogEntry__kind__help'),
+        verbose_name=_('AssemblyLogEntry__kind'),
     )
 
     changes = models.JSONField(
-        blank=True, null=True,
-        help_text=_('AssemblyLogEntry__changes__help'), verbose_name=_('AssemblyLogEntry__changes'),
+        blank=True,
+        null=True,
+        help_text=_('AssemblyLogEntry__changes__help'),
+        verbose_name=_('AssemblyLogEntry__changes'),
     )
     comment = models.TextField(
-        blank=True, null=True,
-        help_text=_('AssemblyLogEntry__comment__help'), verbose_name=_('AssemblyLogEntry__comment'),
+        blank=True,
+        null=True,
+        help_text=_('AssemblyLogEntry__comment__help'),
+        verbose_name=_('AssemblyLogEntry__comment'),
     )
 
     def __str__(self):
-        return (
-                f'{self.timestamp} [{self.kind}]' +
-                (f' @{self.user.username}' if self.user else '') +
-                (f' "{self.comment}"' if self.comment else '')
-            )
+        return f'{self.timestamp} [{self.kind}]' + (f' @{self.user.username}' if self.user else '') + (f' "{self.comment}"' if self.comment else '')
diff --git a/src/core/models/badges.py b/src/core/models/badges.py
index 0943a2ef6..0dc080346 100644
--- a/src/core/models/badges.py
+++ b/src/core/models/badges.py
@@ -4,6 +4,8 @@ from pathlib import Path
 from string import ascii_lowercase, digits
 from uuid import uuid4
 
+from segno import make as segno_make
+
 from django.conf import settings
 from django.contrib.postgres.fields import DateTimeRangeField
 from django.core.exceptions import ValidationError
@@ -14,7 +16,6 @@ from django.db import models
 from django.template.loader import render_to_string
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
-from segno import make as segno_make
 
 from core.markdown import compile_translated_markdown_fields, store_relationships
 from core.validators import ImageDimensionValidator
diff --git a/src/core/models/board.py b/src/core/models/board.py
index bdf8cd79d..f65169149 100644
--- a/src/core/models/board.py
+++ b/src/core/models/board.py
@@ -1,8 +1,8 @@
-from django.db import models
 from django.conf import settings
+from django.db import models
 
-from core.markdown import compile_translated_markdown_fields, store_relationships
 from core.fields import ConferenceReference
+from core.markdown import compile_translated_markdown_fields, store_relationships
 
 
 class BulletinBoardEntry(models.Model):
diff --git a/src/core/models/conference.py b/src/core/models/conference.py
index 4ae3fa0ea..7a28185e1 100644
--- a/src/core/models/conference.py
+++ b/src/core/models/conference.py
@@ -2,10 +2,11 @@ import logging
 import typing
 import xml.etree.ElementTree as ET
 from collections import OrderedDict
+from datetime import datetime, time, timedelta
 from json import JSONEncoder
-
 from uuid import uuid4
-from datetime import datetime, time, timedelta
+
+from timezone_field import TimeZoneField
 
 from django.conf import settings
 from django.contrib.auth.models import Group
@@ -14,21 +15,20 @@ from django.core.exceptions import ValidationError
 from django.core.validators import URLValidator, validate_slug
 from django.db import models
 from django.db.models import Max
-from django.http import HttpRequest, HttpResponseNotModified, HttpResponse
-from django.urls import reverse, NoReverseMatch
+from django.http import HttpRequest, HttpResponse, HttpResponseNotModified
+from django.urls import NoReverseMatch, reverse
 from django.utils import timezone
 from django.utils.functional import cached_property
 from django.utils.html import format_html
 from django.utils.safestring import mark_safe
 from django.utils.timezone import now
-from django.utils.translation import get_language, gettext_lazy as _
-from timezone_field import TimeZoneField
+from django.utils.translation import get_language
+from django.utils.translation import gettext_lazy as _
 
 from ..fields import ConferenceReference
 from ..utils import render_markdown_as_text
 from .users import PlatformUser
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -46,59 +46,41 @@ class ConferenceMember(models.Model):
     active_angel = models.BooleanField(default=False)
     roles = pg_fields.ArrayField(models.CharField(max_length=50), blank=True, null=True)
     is_staff = models.BooleanField(default=False)
-    has_ticket = models.BooleanField(
-        default=False,
-        help_text=_('ConferenceMember--has_ticket--help'),
-        verbose_name=_('ConferenceMember--has_ticket'))
+    has_ticket = models.BooleanField(default=False, help_text=_('ConferenceMember--has_ticket--help'), verbose_name=_('ConferenceMember--has_ticket'))
 
     permission_groups = models.ManyToManyField(Group, blank=True, related_name='+')
     static_page_groups = pg_fields.ArrayField(models.CharField(max_length=50), blank=True, null=True)
 
-    description = models.TextField(
-        blank=True, null=True,
-        help_text=_('PlatformUser__description__help'),
-        verbose_name=_('PlatformUser__description'))
+    description = models.TextField(blank=True, null=True, help_text=_('PlatformUser__description__help'), verbose_name=_('PlatformUser__description'))
     description_html = models.TextField(blank=True, null=True)
 
     class Meta:
         permissions = [
             ('assembly_team', _('ConferenceMember__permission-assembly_team')),
             # See all assemblies, not only the accepted ones.
-
-            ('channel_team', _("ConferenceMember__permission-channel_team")),
+            ('channel_team', _('ConferenceMember__permission-channel_team')),
             # Channelteam, Assemblyteam for Channel-Type Assemblies (Assemblies that provide their own content)
-
             ('static_pages', _('ConferenceMember__permission-static_pages')),
             # Access to static pages, can be further limited by configuring static_page_groups.
-
             ('map_edit', _('ConferenceMember__permission-map_edit')),
             # modification of the map, i.e. POIs
-
             ('platformusers', 'Orga: Users List'),
             ('rename_platformuser', 'Orga: Rename User'),
-
             ('block_platformuser', _('ConferenceMember__permission-block_platformuser')),
             # This is the right to block a misbehaving user.
-
             ('change_conferencemember__active_angel', _('ConferenceMember__permission-change_conferencemember__active_angel')),
             # Permission to set the user's is-currently-on-duty flag.
-
             ('view_platformuser__guardian', _('ConferenceMember__permission-view_platformuser__guardian')),
             # See the guardian(s) of a user.
-
             ('voucher_admin', _('ConferenceMember__permission-voucher_admin')),
             # configure conference's vouchers
-
             ('scheduleadmin', _('ConferenceMember__permission-scheduleadmin')),
             # manage/support ScheduleSupports globally, i.e. without access to an individual assembly
-
             ('workadventure_admin', _('ConferenceMember__permission-workadventure_admin')),
             # manage/support all WA rooms and backend info
         ]
 
-        constraints = [
-            models.constraints.UniqueConstraint(fields=['user', 'conference'], name='conferencemember_user_conference_unique')
-        ]
+        constraints = [models.constraints.UniqueConstraint(fields=['user', 'conference'], name='conferencemember_user_conference_unique')]
 
     def get_permission_groups_permissions(self):
         """Returns only the permission codenames which are given via the associated permission_groups of this ConferenceMember instance."""
@@ -193,88 +175,59 @@ class Conference(models.Model):
     objects = ConferenceManager()
 
     id = models.UUIDField(default=uuid4, primary_key=True, editable=False)
-    slug = models.SlugField(
-        unique=True,
-        help_text=_('Conference__slug__help'),
-        verbose_name=_('Conference__slug'))
-    name = models.CharField(
-        max_length=200,
-        help_text=_('Conference__name__help'),
-        verbose_name=_('Conference__name'))
+    slug = models.SlugField(unique=True, help_text=_('Conference__slug__help'), verbose_name=_('Conference__slug'))
+    name = models.CharField(max_length=200, help_text=_('Conference__name__help'), verbose_name=_('Conference__name'))
 
-    is_public = models.BooleanField(
-        default=False,
-        help_text=_('Conference__is_public__help'),
-        verbose_name=_('Conference__is_public'))
+    is_public = models.BooleanField(default=False, help_text=_('Conference__is_public__help'), verbose_name=_('Conference__is_public'))
     registration_deadline = models.DateTimeField(
-        blank=True, null=True,
-        help_text=_('Conference__registration_deadline__help'),
-        verbose_name=_('Conference__registration_deadline'))
-
-    start = models.DateTimeField(
-        blank=True, null=True,
-        help_text=_('Conference__start__help'),
-        verbose_name=_('Conference__start'))
-    end = models.DateTimeField(
-        blank=True, null=True,
-        help_text=_('Conference__end__help'),
-        verbose_name=_('Conference__end'))
+        blank=True, null=True, help_text=_('Conference__registration_deadline__help'), verbose_name=_('Conference__registration_deadline')
+    )
+
+    start = models.DateTimeField(blank=True, null=True, help_text=_('Conference__start__help'), verbose_name=_('Conference__start'))
+    end = models.DateTimeField(blank=True, null=True, help_text=_('Conference__end__help'), verbose_name=_('Conference__end'))
 
     timezone = TimeZoneField(
-        choices_display="WITH_GMT_OFFSET",
-        default="UTC",
-        help_text=_('Conference__timezone__help'),
-        verbose_name=_('Conference__timezone'))
+        choices_display='WITH_GMT_OFFSET', default='UTC', help_text=_('Conference__timezone__help'), verbose_name=_('Conference__timezone')
+    )
     global_notification = models.TextField(
-        default='', blank=True,
-        help_text=_('Conference__global_notification__help'),
-        verbose_name=_('Conference__global_notification'))
+        default='', blank=True, help_text=_('Conference__global_notification__help'), verbose_name=_('Conference__global_notification')
+    )
 
     logo = models.TextField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('Conference__logo__help'),
         verbose_name=_('Conference__logo'),
     )
     """SVG logo"""
 
-    send_pn_disabled = models.BooleanField(
-        default=False,
-        help_text=_('Conference__send_pn_disabled__help'),
-        verbose_name=_('Conference__send_pn_disabled'))
-    board_disabled = models.BooleanField(
-        default=False,
-        help_text=_('Conference__board_disabled__help'),
-        verbose_name=_('Conference__board_disabled'))
-    require_login = models.BooleanField(
-        default=False,
-        help_text=_('Conference--require_login--help'),
-        verbose_name=_('Conference--require_login'))
-    require_ticket = models.BooleanField(
-        default=False,
-        help_text=_('Conference--require_ticket--help'),
-        verbose_name=_('Conference--require_ticket'))
+    send_pn_disabled = models.BooleanField(default=False, help_text=_('Conference__send_pn_disabled__help'), verbose_name=_('Conference__send_pn_disabled'))
+    board_disabled = models.BooleanField(default=False, help_text=_('Conference__board_disabled__help'), verbose_name=_('Conference__board_disabled'))
+    require_login = models.BooleanField(default=False, help_text=_('Conference--require_login--help'), verbose_name=_('Conference--require_login'))
+    require_ticket = models.BooleanField(default=False, help_text=_('Conference--require_ticket--help'), verbose_name=_('Conference--require_ticket'))
 
-    support_clusters = models.BooleanField(
-        default=True,
-        help_text=_('Conference__support_clusters__help'),
-        verbose_name=_('Conference__support_clusters'))
+    support_clusters = models.BooleanField(default=True, help_text=_('Conference__support_clusters__help'), verbose_name=_('Conference__support_clusters'))
     additional_fields_schema = models.JSONField(
-        blank=True, null=True, encoder=PrettyJSONEncoder,
+        blank=True,
+        null=True,
+        encoder=PrettyJSONEncoder,
         help_text=_('Conference__additional_fields_schema__help'),
-        verbose_name=_('Conference__additional_fields_schema'))
+        verbose_name=_('Conference__additional_fields_schema'),
+    )
 
     disclaimers = models.JSONField(
-        blank=True, null=True, encoder=PrettyJSONEncoder,
-        help_text=_('Conference__disclaimers__help'),
-        verbose_name=_('Conference__disclaimers'))
+        blank=True, null=True, encoder=PrettyJSONEncoder, help_text=_('Conference__disclaimers__help'), verbose_name=_('Conference__disclaimers')
+    )
 
     self_organized_sessions_assembly = models.ForeignKey(
         'Assembly',
-        blank=True, null=True,
+        blank=True,
+        null=True,
         on_delete=models.PROTECT,
         related_name='+',
         help_text=_('Conference__self_organized_sessions_assembly__help'),
-        verbose_name=_('Conference__self_organized_sessions_assembly'))
+        verbose_name=_('Conference__self_organized_sessions_assembly'),
+    )
     """
     Benutzer, die eine Self-Organized-Session (SOS) anlegen wollen,
     können dies entweder in ihrer Assembly tun, in denen sie "Member" sind oder aber in dieser hier.
@@ -305,13 +258,16 @@ class Conference(models.Model):
     )
 
     mail_footer = models.TextField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('Conference__mail_footer__help'),
         verbose_name=_('Conference__mail_footer'),
     )
 
     map_config = models.JSONField(
-        blank=True, null=True, encoder=PrettyJSONEncoder,
+        blank=True,
+        null=True,
+        encoder=PrettyJSONEncoder,
         help_text=_('Conference__map_config__help'),
         verbose_name=_('Conference__map_config'),
     )
@@ -330,11 +286,11 @@ class Conference(models.Model):
                 parsed = ET.fromstring(self.logo)
                 assert parsed.tag in ['svg', '{http://www.w3.org/2000/svg}svg'], 'Not a SVG document.'
 
-                assert 'width' not in parsed.keys() and 'height' not in parsed.keys(), 'SVG has width and/or height attribute.'
+                assert 'width' not in parsed and 'height' not in parsed, 'SVG has width and/or height attribute.'
 
                 xml_start = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
                 if self.logo.startswith(xml_start):
-                    self.logo = self.logo[len(xml_start):].lstrip()
+                    self.logo = self.logo[len(xml_start) :].lstrip()
 
             except ET.ParseError:
                 errors['logo'] = 'SVG (XML) parse error'
@@ -466,27 +422,33 @@ class Conference(models.Model):
         # iterate over the categories (parent entries)
         for parent in self.nav_items.filter(parent=None, is_visible=True).order_by('index'):
             # filter children for this parent entry (they're already sorted) and resolve the URL, if necessary
-            this_children = [{
-                'label': c.label,
-                'title': c.title,
-                'icon': c.icon,
-                'url': c.resolve_url(),
-            } for c in all_children if c.parent_id == parent.id]
+            this_children = [
+                {
+                    'label': c.label,
+                    'title': c.title,
+                    'icon': c.icon,
+                    'url': c.resolve_url(),
+                }
+                for c in all_children
+                if c.parent_id == parent.id
+            ]
 
             # append parent entry, reformatted as dict
-            result.append({
-                'label': parent.label,
-                'title': parent.title,
-                'icon': parent.icon,
-                'children': this_children,
-            })
+            result.append(
+                {
+                    'label': parent.label,
+                    'title': parent.title,
+                    'icon': parent.icon,
+                    'children': this_children,
+                }
+            )
 
         return result
 
     @cached_property
     def logo_html(self):
         if not self.logo:
-            with settings.BASE_DIR.joinpath("hub", "logos", "default.svg").open() as default_logo:
+            with settings.BASE_DIR.joinpath('hub', 'logos', 'default.svg').open() as default_logo:
                 default_logo_content = default_logo.read()
             assert default_logo_content.startswith('<svg'), 'Expected default logo to start with SVG tag.'
             return mark_safe(default_logo_content)
@@ -506,6 +468,7 @@ class Conference(models.Model):
             return None
 
         from ..markdown import render_markdown
+
         return format_html(
             '<div id="footer" style="margin-top: 1em; border-top: 2px solid #999; color: #999; font-size: 80%;">{0}</div>',
             render_markdown(conf=self, markup=self.mail_footer, dont_derefer_allowlist=True),
@@ -558,25 +521,19 @@ class ConferenceTrack(models.Model):
     objects = ConferenceTrackManager()
 
     conference = ConferenceReference(related_name='tracks')
-    slug = models.SlugField(
-        help_text=_('ConferenceTrack__slug__help'),
-        verbose_name=_('ConferenceTrack__slug'))
-    name = models.CharField(
-        max_length=200,
-        help_text=_('ConferenceTrack__name__help'),
-        verbose_name=_('ConferenceTrack__name'))
-    is_public = models.BooleanField(
-        default=True,
-        help_text=_('ConferenceTrack__is_public__help'),
-        verbose_name=_('ConferenceTrack__is_public'))
+    slug = models.SlugField(help_text=_('ConferenceTrack__slug__help'), verbose_name=_('ConferenceTrack__slug'))
+    name = models.CharField(max_length=200, help_text=_('ConferenceTrack__name__help'), verbose_name=_('ConferenceTrack__name'))
+    is_public = models.BooleanField(default=True, help_text=_('ConferenceTrack__is_public__help'), verbose_name=_('ConferenceTrack__is_public'))
     banner_image_height = models.PositiveIntegerField(blank=True, null=True)
     banner_image_width = models.PositiveIntegerField(blank=True, null=True)
     banner_image = models.ImageField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         height_field='banner_image_height',
         width_field='banner_image_width',
         help_text=_('ConferenceTrack__banner_image__help'),
-        verbose_name=_('ConferenceTrack__banner_image'))
+        verbose_name=_('ConferenceTrack__banner_image'),
+    )
 
     last_update = models.DateTimeField(auto_now=True)
 
@@ -610,7 +567,7 @@ class ConferenceExportCache(models.Model):
     last_generated = models.DateTimeField(blank=True, null=True)
     data = models.TextField(blank=True, null=True)
 
-    class Meta(object):
+    class Meta:
         unique_together = [
             ('conference', 'ident'),
         ]
@@ -632,24 +589,27 @@ class ConferenceExportCache(models.Model):
             last_room = Room.objects.filter(conference=self.conference).aggregate(Max('last_update'))['last_update__max']
 
             # if one of those is greater than our last generation, we need to update
-            if (last_track is not None and last_track > self.last_generated) or \
-               (last_event is not None and last_event > self.last_generated) or \
-               (last_room is not None and last_room > self.last_generated):
+            if (
+                (last_track is not None and last_track > self.last_generated)
+                or (last_event is not None and last_event > self.last_generated)
+                or (last_room is not None and last_room > self.last_generated)
+            ):
                 return True
 
         elif self.type == self.Type.MAP:
             from .assemblies import Assembly
+
             last_assemblies = Assembly.objects.filter(conference=self.conference).aggregate(Max('last_update_staff'))['last_update_staff__max']
             if last_assemblies is not None and last_assemblies > self.last_generated:
                 return True
 
         elif self.type == self.Type.ASSEMBLIES:
             from .assemblies import Assembly
+
             last_updates = Assembly.objects.filter(conference=self.conference).aggregate(Max('last_update_assembly'), Max('last_update_staff'))
             last_assemblies = last_updates['last_update_assembly__max']
             last_staff = last_updates['last_update_staff__max']
-            if (last_assemblies is not None and last_assemblies > self.last_generated) or \
-               (last_staff is not None and last_staff > self.last_generated):
+            if (last_assemblies is not None and last_assemblies > self.last_generated) or (last_staff is not None and last_staff > self.last_generated):
                 return True
 
         else:
@@ -668,7 +628,9 @@ class ConferenceExportCache(models.Model):
     @classmethod
     def _get_or_create_entry(cls, conference: Conference, type: Type, ident: str, result_func: typing.Callable[..., bytes]):
         cache_entry, _ = cls.objects.get_or_create(
-            conference=conference, type=type, ident=ident,
+            conference=conference,
+            type=type,
+            ident=ident,
             defaults={'needs_regeneration': True},
         )
         if cache_entry.is_dirty or settings.DEBUG:
@@ -685,9 +647,7 @@ class ConferenceExportCache(models.Model):
         return content.decode('utf-8') if as_text else content
 
     @classmethod
-    def handle_http_request(cls, request: HttpRequest,
-                            conference: Conference, type: Type, ident: str,
-                            content_type, result_func):
+    def handle_http_request(cls, request: HttpRequest, conference: Conference, type: Type, ident: str, content_type, result_func):
         cache_entry = cls._get_or_create_entry(conference, type, ident, result_func)
 
         headers = {
@@ -760,36 +720,48 @@ class ConferenceNavigationItem(models.Model):
 
     conference = ConferenceReference(related_name='nav_items')
     parent = models.ForeignKey(
-        'self', related_name='children', on_delete=models.CASCADE,
-        blank=True, null=True,
-        help_text=_('ConferenceNavigationItem__parent__help'), verbose_name=_('ConferenceNavigationItem__parent'),
+        'self',
+        related_name='children',
+        on_delete=models.CASCADE,
+        blank=True,
+        null=True,
+        help_text=_('ConferenceNavigationItem__parent__help'),
+        verbose_name=_('ConferenceNavigationItem__parent'),
     )
 
     is_visible = models.BooleanField(
         default=True,
-        help_text=_('ConferenceNavigationItem__is_visible__help'), verbose_name=_('ConferenceNavigationItem__is_visible'),
+        help_text=_('ConferenceNavigationItem__is_visible__help'),
+        verbose_name=_('ConferenceNavigationItem__is_visible'),
     )
     index = models.IntegerField(
-        help_text=_('ConferenceNavigationItem__index__help'), verbose_name=_('ConferenceNavigationItem__index'),
+        help_text=_('ConferenceNavigationItem__index__help'),
+        verbose_name=_('ConferenceNavigationItem__index'),
     )
     icon = models.CharField(
         max_length=50,
-        blank=True, null=True,
-        help_text=_('ConferenceNavigationItem__icon__help'), verbose_name=_('ConferenceNavigationItem__icon'),
+        blank=True,
+        null=True,
+        help_text=_('ConferenceNavigationItem__icon__help'),
+        verbose_name=_('ConferenceNavigationItem__icon'),
     )
 
     label = models.CharField(
         max_length=50,
-        help_text=_('ConferenceNavigationItem__label__help'), verbose_name=_('ConferenceNavigationItem__label'),
+        help_text=_('ConferenceNavigationItem__label__help'),
+        verbose_name=_('ConferenceNavigationItem__label'),
     )
     title = models.CharField(
         max_length=200,
-        help_text=_('ConferenceNavigationItem__title__help'), verbose_name=_('ConferenceNavigationItem__title'),
+        help_text=_('ConferenceNavigationItem__title__help'),
+        verbose_name=_('ConferenceNavigationItem__title'),
     )
     url = models.CharField(
         max_length=255,
-        blank=True, validators=[validate_conferencenavigationitem_url],
-        help_text=_('ConferenceNavigationItem__url__help'), verbose_name=_('ConferenceNavigationItem__url'),
+        blank=True,
+        validators=[validate_conferencenavigationitem_url],
+        help_text=_('ConferenceNavigationItem__url__help'),
+        verbose_name=_('ConferenceNavigationItem__url'),
     )
 
     def resolve_url(self):
diff --git a/src/core/models/dereferrer.py b/src/core/models/dereferrer.py
index a5f01ad1b..eb9d80e93 100644
--- a/src/core/models/dereferrer.py
+++ b/src/core/models/dereferrer.py
@@ -1,4 +1,5 @@
 from django.db import models
+
 from core.models.users import PlatformUser
 
 
diff --git a/src/core/models/events.py b/src/core/models/events.py
index daf213f33..279a3674a 100644
--- a/src/core/models/events.py
+++ b/src/core/models/events.py
@@ -5,6 +5,8 @@ from re import compile
 from typing import Any
 from uuid import UUID, uuid4
 
+from ordered_set import OrderedSet
+
 from django.conf import settings
 from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
 from django.db import models
@@ -16,7 +18,6 @@ from django.utils.functional import cached_property
 from django.utils.text import slugify
 from django.utils.timezone import is_aware, make_aware
 from django.utils.translation import gettext_lazy as _
-from ordered_set import OrderedSet
 
 from ..fields import ConferenceReference
 from ..markdown import compile_translated_markdown_fields, store_relationships
@@ -82,12 +83,15 @@ class EventManager(models.Manager):
         return qs.filter(is_public=True)
 
     def conference_accessible(self, conference: Conference):
-        return self.get_queryset().filter(conference=conference, is_public=True) \
+        return (
+            self.get_queryset()
+            .filter(conference=conference, is_public=True)
             .filter(
                 Q(assembly__state_assembly__in=Assembly.PUBLIC_STATES)
                 | Q(assembly__state_channel__in=Assembly.PUBLIC_STATES)
                 | Q(kind=Event.Kind.SELF_ORGANIZED)
             )
+        )
 
     def manageable_by_user(self, user: PlatformUser, conference: Conference):
         assert user is not None
@@ -112,8 +116,9 @@ class EventManager(models.Manager):
             pass
 
         # for everybody else, only show public events
-        manageable_assemblies_qs = Assembly.objects.manageable_by_user(conference=conference, user=user) \
-            .filter(members__member=user, members__can_manage_assembly=True)
+        manageable_assemblies_qs = Assembly.objects.manageable_by_user(conference=conference, user=user).filter(
+            members__member=user, members__can_manage_assembly=True
+        )
         return qs.filter(Q(kind=Event.Kind.SELF_ORGANIZED, owner=user) | Q(assembly_id__in=manageable_assemblies_qs.values_list('id', flat=True)))
 
 
@@ -129,90 +134,58 @@ class Event(TaggedItemMixin, BackendMixin, models.Model):
     class Kind(models.TextChoices):
         OFFICIAL = 'official', _('Event__kind-official')  # offizielles Event
         ASSEMBLY = 'assembly', _('Event__kind-assembly')  # von einer Assembly kuratiert
-        SELF_ORGANIZED = 'sos', _('Event__kind-sos')      # hat Einzelperson angelegt
+        SELF_ORGANIZED = 'sos', _('Event__kind-sos')  # hat Einzelperson angelegt
 
     objects = EventManager()
 
     id = models.UUIDField(default=uuid4, primary_key=True, editable=False)
     conference = ConferenceReference(related_name='events')
     # Unique together with conference
-    slug = models.SlugField(
-        max_length=150,
-        help_text=_('Event__slug__help'),
-        verbose_name=_('Event__slug'))
-    kind = models.CharField(
-        max_length=10,
-        choices=Kind.choices,
-        default=Kind.ASSEMBLY,
-        help_text=_('Event__kind__help'),
-        verbose_name=_('Event__kind'))
-
-    name = models.CharField(
-        max_length=200)
+    slug = models.SlugField(max_length=150, help_text=_('Event__slug__help'), verbose_name=_('Event__slug'))
+    kind = models.CharField(max_length=10, choices=Kind.choices, default=Kind.ASSEMBLY, help_text=_('Event__kind__help'), verbose_name=_('Event__kind'))
+
+    name = models.CharField(max_length=200)
     track = models.ForeignKey(ConferenceTrack, blank=True, null=True, on_delete=models.PROTECT)
     assembly = models.ForeignKey(Assembly, related_name='events', on_delete=models.PROTECT, blank=True, null=True)
     room = models.ForeignKey(Room, blank=True, null=True, related_name='events', on_delete=models.PROTECT)
 
-    language = models.CharField(
-        max_length=50, blank=True, null=True,
-        help_text=_('Event__language__help'),
-        verbose_name=_('Event__language'))
-    abstract = models.TextField(
-        blank=True,
-        help_text=_('Event__abstract__help'),
-        verbose_name=_('Event__abstract'))
-    description = models.TextField(
-        blank=True,
-        help_text=_('Event__description__help'),
-        verbose_name=_('Event__description'))
+    language = models.CharField(max_length=50, blank=True, null=True, help_text=_('Event__language__help'), verbose_name=_('Event__language'))
+    abstract = models.TextField(blank=True, help_text=_('Event__abstract__help'), verbose_name=_('Event__abstract'))
+    description = models.TextField(blank=True, help_text=_('Event__description__help'), verbose_name=_('Event__description'))
     description_html = models.TextField(blank=True)
     banner_image_height = models.PositiveIntegerField(blank=True, null=True)
     banner_image_width = models.PositiveIntegerField(blank=True, null=True)
     banner_image = models.ImageField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         height_field='banner_image_height',
         width_field='banner_image_width',
         help_text=_('Event__banner_image__help'),
-        verbose_name=_('Event__banner_image'))
-
-    is_public = models.BooleanField(
-        default=False,
-        help_text=_('Event__is_public__help'),
-        verbose_name=_('Event__is_public'))
-    schedule_start = models.DateTimeField(
-        blank=True, null=True,
-        help_text=_('Event__schedule_start__help'),
-        verbose_name=_('Event__schedule_start'))
-    schedule_duration = EventDurationField(
-        blank=True, null=True,
-        help_text=_('Event__schedule_duration__help'),
-        verbose_name=_('Event__schedule_duration'))
-    schedule_end = models.DateTimeField(
-        blank=True, null=True,
-        help_text=_('Event__schedule_end__help'),
-        verbose_name=_('Event__schedule_end'))
+        verbose_name=_('Event__banner_image'),
+    )
+
+    is_public = models.BooleanField(default=False, help_text=_('Event__is_public__help'), verbose_name=_('Event__is_public'))
+    schedule_start = models.DateTimeField(blank=True, null=True, help_text=_('Event__schedule_start__help'), verbose_name=_('Event__schedule_start'))
+    schedule_duration = EventDurationField(blank=True, null=True, help_text=_('Event__schedule_duration__help'), verbose_name=_('Event__schedule_duration'))
+    schedule_end = models.DateTimeField(blank=True, null=True, help_text=_('Event__schedule_end__help'), verbose_name=_('Event__schedule_end'))
 
     additional_data = models.JSONField(blank=True, null=True)
     favorited_by = models.ManyToManyField(
-        PlatformUser,
-        related_name='favorite_events',
-        help_text=_('Event__favorited_by__help'),
-        verbose_name=_('Event__favorited_by')
+        PlatformUser, related_name='favorite_events', help_text=_('Event__favorited_by__help'), verbose_name=_('Event__favorited_by')
     )
     in_personal_calendar = models.ManyToManyField(
-        PlatformUser,
-        related_name='calendar_events',
-        help_text=_('Event__in_personal_calendar__help'),
-        verbose_name=_('Event__in_personal_calendar')
+        PlatformUser, related_name='calendar_events', help_text=_('Event__in_personal_calendar__help'), verbose_name=_('Event__in_personal_calendar')
     )
 
     owner = models.ForeignKey(
         settings.AUTH_USER_MODEL,
-        blank=True, null=True,
+        blank=True,
+        null=True,
         related_name='owned_events',
         on_delete=models.PROTECT,
         help_text=_('Event__owner__help'),
-        verbose_name=_('Event__owner'))
+        verbose_name=_('Event__owner'),
+    )
     """
     Ersteller/Eigentümer des Events, dies ist insb. für Self-Organized-Sessions relevant.
     """
@@ -286,9 +259,17 @@ class Event(TaggedItemMixin, BackendMixin, models.Model):
         return self.name
 
     @classmethod
-    def from_dict(cls, data: dict, conference: Conference, assembly: Assembly = None, existing=None, pop_used_keys: bool = False,
-                  allow_kind: bool = False, allow_track: bool = False,
-                  room_lookup=None):
+    def from_dict(
+        cls,
+        data: dict,
+        conference: Conference,
+        assembly: Assembly = None,
+        existing=None,
+        pop_used_keys: bool = False,
+        allow_kind: bool = False,
+        allow_track: bool = False,
+        room_lookup=None,
+    ):
         """
         Loads an Event instance from the given dictionary.
         An existing event can be provided which's data is overwritten (in parts).
@@ -312,11 +293,11 @@ class Event(TaggedItemMixin, BackendMixin, models.Model):
         if existing:
             obj = existing
             if assembly:
-                assert obj.assembly == assembly, 'Existing event\'s assembly does not match given one.'
+                assert obj.assembly == assembly, "Existing event's assembly does not match given one."
             if conference:
-                assert obj.conference == conference, 'Existing event\'s conference does not match given one.'
+                assert obj.conference == conference, "Existing event's conference does not match given one."
             if given_uuid is not None:
-                assert obj.pk == given_uuid, f'expected existing event\'s id {obj.pk} to match the given uuid {given_uuid}'
+                assert obj.pk == given_uuid, f"expected existing event's id {obj.pk} to match the given uuid {given_uuid}"
         else:
             obj = cls(assembly=assembly, conference=conference)
             if given_uuid is not None:
@@ -324,11 +305,14 @@ class Event(TaggedItemMixin, BackendMixin, models.Model):
         assert obj.conference_id is not None
 
         direct_fields = [
-            'slug', 'name',
-            'abstract', 'description',
+            'slug',
+            'name',
+            'abstract',
+            'description',
             'language',
             'is_public',
-            'schedule_start', 'schedule_end',
+            'schedule_start',
+            'schedule_end',
         ]
         bool_fields = ['is_public']
         dt_fields = ['schedule_start', 'schedule_end']
@@ -444,7 +428,7 @@ class Event(TaggedItemMixin, BackendMixin, models.Model):
         # When setting to public we must have a duration
         if (test_for_publication or self.is_public) and not self.schedule_duration:
             publication_errors['schedule_duration'] = errors['schedule_duration'] = ValidationError(
-               _('Event__schedule_duration__non_empty %(event_type)s') % {'event_type': event_type}, code='empty'
+                _('Event__schedule_duration__non_empty %(event_type)s') % {'event_type': event_type}, code='empty'
             )
         if self.schedule_duration is not None:
             # duration must not be zero
@@ -516,7 +500,7 @@ class Event(TaggedItemMixin, BackendMixin, models.Model):
 
 def _EventAttachment_upload_path(instance, filename):
     # file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
-    return 'eventattachment/{0}/{1}'.format(instance.event.id, instance.id)
+    return f'eventattachment/{instance.event.id}/{instance.id}'
 
 
 class EventAttachment(models.Model):
@@ -553,27 +537,18 @@ class EventParticipant(models.Model):
     participant = models.ForeignKey(PlatformUser, on_delete=models.CASCADE, related_name='events')
 
     role = models.CharField(
-        max_length=20, choices=Role.choices, default=Role.REGULAR,
-        help_text=_('EventParticipant__role__help'),
-        verbose_name=_('EventParticipant__role'))
-
-    is_accepted = models.BooleanField(
-        default=False,
-        help_text=_('EventParticipant__is_accepted__help'),
-        verbose_name=_('EventParticipant__is_accepted'))
-    is_public = models.BooleanField(
-        default=False,
-        help_text=_('EventParticipant__is_public__help'),
-        verbose_name=_('EventParticipant__is_public'))
+        max_length=20, choices=Role.choices, default=Role.REGULAR, help_text=_('EventParticipant__role__help'), verbose_name=_('EventParticipant__role')
+    )
+
+    is_accepted = models.BooleanField(default=False, help_text=_('EventParticipant__is_accepted__help'), verbose_name=_('EventParticipant__is_accepted'))
+    is_public = models.BooleanField(default=False, help_text=_('EventParticipant__is_public__help'), verbose_name=_('EventParticipant__is_public'))
 
     public_description = models.TextField(
-        blank=True, null=True,
-        help_text=_('EventParticipant__public_description__help'),
-        verbose_name=_('EventParticipant__public_description'))
+        blank=True, null=True, help_text=_('EventParticipant__public_description__help'), verbose_name=_('EventParticipant__public_description')
+    )
     personal_comment = models.TextField(
-        blank=True, null=True,
-        help_text=_('EventParticipant__personal_comment__help'),
-        verbose_name=_('EventParticipant__personal_comment'))
+        blank=True, null=True, help_text=_('EventParticipant__personal_comment__help'), verbose_name=_('EventParticipant__personal_comment')
+    )
 
     def clean(self, *args, **kwargs):
         # verify that the participant is a member of the event's conference
@@ -591,10 +566,9 @@ class EventParticipant(models.Model):
 
 class EventLikeCount(models.Model):
     class Meta:
-        indexes = [
-            models.Index(fields=['event1', 'like_ratio'])
-        ]
+        indexes = [models.Index(fields=['event1', 'like_ratio'])]
         unique_together = [('event1', 'event2')]
+
     event1 = models.ForeignKey(Event, related_name='suggestions', on_delete=models.CASCADE)
     event2 = models.ForeignKey(Event, related_name='+', on_delete=models.CASCADE)
     likes = models.IntegerField()
diff --git a/src/core/models/map.py b/src/core/models/map.py
index 8200d4d96..aa991af12 100644
--- a/src/core/models/map.py
+++ b/src/core/models/map.py
@@ -10,7 +10,6 @@ from django.utils.translation import gettext_lazy as _
 from ..fields import ConferenceReference
 from ..markdown import compile_translated_markdown_fields, store_relationships
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -42,37 +41,27 @@ class MapPOI(models.Model):
     id = models.UUIDField(default=uuid4, primary_key=True, editable=False, serialize=False)
     conference = ConferenceReference(related_name='pois')
 
-    visible = models.BooleanField(
-        default=True,
-        help_text=_('MapPOI__visible__help'),
-        verbose_name=_('MapPOI__visible'))
+    visible = models.BooleanField(default=True, help_text=_('MapPOI__visible__help'), verbose_name=_('MapPOI__visible'))
 
-    name = models.CharField(
-        max_length=200,
-        unique=True,
-        help_text=_('MapPOI__name__help'),
-        verbose_name=_('MapPOI__name'))
-    description = models.TextField(
-        blank=True, null=True,
-        help_text=_('MapPOI__description__help'),
-        verbose_name=_('MapPOI__description'))
+    name = models.CharField(max_length=200, unique=True, help_text=_('MapPOI__name__help'), verbose_name=_('MapPOI__name'))
+    description = models.TextField(blank=True, null=True, help_text=_('MapPOI__description__help'), verbose_name=_('MapPOI__description'))
     description_html = models.TextField(blank=True)
 
-    is_official = models.BooleanField(
-        default=False,
-        help_text=_('MapPOI__is_official__help'),
-        verbose_name=_('MapPOI__is_official'))
+    is_official = models.BooleanField(default=False, help_text=_('MapPOI__is_official__help'), verbose_name=_('MapPOI__is_official'))
 
     location_floor = models.ForeignKey(
         MapFloor,
-        blank=True, null=True,
-        related_name='pois', on_delete=models.PROTECT,
+        blank=True,
+        null=True,
+        related_name='pois',
+        on_delete=models.PROTECT,
         help_text=_('MapPOI__location_floor__help'),
         verbose_name=_('MapPOI__location_floor'),
     )
 
     location_point = gis_models.PointField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('MapPOI__location_point__help'),
         verbose_name=_('MapPOI__location_point'),
     )
diff --git a/src/core/models/messages.py b/src/core/models/messages.py
index ed79b3cbf..08315dce1 100644
--- a/src/core/models/messages.py
+++ b/src/core/models/messages.py
@@ -1,8 +1,9 @@
 from uuid import uuid4
+
 from django.conf import settings
 from django.db import models
 from django.template.loader import render_to_string
-from django.urls import reverse, NoReverseMatch
+from django.urls import NoReverseMatch, reverse
 from django.utils.text import format_lazy
 from django.utils.translation import gettext_lazy as _
 
@@ -18,11 +19,13 @@ class DirectMessage(models.Model):
 
     sender = models.ForeignKey(
         settings.AUTH_USER_MODEL,
-        blank=True, null=True,
+        blank=True,
+        null=True,
         on_delete=models.CASCADE,
         related_name='sent_messages',
         help_text=_('DirectMessage__sender__help'),
-        verbose_name=_('DirectMessage__sender'))
+        verbose_name=_('DirectMessage__sender'),
+    )
     """The sender of this message or None if it is a system message (in which case the conference field is being used)."""
 
     recipient = models.ForeignKey(
@@ -30,65 +33,46 @@ class DirectMessage(models.Model):
         on_delete=models.CASCADE,
         related_name='received_messages',
         help_text=_('DirectMessage__recipient__help'),
-        verbose_name=_('DirectMessage__recipient'))
+        verbose_name=_('DirectMessage__recipient'),
+    )
     """The intended recipient of this message."""
 
-    timestamp = models.DateTimeField(
-        auto_now_add=True,
-        help_text=_('DirectMessage__timestamp__help'),
-        verbose_name=_('DirectMessage__timestamp'))
+    timestamp = models.DateTimeField(auto_now_add=True, help_text=_('DirectMessage__timestamp__help'), verbose_name=_('DirectMessage__timestamp'))
     """Timestamp when the message was sent."""
 
     autodelete_after = models.DateTimeField(
-        blank=True, null=True,
-        help_text=_('DirectMessage__autodelete_after__help'),
-        verbose_name=_('DirectMessage__autodelete_after'))
+        blank=True, null=True, help_text=_('DirectMessage__autodelete_after__help'), verbose_name=_('DirectMessage__autodelete_after')
+    )
     """Automatic purging deletes this message after this timestamp."""
 
     in_reply_to = models.ForeignKey(
-        'self',
-        blank=True, null=True, on_delete=models.SET_NULL,
-        help_text=_('DirectMessage__in_reply_to__help'),
-        verbose_name=_('DirectMessage__in_reply_to'))
+        'self', blank=True, null=True, on_delete=models.SET_NULL, help_text=_('DirectMessage__in_reply_to__help'), verbose_name=_('DirectMessage__in_reply_to')
+    )
     """Original message to which this is a reply."""
 
-    subject = models.CharField(
-        max_length=200,
-        help_text=_('DirectMessage__subject__help'),
-        verbose_name=_('DirectMessage__subject'))
+    subject = models.CharField(max_length=200, help_text=_('DirectMessage__subject__help'), verbose_name=_('DirectMessage__subject'))
     """Subject of the message, might include 'Re:' markers."""
 
-    body = models.TextField(
-        help_text=_('DirectMessage__body__help'),
-        verbose_name=_('DirectMessage__body'))
+    body = models.TextField(help_text=_('DirectMessage__body__help'), verbose_name=_('DirectMessage__body'))
     """Message with (limited) Markdown support."""
 
     deleted_by_sender = models.BooleanField(
-        default=False,
-        help_text=_('DirectMessage__deleted_by_sender__help'),
-        verbose_name=_('DirectMessage__deleted_by_sender'))
+        default=False, help_text=_('DirectMessage__deleted_by_sender__help'), verbose_name=_('DirectMessage__deleted_by_sender')
+    )
 
     deleted_by_recipient = models.BooleanField(
-        default=False,
-        help_text=_('DirectMessage__deleted_by_recipient__help'),
-        verbose_name=_('DirectMessage__deleted_by_recipient'))
-
-    was_read = models.BooleanField(
-        default=False,
-        help_text=_('DirectMessage__was_read__help'),
-        verbose_name=_('DirectMessage__was_read'))
+        default=False, help_text=_('DirectMessage__deleted_by_recipient__help'), verbose_name=_('DirectMessage__deleted_by_recipient')
+    )
+
+    was_read = models.BooleanField(default=False, help_text=_('DirectMessage__was_read__help'), verbose_name=_('DirectMessage__was_read'))
     """Signals whether the recipient has read the message, this is *not* shown to the sender!"""
 
-    has_responded = models.BooleanField(
-        default=False,
-        help_text=_('DirectMessage__has_responded__help'),
-        verbose_name=_('DirectMessage__has_responded'))
+    has_responded = models.BooleanField(default=False, help_text=_('DirectMessage__has_responded__help'), verbose_name=_('DirectMessage__has_responded'))
     """Signals whether the recipient has replied to the message. (speed optimization to circumvent EXISTS query)"""
 
     flagged_for_abuse = models.BooleanField(
-        default=False,
-        help_text=_('DirectMessage__flagged_for_abuse__help'),
-        verbose_name=_('DirectMessage__flagged_for_abuse'))
+        default=False, help_text=_('DirectMessage__flagged_for_abuse__help'), verbose_name=_('DirectMessage__flagged_for_abuse')
+    )
     """Messages by abusive users will be hidden, at least if the messages have not been read yet."""
 
     @property
diff --git a/src/core/models/pages.py b/src/core/models/pages.py
index 61f48a8ea..69f26f368 100644
--- a/src/core/models/pages.py
+++ b/src/core/models/pages.py
@@ -1,7 +1,9 @@
 import logging
 import re
-from typing import Dict, Optional, Tuple
 import uuid
+from typing import Dict, Optional, Tuple
+
+from urllib3.util import Url, parse_url
 
 from django.conf import settings
 from django.contrib.postgres import indexes as pg_indexes
@@ -14,14 +16,13 @@ from django.utils import timezone
 from django.utils.functional import cached_property
 from django.utils.html import strip_tags
 from django.utils.translation import gettext_lazy as _
-from urllib3.util import parse_url, Url
 
 from core.markdown import render_markdown_ex, store_relationships
 
 from ..fields import ConferenceReference
+from ..utils import GitRepo
 from .conference import Conference
 from .users import PlatformUser
-from ..utils import GitRepo
 
 
 class StaticPageNamespace(models.Model):
@@ -45,13 +46,15 @@ class StaticPageNamespace(models.Model):
     )
 
     upstream_url = models.URLField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('StaticPageNamespace__upstream_url__help'),
         verbose_name=_('StaticPageNamespace__upstream_url'),
     )
 
     upstream_image_base_url = models.URLField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('StaticPageNamespace__upstream_image_base_url__help'),
         verbose_name=_('StaticPageNamespace__upstream_image_base_url'),
     )
@@ -68,7 +71,7 @@ class StaticPageNamespace(models.Model):
             metadata_raw, content = markup[4:].split('---\n', maxsplit=1)
             metadata = {}
             for line in metadata_raw.splitlines():
-                k, v = line.split(":", maxsplit=1)
+                k, v = line.split(':', maxsplit=1)
                 metadata[k.strip()] = v.strip()
         else:
             metadata = {}
@@ -85,7 +88,7 @@ class StaticPageNamespace(models.Model):
         RE_MARKDOWN_IMAGE = re.compile(r'!\[(.+?)\]\((.+?)\)')
 
         with GitRepo(url) as repo:
-            docs = repo.get_documents(glob="*.md")
+            docs = repo.get_documents(glob='*.md')
 
             if image_base_url := self.upstream_image_base_url:
                 image_base_url_url = parse_url(image_base_url)
@@ -97,10 +100,10 @@ class StaticPageNamespace(models.Model):
                         # seems to be a valid URL => don't change anything
                         return match.string
 
-                    if image_url.startswith("./"):
+                    if image_url.startswith('./'):
                         image_url = image_url[2:]
 
-                    if not image_url.startswith("/"):
+                    if not image_url.startswith('/'):
                         new_image_url = image_base_url + image_url
                     else:
                         new_image_url = Url(
@@ -111,7 +114,7 @@ class StaticPageNamespace(models.Model):
                             path=image_url,
                         )
 
-                    return f"![{match.group(1)}]({new_image_url})"
+                    return f'![{match.group(1)}]({new_image_url})'
 
                 for doc_name, doc in docs.items():
                     docs[doc_name] = RE_MARKDOWN_IMAGE.sub(derive_image_url, doc)
@@ -120,7 +123,7 @@ class StaticPageNamespace(models.Model):
         obsolete_pages = set(existing_pages.keys())
 
         for doc_filename, document_raw in docs.items():
-            page_slug = self.prefix.lower() + doc_filename.rstrip(".md")
+            page_slug = self.prefix.lower() + doc_filename.rstrip('.md')
             page = existing_pages.get(page_slug)  # type: StaticPage
 
             try:
@@ -149,7 +152,7 @@ class StaticPageNamespace(models.Model):
                     # create new page
                     page = StaticPage.objects.create(conference=self.conference, slug=page_slug)
                     page_rev = page.revisions.create(
-                        title=doc_metadata.get('title') or page_slug[len(self.prefix):].capitalize(),
+                        title=doc_metadata.get('title') or page_slug[len(self.prefix) :].capitalize(),
                         body=document,
                         is_draft=False,
                         author=None,
@@ -165,10 +168,10 @@ class StaticPageNamespace(models.Model):
 
         updated = StaticPage.objects.filter(conference=self.conference).bulk_update(
             objs=(existing_pages[page_slug] for page_slug in obsolete_pages),
-            fields={"privacy": StaticPage.Privacy.PERM},
+            fields={'privacy': StaticPage.Privacy.PERM},
         )
         if updated > 0:
-            logging.info('set %s wiki page(s) "%s" non-public (removed upstream): %s', updated, page_slug, ";".join(obsolete_pages))
+            logging.info('set %s wiki page(s) "%s" non-public (removed upstream): %s', updated, page_slug, ';'.join(obsolete_pages))
 
 
 class StaticPageManager(models.Manager):
@@ -248,21 +251,18 @@ class StaticPageManager(models.Manager):
 
     def conference_accessible(self, conference: Conference, language: str):
         # fetch conference's pages with a public revision and a language set to the given one or no language set
-        return self.get_queryset() \
-            .filter(conference=conference, public_revision__gt=0) \
-            .filter(Q(language=language) | Q(language=None))
+        return self.get_queryset().filter(conference=conference, public_revision__gt=0).filter(Q(language=language) | Q(language=None))
 
 
 class StaticPage(models.Model):
-
     class Protection(models.TextChoices):
-        NONE = 'none', _("StaticPage__protection-none")
-        PERM = 'perm', _("StaticPage__protection-perm")
+        NONE = 'none', _('StaticPage__protection-none')
+        PERM = 'perm', _('StaticPage__protection-perm')
 
     class Privacy(models.TextChoices):
-        NONE = 'none', _("StaticPage__privacy-none")
-        CONFERENCE = 'conf', _("StaticPage__privacy-conf")
-        PERM = 'perm', _("StaticPage__privacy-perm")
+        NONE = 'none', _('StaticPage__privacy-none')
+        CONFERENCE = 'conf', _('StaticPage__privacy-conf')
+        PERM = 'perm', _('StaticPage__privacy-perm')
 
     id = models.UUIDField(default=uuid.uuid4, primary_key=True)
 
@@ -270,12 +270,14 @@ class StaticPage(models.Model):
     slug = models.SlugField()
     language = models.CharField(max_length=20, blank=False, null=True, help_text=_('StaticPage__language__help'), verbose_name=_('StaticPage__language'))
     protection = models.CharField(
-        max_length=20, default=Protection.NONE, choices=Protection.choices,
-        help_text=_('StaticPage__protection__help'), verbose_name=_('StaticPage__protection')
+        max_length=20,
+        default=Protection.NONE,
+        choices=Protection.choices,
+        help_text=_('StaticPage__protection__help'),
+        verbose_name=_('StaticPage__protection'),
     )
     privacy = models.CharField(
-        max_length=20, default=Privacy.NONE, choices=Privacy.choices,
-        help_text=_('StaticPage__privacy__help'), verbose_name=_('StaticPage__privacy')
+        max_length=20, default=Privacy.NONE, choices=Privacy.choices, help_text=_('StaticPage__privacy__help'), verbose_name=_('StaticPage__privacy')
     )
 
     remove_html = models.BooleanField(
@@ -289,22 +291,10 @@ class StaticPage(models.Model):
         verbose_name=_('StaticPage__sanitize_html'),
     )
 
-    public_revision = models.PositiveIntegerField(
-        default=0,
-        help_text=_('StaticPage__public_revision__help'),
-        verbose_name=_('StaticPage__public_revision'))
-    title = models.CharField(
-        max_length=200,
-        help_text=_('StaticPage__title__help'),
-        verbose_name=_('StaticPage__title'))
-    body = models.TextField(
-        default='',
-        help_text=_('StaticPage__body__help'),
-        verbose_name=_('StaticPage__body'))
-    body_html = models.TextField(
-        blank=True, null=True, editable=False,
-        help_text=_('StaticPage__body_html__help'),
-        verbose_name=_('StaticPage__body_html'))
+    public_revision = models.PositiveIntegerField(default=0, help_text=_('StaticPage__public_revision__help'), verbose_name=_('StaticPage__public_revision'))
+    title = models.CharField(max_length=200, help_text=_('StaticPage__title__help'), verbose_name=_('StaticPage__title'))
+    body = models.TextField(default='', help_text=_('StaticPage__body__help'), verbose_name=_('StaticPage__body'))
+    body_html = models.TextField(blank=True, null=True, editable=False, help_text=_('StaticPage__body_html__help'), verbose_name=_('StaticPage__body_html'))
     """HTML of the current public revision"""
     search_content = models.TextField(blank=True, null=True, editable=False)
     """content used for searching (internal use only)"""
@@ -315,7 +305,14 @@ class StaticPage(models.Model):
 
     class Meta:
         constraints = [
-            models.constraints.UniqueConstraint(fields=['conference', 'language', 'slug',], name='unique_conferencestaticpage_slug'),
+            models.constraints.UniqueConstraint(
+                fields=[
+                    'conference',
+                    'language',
+                    'slug',
+                ],
+                name='unique_conferencestaticpage_slug',
+            ),
         ]
         indexes = [
             pg_indexes.GinIndex(fields=['search_vector'], name='staticpage_searchvector'),
@@ -325,7 +322,7 @@ class StaticPage(models.Model):
         verbose_name_plural = _('StaticPages')
 
     def __str__(self):
-        return '%s[%s]' % (self.title, self.language)
+        return f'{self.title}[{self.language}]'
 
     @cached_property
     def newest_revision(self):
@@ -353,13 +350,8 @@ class StaticPageRevision(models.Model):
     page = models.ForeignKey(StaticPage, related_name='revisions', on_delete=models.CASCADE)
     revision = models.AutoField(primary_key=True)
 
-    title = models.CharField(
-        max_length=200,
-        help_text=_('StaticPageRevision__title__help'),
-        verbose_name=_('StaticPageRevision__title'))
-    body = models.TextField(
-        help_text=_('StaticPageRevision__body__help'),
-        verbose_name=_('StaticPageRevision__body'))
+    title = models.CharField(max_length=200, help_text=_('StaticPageRevision__title__help'), verbose_name=_('StaticPageRevision__title'))
+    body = models.TextField(help_text=_('StaticPageRevision__body__help'), verbose_name=_('StaticPageRevision__body'))
 
     # `is_draft` wird momentan nicht mehr wirklich verwendet.
     # Um das wieder sinnvoll zu nutzen sollte eine Möglichkeit geschaffen zu werden, drafts
@@ -367,20 +359,17 @@ class StaticPageRevision(models.Model):
     # werden. Und dann eine Möglichkeit einen draft zu veröffentlichen.
     # das Veröffentlichen sollte jedoch eine neue Revision erzeugen,
     # so dass Revisionen, die als `is_draft` markiert sind, dauerhaft `is_draft` bleiben.
-    is_draft = models.BooleanField(
-        default=False,
-        help_text=_('StaticPageRevision__is_draft__help'),
-        verbose_name=_('StaticPageRevision__is_draft'))
+    is_draft = models.BooleanField(default=False, help_text=_('StaticPageRevision__is_draft__help'), verbose_name=_('StaticPageRevision__is_draft'))
 
     author = models.ForeignKey(
         settings.AUTH_USER_MODEL,
-        blank=True, null=True, on_delete=models.SET_NULL,
+        blank=True,
+        null=True,
+        on_delete=models.SET_NULL,
         help_text=_('StaticPageRevision__author__help'),
-        verbose_name=_('StaticPageRevision__author'))
-    timestamp = models.DateTimeField(
-        auto_now_add=True,
-        help_text=_('StaticPageRevision__timestamp__help'),
-        verbose_name=_('StaticPageRevision__timestamp'))
+        verbose_name=_('StaticPageRevision__author'),
+    )
+    timestamp = models.DateTimeField(auto_now_add=True, help_text=_('StaticPageRevision__timestamp__help'), verbose_name=_('StaticPageRevision__timestamp'))
 
     class Meta:
         constraints = [
@@ -402,9 +391,7 @@ class StaticPageRevision(models.Model):
         self.page.title = self.title
         self.page.body = self.body
         render_result = render_markdown_ex(
-            self.page.conference, self.body,
-            allow_embedded_html=not self.page.remove_html,
-            sanitize_html=self.page.sanitize_html
+            self.page.conference, self.body, allow_embedded_html=not self.page.remove_html, sanitize_html=self.page.sanitize_html
         )
         self.page.body_html = render_result.document
         store_relationships(self.page.conference, self.page, render_result)
diff --git a/src/core/models/rooms.py b/src/core/models/rooms.py
index f7fc80da6..9bba968c9 100644
--- a/src/core/models/rooms.py
+++ b/src/core/models/rooms.py
@@ -7,13 +7,13 @@ from django.db.models import Q
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 
-from .shared import BackendMixin
 from ..fields import ConferenceReference
+from ..markdown import compile_translated_markdown_fields, store_relationships
 from ..models.conference import Conference, ConferenceMember
 from ..models.users import PlatformUser
 from ..utils import str2bool
-from ..markdown import compile_translated_markdown_fields, store_relationships
 from .assemblies import Assembly
+from .shared import BackendMixin
 
 
 class RoomManager(models.Manager):
@@ -44,8 +44,11 @@ class RoomManager(models.Manager):
         return qs.filter(blocked=False)
 
     def conference_accessible(self, conference: Conference):
-        return self.get_queryset().filter(conference=conference, blocked=False) \
+        return (
+            self.get_queryset()
+            .filter(conference=conference, blocked=False)
             .filter(Q(assembly__state_assembly__in=Assembly.PUBLIC_STATES) | Q(assembly__state_channel__in=Assembly.PUBLIC_STATES))
+        )
 
 
 class Room(BackendMixin, models.Model):
@@ -84,17 +87,14 @@ class Room(BackendMixin, models.Model):
 
     slug = models.SlugField(unique=True)
 
-    description = models.TextField(
-        blank=True, null=True,
-        help_text=_('Room__description__help'),
-        verbose_name=_('Room__description'))
+    description = models.TextField(blank=True, null=True, help_text=_('Room__description__help'), verbose_name=_('Room__description'))
     """Description of the room/project."""
     description_html = models.TextField(blank=True, null=True)
 
     is_public_fahrplan = models.BooleanField(default=False, help_text=_('Room__is_public_fahrplan__help'), verbose_name=_('Room__is_public_fahrplan'))
 
-    is_official = models.BooleanField(default=False, help_text=_("Room__is_official__help"), verbose_name=_("Room__is_official"))
-    official_room_order = models.IntegerField(default=0, help_text=_("Room__official_room_order__help"), verbose_name=_("Room__official_room_order"))
+    is_official = models.BooleanField(default=False, help_text=_('Room__is_official__help'), verbose_name=_('Room__is_official'))
+    official_room_order = models.IntegerField(default=0, help_text=_('Room__official_room_order__help'), verbose_name=_('Room__official_room_order'))
 
     room_type = models.CharField(max_length=20, choices=RoomType.choices, help_text=_('Room__type__help'), verbose_name=_('Room__type'))
     """Style of the room."""
@@ -121,21 +121,20 @@ class Room(BackendMixin, models.Model):
     """Arbitrary data necessary for room operation in the backend - used by the actual integration component (not for users!!!)."""
 
     backend_status = models.CharField(
-        max_length=20, blank=True,
+        max_length=20,
+        blank=True,
         choices=BackendMixin.BackendStatus.choices,
         default=BackendMixin.BackendStatus.NONE,
         help_text=_('Room__backend_status__help'),
-        verbose_name=_('Room__backend_status'))
+        verbose_name=_('Room__backend_status'),
+    )
     """The backend's status information."""
 
     capacity = models.PositiveIntegerField(blank=True, null=True, help_text=_('Room__capacity__help'), verbose_name=_('Room__capacity'))
     """A room's capacity, i.e. maximum of participants."""
 
     reserve_capacity = models.PositiveIntegerField(
-      default=20,
-      help_text=_('Room__reserve_capacity__help'),
-      verbose_name=_('Room__reserve_capacity'),
-      editable=False
+        default=20, help_text=_('Room__reserve_capacity__help'), verbose_name=_('Room__reserve_capacity'), editable=False
     )
     """Number of slots the deployment infrastructure should keep available for this room"""
 
@@ -209,8 +208,9 @@ class Room(BackendMixin, models.Model):
         return super().save(*args, update_fields=update_fields, **kwargs)
 
     @classmethod
-    def from_dict(cls, data: dict, conference: Conference, assembly: Assembly = None, existing=None, pop_used_keys: bool = False,
-                  allow_backend_roomtypes: bool = False):
+    def from_dict(
+        cls, data: dict, conference: Conference, assembly: Assembly = None, existing=None, pop_used_keys: bool = False, allow_backend_roomtypes: bool = False
+    ):
         """
         Loads an Room instance from the given dictionary.
         An existing event can be provided which's data is overwritten (in parts).
@@ -231,10 +231,10 @@ class Room(BackendMixin, models.Model):
         if existing:
             obj = existing
             if assembly:
-                assert obj.assembly == assembly, 'Existing room\'s assembly does not match given one.'
-                assert obj.conference == assembly.conference, 'Existing room\'s conference does not match the given assembly\'s one.'
+                assert obj.assembly == assembly, "Existing room's assembly does not match given one."
+                assert obj.conference == assembly.conference, "Existing room's conference does not match the given assembly's one."
             if conference:
-                assert obj.conference == conference, 'Existing room\'s conference does not match given one.'
+                assert obj.conference == conference, "Existing room's conference does not match given one."
         else:
             obj = cls(assembly=assembly, conference=conference)
         assert obj.conference_id is not None
@@ -291,21 +291,19 @@ class RoomLink(models.Model):
     room = models.ForeignKey(Room, related_name='links', on_delete=models.CASCADE)
     name = models.CharField(max_length=200)
 
-    link_type = models.CharField(
-        max_length=20, choices=LinkType.choices,
-        help_text=_('RoomLink__type__help'),
-        verbose_name=_('RoomLink__type'))
+    link_type = models.CharField(max_length=20, choices=LinkType.choices, help_text=_('RoomLink__type__help'), verbose_name=_('RoomLink__type'))
     link = models.CharField(max_length=255)
 
     conference_internal = models.BooleanField(
-        default=False,
-        help_text=_('RoomLink__conference_internal__help'),
-        verbose_name=_('RoomLink__conference_internal'))
+        default=False, help_text=_('RoomLink__conference_internal__help'), verbose_name=_('RoomLink__conference_internal')
+    )
     send_token = models.CharField(
-        max_length=20, choices=Token.choices,
+        max_length=20,
+        choices=Token.choices,
         default=Token.NONE,
         help_text=_('RoomLink__conference_internal__help'),
-        verbose_name=_('RoomLink__conference_internal'))
+        verbose_name=_('RoomLink__conference_internal'),
+    )
 
     def clean(self):
         if self.link_type in RoomLink.URL_LINKTYPES:
diff --git a/src/core/models/schedules.py b/src/core/models/schedules.py
index 16e39ba21..47d404ed7 100644
--- a/src/core/models/schedules.py
+++ b/src/core/models/schedules.py
@@ -1,19 +1,18 @@
-from datetime import timedelta
 import logging
-from uuid import uuid4, UUID
+from datetime import timedelta
+from uuid import UUID, uuid4
 
 from django.core.exceptions import ObjectDoesNotExist
 from django.db import models
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 
-from .assemblies import Assembly
-from .events import Event, EventAttachment, EventParticipant
-from .rooms import Room
 from ..fields import ConferenceReference
 from ..schedules import ScheduleTypeManager
 from ..utils import mask_url, str2bool
-
+from .assemblies import Assembly
+from .events import Event, EventAttachment, EventParticipant
+from .rooms import Room
 
 logger = logging.getLogger(__name__)
 
@@ -33,7 +32,8 @@ class ScheduleSource(models.Model):
     assembly = models.ForeignKey(Assembly, blank=True, null=True, related_name='schedule_sources', on_delete=models.CASCADE)
 
     import_type = models.CharField(
-        blank=False, max_length=20,
+        blank=False,
+        max_length=20,
         help_text=_('ScheduleSource__import_type__help'),
         verbose_name=_('ScheduleSource__import_type'),
     )
@@ -60,7 +60,8 @@ class ScheduleSource(models.Model):
         verbose_name=_('ScheduleSource__import_timeout'),
     )
     last_import = models.DateTimeField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('ScheduleSource__last_import__help'),
         verbose_name=_('ScheduleSource__last_import'),
     )
@@ -199,12 +200,14 @@ class ScheduleSource(models.Model):
                 item.delete()
 
             # log us skipping the entry
-            activity.append({
-                'action': 'skipped',
-                'type': item_type,
-                'source_id': item_source_id,
-                'local_id': None,
-            })
+            activity.append(
+                {
+                    'action': 'skipped',
+                    'type': item_type,
+                    'source_id': item_source_id,
+                    'local_id': None,
+                }
+            )
 
             # if we have the item locally but shall skip it, handle it as 'handled' anyway
             # so that it is not picked up again as e.g. 'missing_events'
@@ -240,13 +243,15 @@ class ScheduleSource(models.Model):
             except Exception as err:
                 # log the error
                 items[item_source_id] = None
-                activity.append({
-                    'action': 'error',
-                    'type': item_type,
-                    'source_id': item_source_id,
-                    'local_id': None,
-                    'message': str(err),
-                })
+                activity.append(
+                    {
+                        'action': 'error',
+                        'type': item_type,
+                        'source_id': item_source_id,
+                        'local_id': None,
+                        'message': str(err),
+                    }
+                )
 
                 # ... and delete the incomplete (wrong) mapping
                 mapping.delete()
@@ -255,12 +260,14 @@ class ScheduleSource(models.Model):
             else:
                 # store new item's data
                 items[item_source_id] = mapping.local_object
-                activity.append({
-                    'action': 'added',
-                    'type': item_type,
-                    'source_id': item_source_id,
-                    'local_id': str(mapping.local_id),
-                })
+                activity.append(
+                    {
+                        'action': 'added',
+                        'type': item_type,
+                        'source_id': item_source_id,
+                        'local_id': str(mapping.local_id),
+                    }
+                )
             return 'added'
 
         # note that we've seen the existing room in the imported data
@@ -273,12 +280,14 @@ class ScheduleSource(models.Model):
         if item_delete:
             mapping.local_object.delete()
             items[item_source_id] = None
-            activity.append({
-                'action': 'deleted',
-                'type': item_type,
-                'source_id': item_source_id,
-                'local_id': str(mapping.local_id),
-            })
+            activity.append(
+                {
+                    'action': 'deleted',
+                    'type': item_type,
+                    'source_id': item_source_id,
+                    'local_id': str(mapping.local_id),
+                }
+            )
             return 'deleted'
 
         # update data on existing room
@@ -297,12 +306,14 @@ class ScheduleSource(models.Model):
         mapping.local_object.save()
 
         items[item_source_id] = mapping.local_object
-        activity.append({
-            'action': 'seen',  # TODO: set to 'changed' if data was changed (returned by .from_dict()?)
-            'type': item_type,
-            'source_id': item_source_id,
-            'local_id': str(mapping.local_id),
-        })
+        activity.append(
+            {
+                'action': 'seen',  # TODO: set to 'changed' if data was changed (returned by .from_dict()?)
+                'type': item_type,
+                'source_id': item_source_id,
+                'local_id': str(mapping.local_id),
+            }
+        )
         return 'seen'
 
     def load_data(self, data):
@@ -324,18 +335,22 @@ class ScheduleSource(models.Model):
         if self.assembly:
             expected_rooms = list(self.assembly.rooms.values_list('id', flat=True))
         else:
-            expected_rooms = list(self.mappings.filter(
-                mapping_type=ScheduleSourceMapping.MappingType.ROOM,
-            ).values_list('local_id', flat=True))
-        expected_events = list(self.mappings.filter(
-            mapping_type=ScheduleSourceMapping.MappingType.EVENT,
-        ).values_list('local_id', flat=True))
+            expected_rooms = list(
+                self.mappings.filter(
+                    mapping_type=ScheduleSourceMapping.MappingType.ROOM,
+                ).values_list('local_id', flat=True)
+            )
+        expected_events = list(
+            self.mappings.filter(
+                mapping_type=ScheduleSourceMapping.MappingType.EVENT,
+            ).values_list('local_id', flat=True)
+        )
 
         # first, load the rooms (as they're needed for events)
         for r_id, r in data['rooms'].items():
             try:
                 if replace_conference_slug_prefix and r.get('slug', '').startswith(replace_conference_slug_prefix):
-                    r['slug'] = self.conference.slug + r['slug'][len(replace_conference_slug_prefix):]
+                    r['slug'] = self.conference.slug + r['slug'][len(replace_conference_slug_prefix) :]
 
                 action = self._load_dataitem(
                     activity=activity,
@@ -356,19 +371,21 @@ class ScheduleSource(models.Model):
                     rooms[r_id] = r_mapping.local_object
 
             except Exception as err:
-                activity.append({
-                    'action': 'error',
-                    'type': 'room',
-                    'source_id': r_id,
-                    'local_id': None,
-                    'message': str(err),
-                })
+                activity.append(
+                    {
+                        'action': 'error',
+                        'type': 'room',
+                        'source_id': r_id,
+                        'local_id': None,
+                        'message': str(err),
+                    }
+                )
 
         # then load events
         for e_id, e in data['events'].items():
             try:
                 if replace_conference_slug_prefix and e.get('slug', '').startswith(replace_conference_slug_prefix):
-                    e['slug'] = self.conference.slug + e['slug'][len(replace_conference_slug_prefix):]
+                    e['slug'] = self.conference.slug + e['slug'][len(replace_conference_slug_prefix) :]
 
                 self._load_dataitem(
                     activity=activity,
@@ -381,25 +398,29 @@ class ScheduleSource(models.Model):
                         'allow_kind': self.assembly.is_official if self.assembly else False,  # TODO: lookup assembly's room if not given
                         'allow_track': False,  # TODO
                         'room_lookup': lambda r_source_id: rooms.get(r_source_id),
-                    }
+                    },
                 )
             except Exception as err:
-                activity.append({
-                    'action': 'error',
-                    'type': 'event',
-                    'source_id': e_id,
-                    'local_id': e.get('uuid', None),
-                    'message': str(err),
-                })
+                activity.append(
+                    {
+                        'action': 'error',
+                        'type': 'event',
+                        'source_id': e_id,
+                        'local_id': e.get('uuid', None),
+                        'message': str(err),
+                    }
+                )
 
         # flag the non-loaded rooms as 'missing'
         for room_id in expected_rooms:
-            activity.append({
-                'action': 'missing',
-                'type': 'room',
-                'source_id': None,
-                'local_id': str(room_id),
-            })
+            activity.append(
+                {
+                    'action': 'missing',
+                    'type': 'room',
+                    'source_id': None,
+                    'local_id': str(room_id),
+                }
+            )
 
         # flag the non-loaded events as 'missing'
         for event_id in expected_events:
@@ -474,9 +495,8 @@ class ScheduleSourceMapping(models.Model):
             if room.assembly_id is not None:
                 if self.schedule_source.assembly_id is not None and room.assembly_id != self.schedule_source.assembly_id:
                     raise LocalObjectAccessViolation('Assembly of Room does not match.')
-            else:
-                if self.schedule_source.assembly_id is not None and room.conference_id != self.schedule_source.assembly.conference_id:
-                    raise LocalObjectAccessViolation('Conference of Room does not match.')
+            elif self.schedule_source.assembly_id is not None and room.conference_id != self.schedule_source.assembly.conference_id:
+                raise LocalObjectAccessViolation('Conference of Room does not match.')
             return room
 
         if self.mapping_type == self.MappingType.EVENT:
@@ -625,15 +645,18 @@ class ScheduleSourceImport(models.Model):
                 # create list of unique errors for summary
                 msgs = list({x['message'].split('\n')[0] for x in errors})
 
-            stats = ', '.join(
-                (t + '=' + str(sum(1 for x in activity if x['action'] == t))) for t in [
-                    'added', 'changed', 'seen', 'deleted', 'missing', 'error', 'skipped'
-                ]
-            ) + ' \n' + ' \n'.join(msgs)
+            stats = (
+                ', '.join(
+                    (t + '=' + str(sum(1 for x in activity if x['action'] == t)))
+                    for t in ['added', 'changed', 'seen', 'deleted', 'missing', 'error', 'skipped']
+                )
+                + ' \n'
+                + ' \n'.join(msgs)
+            )
             self.summary = ('DONE: ' + stats)[:200]
 
             if len(errors) > len(activity) / 2:
-                raise Exception("Too many errors, aborting import: " + stats)
+                raise Exception('Too many errors, aborting import: ' + stats)
 
             self.save(update_fields=['data', 'summary'])
 
diff --git a/src/core/models/shared.py b/src/core/models/shared.py
index 370c97745..77d69925d 100644
--- a/src/core/models/shared.py
+++ b/src/core/models/shared.py
@@ -64,21 +64,20 @@ class BackendMixin(models.Model):
     """Arbitrary data necessary for room operation in the backend - used by the actual integration component (not for users!!!)."""
 
     backend_status = models.CharField(
-        max_length=20, blank=True,
+        max_length=20,
+        blank=True,
         choices=BackendStatus.choices,
         default=BackendStatus.NONE,
         help_text=_('Room__backend_status__help'),
-        verbose_name=_('Room__backend_status'))
+        verbose_name=_('Room__backend_status'),
+    )
     """The backend's status information."""
 
     capacity = models.PositiveIntegerField(blank=True, null=True, help_text=_('Room__capacity__help'), verbose_name=_('Room__capacity'))
     """A room's capacity, i.e. maximum of participants."""
 
     reserve_capacity = models.PositiveIntegerField(
-      default=20,
-      help_text=_('Room__reserve_capacity__help'),
-      verbose_name=_('Room__reserve_capacity'),
-      editable=False
+        default=20, help_text=_('Room__reserve_capacity__help'), verbose_name=_('Room__reserve_capacity'), editable=False
     )
     """Number of slots the deployment infrastructure should keep available for this room"""
 
diff --git a/src/core/models/sso.py b/src/core/models/sso.py
index efa9d5ceb..816e4abcd 100644
--- a/src/core/models/sso.py
+++ b/src/core/models/sso.py
@@ -1,6 +1,7 @@
+from oauth2_provider.models import AbstractApplication
+
 from django.db import models
 from django.utils.translation import gettext_lazy as _
-from oauth2_provider.models import AbstractApplication
 
 from .assemblies import Assembly
 
@@ -8,6 +9,5 @@ from .assemblies import Assembly
 class Application(AbstractApplication):
     assembly = models.ForeignKey(Assembly, related_name='applications', blank=True, null=True, on_delete=models.CASCADE)
     internal_description = models.TextField(
-        blank=True, null=True,
-        help_text=_('Application__internal_description__help'),
-        verbose_name=_('Application__internal_description'))
+        blank=True, null=True, help_text=_('Application__internal_description__help'), verbose_name=_('Application__internal_description')
+    )
diff --git a/src/core/models/tags.py b/src/core/models/tags.py
index 15015992d..b16550330 100644
--- a/src/core/models/tags.py
+++ b/src/core/models/tags.py
@@ -1,3 +1,5 @@
+import contextlib
+
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
@@ -26,11 +28,9 @@ class ConferenceTag(models.Model):
     is_public = models.BooleanField(default=False, help_text=_('ConferenceTag__is_public__help'), verbose_name=_('ConferenceTag__is_public'))
 
     value_type = models.CharField(
-        max_length=20, choices=Type.choices, default=Type.SIMPLE,
-        help_text=_('ConferenceTag__value_type__help'), verbose_name=_('ConferenceTag__value_type'))
-    description = models.TextField(
-        blank=True, null=True,
-        help_text=_('ConferenceTag__description__help'), verbose_name=_('ConferenceTag__description'))
+        max_length=20, choices=Type.choices, default=Type.SIMPLE, help_text=_('ConferenceTag__value_type__help'), verbose_name=_('ConferenceTag__value_type')
+    )
+    description = models.TextField(blank=True, null=True, help_text=_('ConferenceTag__description__help'), verbose_name=_('ConferenceTag__description'))
 
     def __str__(self):
         return self.slug
@@ -38,9 +38,7 @@ class ConferenceTag(models.Model):
 
 class TagItem(models.Model):
     class Meta:
-        constraints = [
-            models.UniqueConstraint(fields=['target_type', 'target_id', 'tag'], name='unique_target_tag')
-        ]
+        constraints = [models.UniqueConstraint(fields=['target_type', 'target_id', 'tag'], name='unique_target_tag')]
 
     tag = models.ForeignKey(ConferenceTag, related_name='+', on_delete=models.CASCADE)
 
@@ -91,7 +89,7 @@ class TagItem(models.Model):
             return
 
         if self.tag.value_type == ConferenceTag.Type.BOOL:
-            if isinstance(new_value, bool) or isinstance(new_value, int):
+            if isinstance(new_value, (bool, int)):
                 self._value_int = 0 if new_value else 1
                 return
 
@@ -119,20 +117,20 @@ class TagItem(models.Model):
 
             elif self.tag.value_type == ConferenceTag.Type.INT:
                 if not isinstance(self.value, int):
-                    raise ValidationError('Tag\'s value must be integer.')
+                    raise ValidationError("Tag's value must be integer.")
 
             elif self.tag.value_type == ConferenceTag.Type.BOOL:
                 if self.value not in [True, False]:
-                    raise ValidationError('Tag\'s value must be a boolean.')
+                    raise ValidationError("Tag's value must be a boolean.")
 
     def __str__(self):
         if self.tag.value_type == ConferenceTag.Type.SIMPLE:
             return self.tag.slug
 
-        return '{}={}'.format(self.tag.slug, self.value)
+        return f'{self.tag.slug}={self.value}'
 
 
-class TaggedItemMixin(object):
+class TaggedItemMixin:
     @property
     def tags(self):
         qs = TagItem.objects.filter(target_type=ContentType.objects.get_for_model(type(self)), target_id=self.id, tag__is_public=True).select_related('tag')
@@ -164,10 +162,8 @@ class TaggedItemMixin(object):
 
     def remove_tag(self, tag, value=None):
         if not isinstance(tag, ConferenceTag):
-            try:
+            with contextlib.suppress(ConferenceTag.DoesNotExist):
                 tag = ConferenceTag.objects.get(conference=self.conference, slug=tag, is_public=True)
-            except ConferenceTag.DoesNotExist:
-                pass
 
         # TODO: check user's permission
         TagItem.objects.filter(tag=tag, target_type=ContentType.objects.get_for_model(type(self)), target_id=self.id).delete()
diff --git a/src/core/models/ticket.py b/src/core/models/ticket.py
index f0a7c60a7..e8804d537 100644
--- a/src/core/models/ticket.py
+++ b/src/core/models/ticket.py
@@ -1,16 +1,16 @@
 import logging
 
+import jwt
+
 from django.conf import settings
 from django.db import models
 from django.db.utils import IntegrityError
 from django.utils.translation import gettext_lazy as _
-import jwt
 
 from ..fields import ConferenceReference
 from .conference import Conference
 from .users import PlatformUser
 
-
 logger = logging.getLogger(__name__)
 
 
diff --git a/src/core/models/users.py b/src/core/models/users.py
index 41f979c34..f410861e5 100644
--- a/src/core/models/users.py
+++ b/src/core/models/users.py
@@ -3,7 +3,8 @@ import re
 from typing import Any
 from uuid import uuid4
 
-from django.db.models import Q
+from timezone_field import TimeZoneField
+
 from django.contrib.auth.models import AbstractUser
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
@@ -12,11 +13,11 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
 from django.core.mail import send_mail
 from django.core.validators import validate_email
 from django.db import models
+from django.db.models import Q
 from django.utils.functional import cached_property
+from django.utils.text import slugify
 from django.utils.timezone import now
 from django.utils.translation import gettext_lazy as _
-from django.utils.text import slugify
-from timezone_field import TimeZoneField
 
 from ..fields import ConferenceReference
 
@@ -29,157 +30,103 @@ class PlatformUser(AbstractUser):
         BOT = ('bot', _('PlatformUser__type-bot'))
 
     class Theme(models.TextChoices):
-        DARK = ('dark', _("PlatformUser__theme-dark"))
-        LIGHT = ('light', _("PlatformUser__theme-light"))
+        DARK = ('dark', _('PlatformUser__theme-dark'))
+        LIGHT = ('light', _('PlatformUser__theme-light'))
 
     INTERACTIVE_TYPES = [Type.HUMAN, Type.BOT]
     """User types which can be interactive (and can thus have an avatar and/or modified by e.g. the Engelsystem)."""
 
     user_type = models.CharField(
-        max_length=10, choices=Type.choices, default=Type.HUMAN,
-        help_text=_('PlatformUser__type__help'),
-        verbose_name=_('PlatformUser__type'))
+        max_length=10, choices=Type.choices, default=Type.HUMAN, help_text=_('PlatformUser__type__help'), verbose_name=_('PlatformUser__type')
+    )
 
     uuid = models.UUIDField(default=uuid4, unique=True)
 
     slug = models.SlugField(blank=False, unique=True)
     # legal stuff
     accepted_speakersagreement = models.BooleanField(
-        null=True,
-        help_text=_('PlatformUser__accepted_speakersagreement__help'),
-        verbose_name=_('PlatformUser__accepted_speakersagreement'))
+        null=True, help_text=_('PlatformUser__accepted_speakersagreement__help'), verbose_name=_('PlatformUser__accepted_speakersagreement')
+    )
 
     # self portrayal
-    show_name = models.BooleanField(
-        null=True,
-        help_text=_('PlatformUser__show_name__help'),
-        verbose_name=_('PlatformUser__show_name'))
-    avatar_url = models.URLField(
-        blank=True, null=True,
-        help_text=_('PlatformUser__avatar_url__help'),
-        verbose_name=_('PlatformUser__avatar_url'))
-    avatar_config = models.JSONField(
-        blank=True, null=True,
-        help_text=_('PlatformUser__avatar_config__help'),
-        verbose_name=_('PlatformUser__avatar_config'))
-    pronouns = models.CharField(
-        max_length=50, blank=True, default='',
-        help_text=_('PlatformUser__pronouns__help'),
-        verbose_name=_('PlatformUser__pronouns'))
-
-    status = models.CharField(
-        max_length=50, blank=True, null=True,
-        help_text=_('PlatformUser__status__help'),
-        verbose_name=_('PlatformUser__status'))
-    status_public = models.BooleanField(
-        null=True,
-        help_text=_('PlatformUser__status_public__help'),
-        verbose_name=_('PlatformUser__status_public'))
+    show_name = models.BooleanField(null=True, help_text=_('PlatformUser__show_name__help'), verbose_name=_('PlatformUser__show_name'))
+    avatar_url = models.URLField(blank=True, null=True, help_text=_('PlatformUser__avatar_url__help'), verbose_name=_('PlatformUser__avatar_url'))
+    avatar_config = models.JSONField(blank=True, null=True, help_text=_('PlatformUser__avatar_config__help'), verbose_name=_('PlatformUser__avatar_config'))
+    pronouns = models.CharField(max_length=50, blank=True, default='', help_text=_('PlatformUser__pronouns__help'), verbose_name=_('PlatformUser__pronouns'))
+
+    status = models.CharField(max_length=50, blank=True, null=True, help_text=_('PlatformUser__status__help'), verbose_name=_('PlatformUser__status'))
+    status_public = models.BooleanField(null=True, help_text=_('PlatformUser__status_public__help'), verbose_name=_('PlatformUser__status_public'))
     timezone = TimeZoneField(
         help_text=_('PlatformUser__timezone__help'),
         verbose_name=_('PlatformUser__timezone'),
         default='Europe/Berlin',
-        choices_display="WITH_GMT_OFFSET",
+        choices_display='WITH_GMT_OFFSET',
     )
 
     # Accessibility Options
-    no_animations = models.BooleanField(
-        default=False,
-        help_text=_('PlatformUser__no_animations__help'),
-        verbose_name=_('PlatformUser__no_animations'))
-    colorblind = models.BooleanField(
-        default=False,
-        help_text=_('PlatformUser__colorblind__help'),
-        verbose_name=_('PlatformUser__colorblind'))
-    high_contrast = models.BooleanField(
-        default=False,
-        help_text=_('PlatformUser__high_contrast__help'),
-        verbose_name=_('PlatformUser__high_contrast'))
+    no_animations = models.BooleanField(default=False, help_text=_('PlatformUser__no_animations__help'), verbose_name=_('PlatformUser__no_animations'))
+    colorblind = models.BooleanField(default=False, help_text=_('PlatformUser__colorblind__help'), verbose_name=_('PlatformUser__colorblind'))
+    high_contrast = models.BooleanField(default=False, help_text=_('PlatformUser__high_contrast__help'), verbose_name=_('PlatformUser__high_contrast'))
     tag_ignorelist = pg_fields.ArrayField(
-        models.SlugField(
-        ), blank=True, default=list,
-        help_text=_('PlatformUser__tag_ignorelist__help'),
-        verbose_name=_('PlatformUser__tag_ignorelist'))
+        models.SlugField(), blank=True, default=list, help_text=_('PlatformUser__tag_ignorelist__help'), verbose_name=_('PlatformUser__tag_ignorelist')
+    )
     theme = models.CharField(
-        max_length=50, choices=Theme.choices, default=Theme.DARK,
-        verbose_name=_("PlatformUser__theme"),
-        help_text=_("PlatformUser__theme__help"))
+        max_length=50, choices=Theme.choices, default=Theme.DARK, verbose_name=_('PlatformUser__theme'), help_text=_('PlatformUser__theme__help')
+    )
 
     # Disturbance Settings
-    receive_dms = models.BooleanField(
-        default=True,
-        help_text=_('PlatformUser__receive_dms__help'),
-        verbose_name=_('PlatformUser__receive_dms'))
+    receive_dms = models.BooleanField(default=True, help_text=_('PlatformUser__receive_dms__help'), verbose_name=_('PlatformUser__receive_dms'))
     receive_dm_images = models.BooleanField(
-        default=False,
-        help_text=_('PlatformUser__receive_dm_images__help'),
-        verbose_name=_('PlatformUser__receive_dm_images'))
-    receive_audio = models.BooleanField(
-        default=True,
-        help_text=_('PlatformUser__receive_audio__help'),
-        verbose_name=_('PlatformUser__receive_audio'))
-    receive_video = models.BooleanField(
-        default=True,
-        help_text=_('PlatformUser__receive_video__help'),
-        verbose_name=_('PlatformUser__receive_video'))
+        default=False, help_text=_('PlatformUser__receive_dm_images__help'), verbose_name=_('PlatformUser__receive_dm_images')
+    )
+    receive_audio = models.BooleanField(default=True, help_text=_('PlatformUser__receive_audio__help'), verbose_name=_('PlatformUser__receive_audio'))
+    receive_video = models.BooleanField(default=True, help_text=_('PlatformUser__receive_video__help'), verbose_name=_('PlatformUser__receive_video'))
 
     # Administrative
-    shadow_banned = models.BooleanField(
-        default=False,
-        help_text=_('PlatformUser__shadow_banned__help'),
-        verbose_name=_('PlatformUser__shadow_banned'))
+    shadow_banned = models.BooleanField(default=False, help_text=_('PlatformUser__shadow_banned__help'), verbose_name=_('PlatformUser__shadow_banned'))
     admin_notification = models.TextField(
-        default='', blank=True,
-        help_text=_('PlatformUser__admin_notification__help'),
-        verbose_name=_('PlatformUser__admin_notification'))
+        default='', blank=True, help_text=_('PlatformUser__admin_notification__help'), verbose_name=_('PlatformUser__admin_notification')
+    )
 
     audio_muted = models.BooleanField(default=False)
     audio_volume = models.FloatField(blank=True, null=True)
 
     # privacy settings
     autoaccept_contacts = models.BooleanField(
-        default=False,
-        help_text=_('PlatformUser__autoaccept_contacts__help'),
-        verbose_name=_('PlatformUser__autoaccept_contacts'))
+        default=False, help_text=_('PlatformUser__autoaccept_contacts__help'), verbose_name=_('PlatformUser__autoaccept_contacts')
+    )
 
-    is_searchable = models.BooleanField(
-        default=False,
-        help_text=_('PlatformUser__is_searchable__help'),
-        verbose_name=_('PlatformUser__is_searchable'))
+    is_searchable = models.BooleanField(default=False, help_text=_('PlatformUser__is_searchable__help'), verbose_name=_('PlatformUser__is_searchable'))
 
     # menu settings
-    menu = models.JSONField(
-        blank=True, default=dict,
-        help_text=_('PlatformUser__menu__help'),
-        verbose_name=_('PlatformUser__menu'))
+    menu = models.JSONField(blank=True, default=dict, help_text=_('PlatformUser__menu__help'), verbose_name=_('PlatformUser__menu'))
 
     # workadventure preferences
     wa_block_external_content = models.BooleanField(
         default=False,
-        help_text=_("PlatformUser__wa_block_external_content__help"),
-        verbose_name=_("PlatformUser__wa_block_external_content"),
+        help_text=_('PlatformUser__wa_block_external_content__help'),
+        verbose_name=_('PlatformUser__wa_block_external_content'),
     )
     wa_block_ambient_sounds = models.BooleanField(
         default=False,
-        help_text=_("PlatformUser__wa_block_ambient_sounds__help"),
-        verbose_name=_("PlatformUser__wa_block_ambient_sounds"),
+        help_text=_('PlatformUser__wa_block_ambient_sounds__help'),
+        verbose_name=_('PlatformUser__wa_block_ambient_sounds'),
     )
     wa_ignore_follow_requests = models.BooleanField(
         default=False,
-        help_text=_("PlatformUser__wa_ignore_follow_requests__help"),
-        verbose_name=_("PlatformUser__wa_ignore_follow_requests"),
+        help_text=_('PlatformUser__wa_ignore_follow_requests__help'),
+        verbose_name=_('PlatformUser__wa_ignore_follow_requests'),
     )
     wa_force_website_trigger = models.BooleanField(
         default=False,
-        help_text=_("PlatformUser__wa_force_website_trigger__help"),
-        verbose_name=_("PlatformUser__wa_force_website_trigger"),
+        help_text=_('PlatformUser__wa_force_website_trigger__help'),
+        verbose_name=_('PlatformUser__wa_force_website_trigger'),
     )
 
     # security settings
     allow_reset_non_primary = models.BooleanField(
-        default=False,
-        help_text=_('PlatformUser__allow_reset_non_primary__help'),
-        verbose_name=_('PlatformUser__allow_reset_non_primary'))
+        default=False, help_text=_('PlatformUser__allow_reset_non_primary__help'), verbose_name=_('PlatformUser__allow_reset_non_primary')
+    )
 
     @classmethod
     def get_user_flags(cls, user):
@@ -194,7 +141,7 @@ class PlatformUser(AbstractUser):
 
     @classmethod
     def get_anonymous_user(cls):
-        class AnonUser(object):
+        class AnonUser:
             def __init__(self):
                 for x in cls._meta.fields:
                     setattr(self, x.name, x.default if x.default != models.fields.NOT_PROVIDED else None)
@@ -206,7 +153,7 @@ class PlatformUser(AbstractUser):
             is_authenticated = False
 
             def save(self, *args, **kwargs):
-                raise Exception()
+                raise Exception
 
         return AnonUser()
 
@@ -231,8 +178,9 @@ class PlatformUser(AbstractUser):
         return info.get('character_layers', [])
 
     def set_character_layers(self, value):
+        # TODO: Replace both asserts with proper checks
         assert isinstance(value, list), 'character layers must be an array'
-        # assert all(isinstance(x, int) for x in value), 'character layers must be an array of integers'
+        # assert all(isinstance(x, int) for x in value), 'character layers must be an array of integers' # noqa: ERA001
 
         info = self.avatar_config or {}
         info['character_layers'] = value
@@ -281,8 +229,11 @@ class PlatformUser(AbstractUser):
 
         if (avatar_custom := update_data.get('custom', _UNSET)) != _UNSET:
             if avatar_custom is not None:
-                assert isinstance(avatar_custom, list) and len(avatar_custom) == CUSTOM_LENGTH and \
-                    all(isinstance(x, int) and MIN_CUSTOM_INDEX <= x <= MAX_CUSTOM_INDEX for x in avatar_custom)
+                assert (
+                    isinstance(avatar_custom, list)
+                    and len(avatar_custom) == CUSTOM_LENGTH
+                    and all(isinstance(x, int) and MIN_CUSTOM_INDEX <= x <= MAX_CUSTOM_INDEX for x in avatar_custom)
+                )
 
             info['custom'] = avatar_custom
 
@@ -318,9 +269,7 @@ class PlatformUser(AbstractUser):
 
     @property
     def guardians(self):
-        return self.contacts.filter(is_guardian=True) \
-            .annotate(pk=models.F('contact__pk'), username=models.F('contact__username')) \
-            .values('pk', 'username')
+        return self.contacts.filter(is_guardian=True).annotate(pk=models.F('contact__pk'), username=models.F('contact__username')).values('pk', 'username')
 
     def get_staticpage_groups(self, conference):
         """Fetches the ConferenceMember's static_page_groups."""
@@ -401,39 +350,28 @@ class PlatformUser(AbstractUser):
         ).exists()
 
     def get_verified_mail_addresses(self):
-        return list(self.communication_channels.filter(
-            channel=UserCommunicationChannel.Channel.MAIL,
-            is_verified=True,
-        ).values_list('address', flat=True))
+        return list(
+            self.communication_channels.filter(
+                channel=UserCommunicationChannel.Channel.MAIL,
+                is_verified=True,
+            ).values_list('address', flat=True)
+        )
 
 
 class UserContact(models.Model):
     user = models.ForeignKey(PlatformUser, related_name='contacts', on_delete=models.CASCADE)
     contact = models.ForeignKey(PlatformUser, related_name='+', on_delete=models.CASCADE)
 
-    pending = models.BooleanField(
-        default=True,
-        help_text=_('UserContact__pending__help'),
-        verbose_name=_('UserContact__pending'))
+    pending = models.BooleanField(default=True, help_text=_('UserContact__pending__help'), verbose_name=_('UserContact__pending'))
 
-    share_status = models.BooleanField(
-        null=True,
-        help_text=_('UserContact__share_status__help'),
-        verbose_name=_('UserContact__share_status'))
+    share_status = models.BooleanField(null=True, help_text=_('UserContact__share_status__help'), verbose_name=_('UserContact__share_status'))
 
-    receive_dms = models.BooleanField(
-        default=True,
-        help_text=_('UserContact__receive_dms__help'),
-        verbose_name=_('UserContact__receive_dms'))
+    receive_dms = models.BooleanField(default=True, help_text=_('UserContact__receive_dms__help'), verbose_name=_('UserContact__receive_dms'))
     receive_dm_images = models.BooleanField(
-        default=False,
-        help_text=_('UserContact__receive_dm_images__help'),
-        verbose_name=_('UserContact__receive_dm_images'))
+        default=False, help_text=_('UserContact__receive_dm_images__help'), verbose_name=_('UserContact__receive_dm_images')
+    )
 
-    is_guardian = models.BooleanField(
-        default=False,
-        help_text=_('UserContact__is_guardian__help'),
-        verbose_name=_('UserContact__is_guardian'))
+    is_guardian = models.BooleanField(default=False, help_text=_('UserContact__is_guardian__help'), verbose_name=_('UserContact__is_guardian'))
 
     @cached_property
     def reverse_contact(self):
@@ -454,9 +392,7 @@ _RE_VANITY = re.compile(r'^(00|\+)49[- ]?(180|[789]00)[- ]?[A-Za-z0-9 -]+$')
 
 class UserCommunicationChannel(models.Model):
     class Meta:
-        constraints = [
-            models.UniqueConstraint(fields=['user', 'channel', 'is_primary'], condition=Q(is_primary=True), name='unique_channel_primary')
-        ]
+        constraints = [models.UniqueConstraint(fields=['user', 'channel', 'is_primary'], condition=Q(is_primary=True), name='unique_channel_primary')]
 
     class Channel(models.TextChoices):
         MAIL = 'mail', _('UserCommunicationChannel__channel-mail')
@@ -469,31 +405,23 @@ class UserCommunicationChannel(models.Model):
     user = models.ForeignKey(PlatformUser, related_name='communication_channels', on_delete=models.CASCADE)
 
     channel = models.CharField(
-        max_length=20, choices=Channel.choices,
-        help_text=_('UserCommunicationChannel__channel__help'),
-        verbose_name=_('UserCommunicationChannel__channel'))
-    address = models.CharField(
-        max_length=255,
-        help_text=_('UserCommunicationChannel__address__help'),
-        verbose_name=_('UserCommunicationChannel__address'))
+        max_length=20, choices=Channel.choices, help_text=_('UserCommunicationChannel__channel__help'), verbose_name=_('UserCommunicationChannel__channel')
+    )
+    address = models.CharField(max_length=255, help_text=_('UserCommunicationChannel__address__help'), verbose_name=_('UserCommunicationChannel__address'))
 
     is_verified = models.BooleanField(
-        default=False,
-        help_text=_('UserCommunicationChannel__is_verified__help'),
-        verbose_name=_('UserCommunicationChannel__is_verified'))
+        default=False, help_text=_('UserCommunicationChannel__is_verified__help'), verbose_name=_('UserCommunicationChannel__is_verified')
+    )
     is_primary = models.BooleanField(
-        default=False,
-        help_text=_('UserCommunicationChannel__is_primary__help'),
-        verbose_name=_('UserCommunicationChannel__is_primary'))
+        default=False, help_text=_('UserCommunicationChannel__is_primary__help'), verbose_name=_('UserCommunicationChannel__is_primary')
+    )
 
     use_for_notifications = models.BooleanField(
-        default=False,
-        help_text=_('UserCommunicationChannel__use_for_notifications__help'),
-        verbose_name=_('UserCommunicationChannel__use_for_notifications'))
+        default=False, help_text=_('UserCommunicationChannel__use_for_notifications__help'), verbose_name=_('UserCommunicationChannel__use_for_notifications')
+    )
     show_public = models.BooleanField(
-        default=False,
-        help_text=_('UserCommunicationChannel__show_public__help'),
-        verbose_name=_('UserCommunicationChannel__show_public'))
+        default=False, help_text=_('UserCommunicationChannel__show_public__help'), verbose_name=_('UserCommunicationChannel__show_public')
+    )
 
     _logger = logging.getLogger(__name__)
 
@@ -584,7 +512,7 @@ class UserCommunicationChannel(models.Model):
                 )
                 return False
 
-        raise NotImplementedError(f'Don\'t know how to send to a channel of type {self.channel}')
+        raise NotImplementedError(f"Don't know how to send to a channel of type {self.channel}")
 
 
 class UserTimelineEntry(models.Model):
@@ -593,22 +521,13 @@ class UserTimelineEntry(models.Model):
     user = models.ForeignKey(PlatformUser, related_name='timeline', on_delete=models.CASCADE)
     conference = ConferenceReference(related_name='+')
 
-    timestamp = models.DateTimeField(
-        default=now,
-        help_text=_('UserTimelineEntry__timestamp__help'),
-        verbose_name=_('UserTimelineEntry__timestamp'))
+    timestamp = models.DateTimeField(default=now, help_text=_('UserTimelineEntry__timestamp__help'), verbose_name=_('UserTimelineEntry__timestamp'))
 
     target_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
     target_id = models.UUIDField()
     target = GenericForeignKey('target_type', 'target_id')
 
-    is_bookmark = models.BooleanField(
-        default=False,
-        help_text=_('UserTimelineEntry__is_bookmark__help'),
-        verbose_name=_('UserTimelineEntry__is_bookmark'))
+    is_bookmark = models.BooleanField(default=False, help_text=_('UserTimelineEntry__is_bookmark__help'), verbose_name=_('UserTimelineEntry__is_bookmark'))
     """Indicates a timeline entry 'saved for later'."""
 
-    comment = models.TextField(
-        blank=True, null=True,
-        help_text=_('UserTimelineEntry__comment__help'),
-        verbose_name=_('UserTimelineEntry__comment'))
+    comment = models.TextField(blank=True, null=True, help_text=_('UserTimelineEntry__comment__help'), verbose_name=_('UserTimelineEntry__comment'))
diff --git a/src/core/models/voucher.py b/src/core/models/voucher.py
index 1f1ae163a..835d04b33 100644
--- a/src/core/models/voucher.py
+++ b/src/core/models/voucher.py
@@ -85,27 +85,15 @@ class Voucher(models.Model):
 
     id = models.UUIDField(default=uuid4, primary_key=True, editable=False)
     conference = ConferenceReference(related_name='vouchers')
-    name = models.CharField(
-        max_length=200,
-        help_text=_('Voucher__name__help'),
-        verbose_name=_('Voucher__name'))
-
-    enabled = models.BooleanField(
-        default=False,
-        help_text=_('Voucher__enabled__help'),
-        verbose_name=_('Voucher__enabled'))
+    name = models.CharField(max_length=200, help_text=_('Voucher__name__help'), verbose_name=_('Voucher__name'))
+
+    enabled = models.BooleanField(default=False, help_text=_('Voucher__enabled__help'), verbose_name=_('Voucher__enabled'))
     """Mark the voucher as being available. This is intended to be used as a 'draft' marker."""
 
-    show_always = models.BooleanField(
-        default=False,
-        help_text=_('Voucher__show_always__help'),
-        verbose_name=_('Voucher__show_always'))
+    show_always = models.BooleanField(default=False, help_text=_('Voucher__show_always__help'), verbose_name=_('Voucher__show_always'))
     """Show the voucher in any case for a target, even if it hasn't been assigned yet."""
 
-    hide_from_staff = models.BooleanField(
-        default=False,
-        help_text=_('Voucher__hide_from_staff__help'),
-        verbose_name=_('Voucher__hide_from_staff'))
+    hide_from_staff = models.BooleanField(default=False, help_text=_('Voucher__hide_from_staff__help'), verbose_name=_('Voucher__hide_from_staff'))
     """Hide the values even from staff (unless they are Voucher Admin)."""
 
     assignment = models.CharField(
@@ -114,27 +102,16 @@ class Voucher(models.Model):
         choices=Assignment.choices,
         default=Assignment.MANUAL,
         help_text=_('Voucher__assignment__help'),
-        verbose_name=_('Voucher__assignment'))
+        verbose_name=_('Voucher__assignment'),
+    )
 
     data = models.CharField(
-        max_length=20,
-        blank=False,
-        choices=Data.choices,
-        default=Data.TEXT,
-        help_text=_('Voucher__data__help'),
-        verbose_name=_('Voucher__data'))
+        max_length=20, blank=False, choices=Data.choices, default=Data.TEXT, help_text=_('Voucher__data__help'), verbose_name=_('Voucher__data')
+    )
 
-    target = models.CharField(
-        max_length=20,
-        blank=False,
-        choices=Target.choices,
-        help_text=_('Voucher__target__help'),
-        verbose_name=_('Voucher__target'))
+    target = models.CharField(max_length=20, blank=False, choices=Target.choices, help_text=_('Voucher__target__help'), verbose_name=_('Voucher__target'))
 
-    description = models.TextField(
-        blank=True, null=True,
-        help_text=_('Voucher__description__help'),
-        verbose_name=_('Voucher__description'))
+    description = models.TextField(blank=True, null=True, help_text=_('Voucher__description__help'), verbose_name=_('Voucher__description'))
 
     objects = VoucherManager()
     logger = logging.getLogger(__name__)
@@ -201,8 +178,9 @@ class Voucher(models.Model):
         for assembly in missing_assignments:
             # skip non-public assembly/channel if auto-assignment is for public ones only
             if self.assignment == self.Assignment.ON_PUBLIC:
-                if (self.target == self.Target.ASSEMBLY and not assembly.is_public_assembly) or \
-                   (self.target == self.Target.CHANNEL and not assembly.is_public_channel):
+                if (self.target == self.Target.ASSEMBLY and not assembly.is_public_assembly) or (
+                    self.target == self.Target.CHANNEL and not assembly.is_public_channel
+                ):
                     continue
 
             # abort (but warn) if no more available entries are available
@@ -222,23 +200,11 @@ class Voucher(models.Model):
 
 class VoucherEntry(models.Model):
     voucher = models.ForeignKey(Voucher, on_delete=models.CASCADE, related_name='entries')
-    content = models.TextField(
-        blank=True, null=True,
-        help_text=_('VoucherEntry__content__help'),
-        verbose_name=_('VoucherEntry__content'))
-
-    created = models.DateTimeField(
-        auto_now_add=True,
-        help_text=_('VoucherEntry__created__help'),
-        verbose_name=_('VoucherEntry__created'))
-    expires = models.DateTimeField(
-        blank=True, null=True,
-        help_text=_('VoucherEntry__expires__help'),
-        verbose_name=_('VoucherEntry__expires'))
-    assigned = models.DateTimeField(
-        blank=True, null=True,
-        help_text=_('VoucherEntry__assigned__help'),
-        verbose_name=_('VoucherEntry__assigned'))
+    content = models.TextField(blank=True, null=True, help_text=_('VoucherEntry__content__help'), verbose_name=_('VoucherEntry__content'))
+
+    created = models.DateTimeField(auto_now_add=True, help_text=_('VoucherEntry__created__help'), verbose_name=_('VoucherEntry__created'))
+    expires = models.DateTimeField(blank=True, null=True, help_text=_('VoucherEntry__expires__help'), verbose_name=_('VoucherEntry__expires'))
+    assigned = models.DateTimeField(blank=True, null=True, help_text=_('VoucherEntry__assigned__help'), verbose_name=_('VoucherEntry__assigned'))
 
     assigned_assembly = models.ForeignKey(Assembly, on_delete=models.SET_NULL, related_name='vouchers', blank=True, null=True)
     assigned_user = models.ForeignKey(PlatformUser, on_delete=models.SET_NULL, related_name='vouchers', blank=True, null=True)
diff --git a/src/core/models/workadventure.py b/src/core/models/workadventure.py
index f50e1b52a..5c9fc1d7a 100644
--- a/src/core/models/workadventure.py
+++ b/src/core/models/workadventure.py
@@ -1,5 +1,6 @@
 from datetime import timedelta
 from uuid import uuid4
+
 from django.conf import settings
 from django.contrib.auth.models import Group
 from django.contrib.postgres.fields import ArrayField
@@ -9,11 +10,11 @@ from django.db.models import Q
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 
+from ..fields import ConferenceReference
+from ..utils import generate_token
 from .assemblies import Assembly
 from .conference import Conference, ConferenceMember, PlatformUser
 from .rooms import Room
-from ..fields import ConferenceReference
-from ..utils import generate_token
 
 
 class WorkadventureSession(models.Model):
@@ -30,13 +31,15 @@ class WorkadventureSession(models.Model):
     )
 
     token = models.CharField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         max_length=100,
         help_text=_('WorkadventureSession__token__help'),
         verbose_name=_('WorkadventureSession__token'),
     )
     token_expiry = models.DateTimeField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('WorkadventureSession__token_expiry__help'),
         verbose_name=_('WorkadventureSession__token_expiry'),
     )
@@ -44,7 +47,8 @@ class WorkadventureSession(models.Model):
     room = models.ForeignKey(
         Room,
         on_delete=models.SET_NULL,
-        blank=True, null=True,
+        blank=True,
+        null=True,
         related_name='+',
         help_text=_('WorkadventureSession__room__help'),
         verbose_name=_('WorkadventureSession__room'),
@@ -57,7 +61,8 @@ class WorkadventureSession(models.Model):
         default=list,
     )
     additional_data = models.JSONField(
-        blank=True, null=True,
+        blank=True,
+        null=True,
         help_text=_('WorkadventureSession__additional_data__help'),
         verbose_name=_('WorkadventureSession__additional_data'),
     )
@@ -107,10 +112,9 @@ class WorkadventureSession(models.Model):
             user_tags.add('admin')
         if is_angel:
             user_tags.add('angel')
-        for slug in Assembly.objects \
-                .associated_to_user(self.user, self.conference) \
-                .filter(state_assembly__in=Assembly.PUBLIC_STATES) \
-                .values_list('slug', flat=True):
+        for slug in (
+            Assembly.objects.associated_to_user(self.user, self.conference).filter(state_assembly__in=Assembly.PUBLIC_STATES).values_list('slug', flat=True)
+        ):
             user_tags.add('assembly_' + slug)
 
         result = {
@@ -143,9 +147,9 @@ class WorkadventureSession(models.Model):
         # TODO: fehlt hier nicht ein "banned" flag? was ist mit den restlichen Flags?
         # TODO: expand textures and character layers
         # TODO: bei check-user tauchen folgende Zusatzfelder auf:
-        #         mapUrlStart: string;
+        #         mapUrlStart: string; # noqa: ERA001
         #         tags: string[];
-        #         policy_type: number;
+        #         policy_type: number; # noqa: ERA001
         return result
 
     def update_userdata(self, data):
diff --git a/src/core/schedules/__init__.py b/src/core/schedules/__init__.py
index f47a72e9b..4f08b004b 100644
--- a/src/core/schedules/__init__.py
+++ b/src/core/schedules/__init__.py
@@ -1,6 +1,5 @@
 from .base import BaseScheduleSupport, ScheduleTypeManager
 
-
 __all__ = [
     'BaseScheduleSupport',
     'ScheduleTypeManager',
diff --git a/src/core/schedules/base.py b/src/core/schedules/base.py
index eb2751279..6d8cd6ce0 100644
--- a/src/core/schedules/base.py
+++ b/src/core/schedules/base.py
@@ -1,6 +1,6 @@
+import logging
 from abc import ABCMeta, abstractmethod
 from datetime import timedelta
-import logging
 
 from django.conf import settings
 from django.utils import timezone
@@ -8,9 +8,7 @@ from django.utils.module_loading import import_string
 
 
 def filter_additional_data(data: dict) -> dict:
-    return {k: v for k, v in data.items() if (v and k not in [
-        'guid', 'room', 'start', 'date', 'duration', 'title', 'abstract', 'description', 'language'
-    ])}
+    return {k: v for k, v in data.items() if (v and k not in ['guid', 'room', 'start', 'date', 'duration', 'title', 'abstract', 'description', 'language'])}
 
 
 def schedule_time_to_timedelta(s: str) -> timedelta:
@@ -135,7 +133,7 @@ class BaseScheduleSupport(metaclass=ABCMeta):
         raise NotImplementedError('push(data) was not overriden for ScheduleSupport')
 
 
-class _ScheduleTypeManager(object):
+class _ScheduleTypeManager:
     def __init__(self):
         self.types = {}
         self._initialize_from_settings()
diff --git a/src/core/schedules/schedulejson.py b/src/core/schedules/schedulejson.py
index d75d9127d..72c6e1584 100644
--- a/src/core/schedules/schedulejson.py
+++ b/src/core/schedules/schedulejson.py
@@ -1,5 +1,6 @@
-from collections import OrderedDict
 import json
+from collections import OrderedDict
+
 import requests
 from requests_file import FileAdapter
 
@@ -37,32 +38,32 @@ class ScheduleJSONSupport(BaseScheduleSupport):
         schedule = ScheduleJSON.from_url(self.remote_url)
 
         return {
-            "rooms": {
-                r['name']: r for r in schedule.rooms()
-            },
-            "events": {
+            'rooms': {r['name']: r for r in schedule.rooms()},
+            'events': {
                 e.get('id'): {
-                    "guid": e.get('guid'),
-                    "slug": e.get('slug').split(f"{e.get('id')}-")[1][0:150].strip('-') or e.get('slug')[0:150].strip('-'),
-                    "name": e.get('title'),
-                    "language": e.get('language'),
-                    "abstract": e.get('abstract') or '',
-                    "description": e.get('description') or '',
-                    "track": e.get('track'),
-                    "room": e.get('room'),
-                    "schedule_start": e.get('date'),
-                    "schedule_duration": str(schedule_time_to_timedelta(e.get('duration'))),
-                    "is_public": True,
-                    "additional_data": filter_additional_data(e)
-                } for e in schedule.events()
-            }
+                    'guid': e.get('guid'),
+                    'slug': e.get('slug').split(f"{e.get('id')}-")[1][0:150].strip('-') or e.get('slug')[0:150].strip('-'),
+                    'name': e.get('title'),
+                    'language': e.get('language'),
+                    'abstract': e.get('abstract') or '',
+                    'description': e.get('description') or '',
+                    'track': e.get('track'),
+                    'room': e.get('room'),
+                    'schedule_start': e.get('date'),
+                    'schedule_duration': str(schedule_time_to_timedelta(e.get('duration'))),
+                    'is_public': True,
+                    'additional_data': filter_additional_data(e),
+                }
+                for e in schedule.events()
+            },
         }
 
 
 class ScheduleJSON:
-    '''
+    """
     Schedule from JSON document
-    '''
+    """
+
     _schedule = None
 
     def __init__(self, json):
@@ -73,7 +74,7 @@ class ScheduleJSON:
         r = s.get(url)
 
         if r.ok is False:
-            raise Exception('Request failed, HTTP {0}.'.format(r.status_code))
+            raise Exception(f'Request failed, HTTP {r.status_code}.')
 
         # maintain order from input file
         schedule = json.JSONDecoder(object_pairs_hook=OrderedDict).decode(r.text)
@@ -104,13 +105,12 @@ class ScheduleJSON:
         for day in self.days():
             for roomname in day.get('rooms'):
                 rooms.add(roomname)
-        return [{"name": name} for name in rooms]
+        return [{'name': name} for name in rooms]
 
     def events(self):
         for day in self.days():
             for room in day.get('rooms'):
-                for event in day.get('rooms')[room]:
-                    yield event
+                yield from day.get('rooms')[room]
 
     def __str__(self):
         return json.dumps(self._schedule, indent=2)
diff --git a/src/core/schedules/schedulexml.py b/src/core/schedules/schedulexml.py
index b38afac3b..679adfa6f 100644
--- a/src/core/schedules/schedulexml.py
+++ b/src/core/schedules/schedulexml.py
@@ -1,12 +1,13 @@
 import collections
 import datetime
-from django.utils.dateparse import parse_datetime
+from xml.etree import ElementTree
+
 import requests
 from requests_file import FileAdapter
-from xml.etree import ElementTree
 
-from .base import BaseScheduleSupport, filter_additional_data, schedule_time_to_timedelta
+from django.utils.dateparse import parse_datetime
 
+from .base import BaseScheduleSupport, filter_additional_data, schedule_time_to_timedelta
 
 s = requests.Session()
 s.mount('file://', FileAdapter())
@@ -40,34 +41,34 @@ class ScheduleXMLSupport(BaseScheduleSupport):
         schedule = ScheduleXML.from_url(self.remote_url)
 
         return {
-            "rooms": {
-                r['name']: r for r in schedule.rooms()
-            },
-            "events": {
+            'rooms': {r['name']: r for r in schedule.rooms()},
+            'events': {
                 e.get('id'): {
-                    "guid": e.get('guid'),
-                    "slug": e.get('slug').split(f"{e.get('id')}-")[1][0:150].strip('-') or e.get('slug')[0:150].strip('-'),
-                    "name": e.get('title'),
-                    "language": e.get('language'),
-                    "abstract": e.get('abstract') or '',
-                    "description": e.get('description') or '',
-                    "track": e.get('track'),
-                    "room": e.get('room'),
-                    "schedule_start": e.get('date'),
-                    "schedule_duration": str(schedule_time_to_timedelta(e.get('duration'))),
-                    "is_public": True,
-                    "additional_data": filter_additional_data(e)
-                } for e in schedule.events()
-            }
+                    'guid': e.get('guid'),
+                    'slug': e.get('slug').split(f"{e.get('id')}-")[1][0:150].strip('-') or e.get('slug')[0:150].strip('-'),
+                    'name': e.get('title'),
+                    'language': e.get('language'),
+                    'abstract': e.get('abstract') or '',
+                    'description': e.get('description') or '',
+                    'track': e.get('track'),
+                    'room': e.get('room'),
+                    'schedule_start': e.get('date'),
+                    'schedule_duration': str(schedule_time_to_timedelta(e.get('duration'))),
+                    'is_public': True,
+                    'additional_data': filter_additional_data(e),
+                }
+                for e in schedule.events()
+            },
         }
 
 
 class ScheduleXML:
-    '''
+    """
     Schedule from XML document using etree, with inspirations from
       - https://github.com/pretalx/pretalx-downstream/blob/master/pretalx_downstream/tasks.py#L67
       - https://github.com/Zverik/schedule-convert/blob/master/schedule_convert/importers/frab_xml.py#L55
-    '''
+    """
+
     _schedule: ElementTree = None
     tz = None
 
@@ -79,7 +80,7 @@ class ScheduleXML:
         r = s.get(url)
 
         if r.ok is False:
-            raise Exception('Request failed, HTTP {0}.'.format(r.status_code))
+            raise Exception(f'Request failed, HTTP {r.status_code}.')
 
         schedule = ElementTree.fromstring(r.text)
         return ScheduleXML(tree=schedule)
diff --git a/src/core/search.py b/src/core/search.py
index 25062358d..baf83f2c1 100644
--- a/src/core/search.py
+++ b/src/core/search.py
@@ -10,8 +10,9 @@ from .models.tags import ConferenceTag
 from .models.users import PlatformUser
 
 
-def search(user: PlatformUser, conference: Conference, search_term: str, max_per_category: int = 10) \
-        -> Iterator[Union[ConferenceTag, ConferenceTrack, Assembly, Event]]:
+def search(
+    user: PlatformUser, conference: Conference, search_term: str, max_per_category: int = 10
+) -> Iterator[Union[ConferenceTag, ConferenceTrack, Assembly, Event]]:
     """
     Search assemblies, events, pages and tags for the search_term(s).
     Matches on the name are ranked higher than those only in the description.
@@ -47,8 +48,7 @@ def search(user: PlatformUser, conference: Conference, search_term: str, max_per
     if len(exclude_terms) > 0:
         for term in exclude_terms:
             tags = tags.exclude(slug__icontains=term)
-    for tag in tags[:max_per_category]:
-        yield tag
+    yield from tags[:max_per_category]
 
     # matching tracks are good, too
     tracks = conference.tracks.filter(is_public=True)
@@ -56,13 +56,11 @@ def search(user: PlatformUser, conference: Conference, search_term: str, max_per
         tracks = tracks.filter(name__icontains=term)
     for term in exclude_terms:
         tracks = tracks.exclude(name__icontains=term)
-    for track in tracks[:max_per_category]:
-        yield track
+    yield from tracks[:max_per_category]
 
     # search static pages
     pages = conference.pages.filter(public_revision__gte=0)
-    for page in pages.filter(search_vector=search_q):
-        yield page
+    yield from pages.filter(search_vector=search_q)
 
     # tracking which assemblies we've already seen so we don't return them twice
     assemblies_seen = []
@@ -83,11 +81,9 @@ def search(user: PlatformUser, conference: Conference, search_term: str, max_per
     # description match on Assembly
     if len(assemblies) < max_per_category:
         description_matches = Assembly.objects.conference_accessible(conference=conference).exclude(pk__in=assemblies_seen)
-        for assembly in apply_terms_description(description_matches)[:max_per_category - len(assemblies)]:
-            yield assembly
+        yield from apply_terms_description(description_matches)[: max_per_category - len(assemblies)]
 
     # description match on Event
     if len(events) < max_per_category:
         description_matches = Event.objects.conference_accessible(conference=conference).exclude(pk__in=events_seen)
-        for event in apply_terms_description(description_matches)[:max_per_category - len(events)]:
-            yield event
+        yield from apply_terms_description(description_matches)[: max_per_category - len(events)]
diff --git a/src/core/sso.py b/src/core/sso.py
index 36b8d1a3d..1a70b5a9e 100644
--- a/src/core/sso.py
+++ b/src/core/sso.py
@@ -1,20 +1,20 @@
-from functools import lru_cache
 import logging
+from functools import lru_cache
+
+import jwt
+from oauth2_provider.scopes import BaseScopes
 
 from django.conf import settings
 from django.utils.text import format_lazy
 from django.utils.translation import gettext_lazy as _
-import jwt
-from oauth2_provider.scopes import BaseScopes
 
 from .models.conference import Conference
 from .models.users import PlatformUser
 
-
 logger = logging.getLogger(__name__)
 
 
-class SingleSignOn(object):
+class SingleSignOn:
     def __init__(self):
         self._secret = settings.SSO_SECRET
         settings.SSO_SECRET = 'removed'
diff --git a/src/core/tests/__init__.py b/src/core/tests/__init__.py
index 851874ec7..da0a30278 100644
--- a/src/core/tests/__init__.py
+++ b/src/core/tests/__init__.py
@@ -4,14 +4,14 @@ from .bigbluebutton import *  # noqa: F401, F403
 from .conference import *  # noqa: F401, F403
 from .events import *  # noqa: F401, F403
 from .exportcache import *  # noqa: F401, F403
+from .markdown import *  # noqa: F401, F403
+from .schedules import *  # noqa: F401, F403
 from .search import *  # noqa: F401, F403
-from .users import *  # noqa: F401, F403
 from .tags import *  # noqa: F401, F403
 from .tickets import *  # noqa: F401, F403
-from .markdown import *  # noqa: F401, F403
-from .schedules import *  # noqa: F401, F403
+from .users import *  # noqa: F401, F403
 from .utils import *  # noqa: F401, F403
 from .validators import *  # noqa: F401, F403
 from .workadventure import *  # noqa: F401, F403
 
-__all__ = '*'
+__all__ = ('*',)  # noqa: F405
diff --git a/src/core/tests/assemblies.py b/src/core/tests/assemblies.py
index dfc6ebbac..faff811ac 100644
--- a/src/core/tests/assemblies.py
+++ b/src/core/tests/assemblies.py
@@ -14,30 +14,40 @@ class AssembliesTests(TestCase):
         self.user_mgmt = PlatformUser(username='manager')
         self.user_mgmt.save()
         self.user_mgmt.communication_channels.create(
-            channel=UserCommunicationChannel.Channel.MAIL, address='manager@unittest.local',
-            is_verified=True, use_for_notifications=False,
+            channel=UserCommunicationChannel.Channel.MAIL,
+            address='manager@unittest.local',
+            is_verified=True,
+            use_for_notifications=False,
         )
         self.user_mgmt.communication_channels.create(
-            channel=UserCommunicationChannel.Channel.MAIL, address='notifications@unittest.local',
-            is_verified=True, use_for_notifications=True,
+            channel=UserCommunicationChannel.Channel.MAIL,
+            address='notifications@unittest.local',
+            is_verified=True,
+            use_for_notifications=True,
         )
         self.user_mgmt2 = PlatformUser(username='manager2')
         self.user_mgmt2.save()
         self.user_mgmt2.communication_channels.create(
-            channel=UserCommunicationChannel.Channel.MAIL, address='notifications2@unittest.local',
-            is_verified=True, use_for_notifications=True,
+            channel=UserCommunicationChannel.Channel.MAIL,
+            address='notifications2@unittest.local',
+            is_verified=True,
+            use_for_notifications=True,
         )
         self.user_participant = PlatformUser(username='participant')
         self.user_participant.save()
         self.user_participant.communication_channels.create(
-            channel=UserCommunicationChannel.Channel.MAIL, address='participant@unittest.local',
-            is_verified=True, use_for_notifications=False,
+            channel=UserCommunicationChannel.Channel.MAIL,
+            address='participant@unittest.local',
+            is_verified=True,
+            use_for_notifications=False,
         )
         self.user_visitor = PlatformUser(username='visitor')
         self.user_visitor.save()
         self.user_visitor.communication_channels.create(
-            channel=UserCommunicationChannel.Channel.MAIL, address='visitor@unittest.local',
-            is_verified=True, use_for_notifications=False,
+            channel=UserCommunicationChannel.Channel.MAIL,
+            address='visitor@unittest.local',
+            is_verified=True,
+            use_for_notifications=False,
         )
         ConferenceMember(conference=self.conference, user=self.user_mgmt).save()
         ConferenceMember(conference=self.conference, user=self.user_mgmt2).save()
@@ -78,8 +88,7 @@ class AssembliesTests(TestCase):
     def test_mail_htmlwithlinks(self):
         self.assembly1.send_mail_to_managers(
             subject='Hello World',
-            message='Please check [the blog](https://events.ccc.de/) and do stuff.\n'
-                    'And don\'t forget to visit [events.ccc.de](https://events.ccc.de/)!',
+            message='Please check [the blog](https://events.ccc.de/) and do stuff.\n' "And don't forget to visit [events.ccc.de](https://events.ccc.de/)!",
         )
         self.assertEqual(len(mail.outbox), 2)
 
diff --git a/src/core/tests/badges.py b/src/core/tests/badges.py
index 1abb9572f..7eb3f9ce1 100644
--- a/src/core/tests/badges.py
+++ b/src/core/tests/badges.py
@@ -2,9 +2,9 @@ from django.core.exceptions import ValidationError
 from django.test import TestCase
 
 from ..models.assemblies import Assembly
+from ..models.badges import Badge, UserBadge
 from ..models.conference import Conference, ConferenceMember
 from ..models.users import PlatformUser
-from ..models.badges import Badge, UserBadge
 
 
 class BadgeTests(TestCase):
diff --git a/src/core/tests/bigbluebutton.py b/src/core/tests/bigbluebutton.py
index fd5e5dec2..4cb0f65c1 100644
--- a/src/core/tests/bigbluebutton.py
+++ b/src/core/tests/bigbluebutton.py
@@ -1,15 +1,15 @@
-from django.test import TestCase
 from unittest.mock import Mock, patch
 
+from django.test import TestCase
+
 from core.integrations import BigBlueButtonIntegration, IntegrationError
 from core.models import Assembly, Conference, PlatformUser, Room
 
-
 BigBlueButton = BigBlueButtonIntegration('http://localhost/', 'asdf', 'https://localhost/end_meeting', None)
 
 
 # from https://github.com/Grollicus/unittest_patterns/blob/master/unittest_patterns/__init__.py
-class Pattern(object):
+class Pattern:
     def __req__(self, lhs):
         return self.__eq__(lhs)
 
@@ -18,7 +18,7 @@ class Pattern(object):
 
 # from https://github.com/Grollicus/unittest_patterns/blob/master/unittest_patterns/__init__.py
 class Any(Pattern):
-    """ Equals everything """
+    """Equals everything"""
 
     def __eq__(self, rhs):
         return True
@@ -31,8 +31,7 @@ class BigBlueButtonTest(TestCase):
         self.assembly = Assembly(name='TestAssembly', slug='asmbly', conference=self.conf)
         self.assembly.save()
         self.room = Room(
-            conference=self.conf, assembly=self.assembly, name='Room 1',
-            room_type=Room.RoomType.BIGBLUEBUTTON, backend_status=Room.BackendStatus.NEW
+            conference=self.conf, assembly=self.assembly, name='Room 1', room_type=Room.RoomType.BIGBLUEBUTTON, backend_status=Room.BackendStatus.NEW
         )
         self.room.save()
 
@@ -47,23 +46,23 @@ class BigBlueButtonTest(TestCase):
 
             get_mock.return_value.status_code = 200
             get_mock.return_value.text = (
-                    '<response>'
-                    '<returncode>SUCCESS</returncode>'
-                    '<meetingID>Test</meetingID>'
-                    '<internalMeetingID>640ab2bae07bedc4c163f679a746f7ab7fb5d1fa-1531155809613</internalMeetingID>'
-                    '<parentMeetingID>bbb-none</parentMeetingID>'
-                    '<attendeePW>ap</attendeePW>'
-                    '<moderatorPW>mp</moderatorPW>'
-                    '<createTime>1531155809613</createTime>'
-                    '<voiceBridge>70757</voiceBridge>'
-                    '<dialNumber>613-555-1234</dialNumber>'
-                    '<createDate>Mon Jul 09 17:03:29 UTC 2018</createDate>'
-                    '<hasUserJoined>false</hasUserJoined>'
-                    '<duration>0</duration>'
-                    '<hasBeenForciblyEnded>false</hasBeenForciblyEnded>'
-                    '<messageKey>duplicateWarning</messageKey>'
-                    '<message>This conference was already in existence and may currently be in progress.</message>'
-                    '</response>'
+                '<response>'
+                '<returncode>SUCCESS</returncode>'
+                '<meetingID>Test</meetingID>'
+                '<internalMeetingID>640ab2bae07bedc4c163f679a746f7ab7fb5d1fa-1531155809613</internalMeetingID>'
+                '<parentMeetingID>bbb-none</parentMeetingID>'
+                '<attendeePW>ap</attendeePW>'
+                '<moderatorPW>mp</moderatorPW>'
+                '<createTime>1531155809613</createTime>'
+                '<voiceBridge>70757</voiceBridge>'
+                '<dialNumber>613-555-1234</dialNumber>'
+                '<createDate>Mon Jul 09 17:03:29 UTC 2018</createDate>'
+                '<hasUserJoined>false</hasUserJoined>'
+                '<duration>0</duration>'
+                '<hasBeenForciblyEnded>false</hasBeenForciblyEnded>'
+                '<messageKey>duplicateWarning</messageKey>'
+                '<message>This conference was already in existence and may currently be in progress.</message>'
+                '</response>'
             )
             self.room.backend_status = Room.BackendStatus.NEW
             self.room.save()
@@ -72,12 +71,15 @@ class BigBlueButtonTest(TestCase):
             get_mock.assert_called_once()
             self.assertEqual(self.room.backend_status, Room.BackendStatus.ACTIVE)
             self.assertEqual(self.room.backend_link, 'Test')
-            self.assertEqual(self.room.backend_data, {
-                'attendeePW': 'ap',
-                'moderatorPW': 'mp',
-                'createTime': '1531155809613',
-                'close_secret': Any(),
-            })
+            self.assertEqual(
+                self.room.backend_data,
+                {
+                    'attendeePW': 'ap',
+                    'moderatorPW': 'mp',
+                    'createTime': '1531155809613',
+                    'close_secret': Any(),
+                },
+            )
 
     def test_remove_room(self):
         with patch.object(BigBlueButton._session, 'get') as get_mock:
@@ -202,35 +204,39 @@ class BigBlueButtonTest(TestCase):
 
         with patch.object(BigBlueButton._session, 'get') as get_mock, self.assertLogs('core.integrations.bigbluebutton'):
             get_mock.side_effect = [
-                Mock(status_code=200, text=(
-                    '<response>'
-                    '<returncode>FAILED</returncode>'
-                    '<messageKey>notFound</messageKey>'
-                    '<message>We could not find a meeting with that meeting ID</message>'
-                    '</response>'
-                )),
-                Mock(status_code=200, text=(
-                    '<response>'
-                    '<returncode>SUCCESS</returncode>'
-                    '<meetingID>Test</meetingID>'
-                    '<internalMeetingID>640ab2bae07bedc4c163f679a746f7ab7fb5d1fa-1531155809613</internalMeetingID>'
-                    '<parentMeetingID>bbb-none</parentMeetingID>'
-                    '<attendeePW>ap</attendeePW>'
-                    '<moderatorPW>mp</moderatorPW>'
-                    '<createTime>1531155809613</createTime>'
-                    '<voiceBridge>70757</voiceBridge>'
-                    '<dialNumber>613-555-1234</dialNumber>'
-                    '<createDate>Mon Jul 09 17:03:29 UTC 2018</createDate>'
-                    '<hasUserJoined>false</hasUserJoined>'
-                    '<duration>0</duration>'
-                    '<hasBeenForciblyEnded>false</hasBeenForciblyEnded>'
-                    '<messageKey>duplicateWarning</messageKey>'
-                    '<message>This conference was already in existence and may currently be in progress.</message>'
-                    '</response>'
-                )),
-                Mock(status_code=302, headers={
-                    'Location': 'https://yourserver.com/client/BigBlueButton.html?sessionToken=ai1wqj8wb6s7rnk0'
-                }),
+                Mock(
+                    status_code=200,
+                    text=(
+                        '<response>'
+                        '<returncode>FAILED</returncode>'
+                        '<messageKey>notFound</messageKey>'
+                        '<message>We could not find a meeting with that meeting ID</message>'
+                        '</response>'
+                    ),
+                ),
+                Mock(
+                    status_code=200,
+                    text=(
+                        '<response>'
+                        '<returncode>SUCCESS</returncode>'
+                        '<meetingID>Test</meetingID>'
+                        '<internalMeetingID>640ab2bae07bedc4c163f679a746f7ab7fb5d1fa-1531155809613</internalMeetingID>'
+                        '<parentMeetingID>bbb-none</parentMeetingID>'
+                        '<attendeePW>ap</attendeePW>'
+                        '<moderatorPW>mp</moderatorPW>'
+                        '<createTime>1531155809613</createTime>'
+                        '<voiceBridge>70757</voiceBridge>'
+                        '<dialNumber>613-555-1234</dialNumber>'
+                        '<createDate>Mon Jul 09 17:03:29 UTC 2018</createDate>'
+                        '<hasUserJoined>false</hasUserJoined>'
+                        '<duration>0</duration>'
+                        '<hasBeenForciblyEnded>false</hasBeenForciblyEnded>'
+                        '<messageKey>duplicateWarning</messageKey>'
+                        '<message>This conference was already in existence and may currently be in progress.</message>'
+                        '</response>'
+                    ),
+                ),
+                Mock(status_code=302, headers={'Location': 'https://yourserver.com/client/BigBlueButton.html?sessionToken=ai1wqj8wb6s7rnk0'}),
             ]
             url = BigBlueButton.join_room(self.room, user)
             self.assertEqual(url, 'https://yourserver.com/client/BigBlueButton.html?sessionToken=ai1wqj8wb6s7rnk0')
diff --git a/src/core/tests/events.py b/src/core/tests/events.py
index d5efcc079..771032316 100644
--- a/src/core/tests/events.py
+++ b/src/core/tests/events.py
@@ -74,7 +74,7 @@ class EventTests(TestCase):
         with self.assertRaises(ValidationError) as cm:
             event = Event(**event_data)
             event.full_clean()
-        self.assertTrue("assembly" in cm.exception.error_dict)
+        self.assertTrue('assembly' in cm.exception.error_dict)
 
     def test_error_negative_duration(self):
         event_data = self.valid_event.copy()
@@ -82,7 +82,7 @@ class EventTests(TestCase):
         with self.assertRaises(ValidationError) as cm:
             event = Event(**event_data)
             event.full_clean()
-        self.assertTrue("schedule_duration" in cm.exception.error_dict)
+        self.assertTrue('schedule_duration' in cm.exception.error_dict)
 
     def test_error_zero_duration(self):
         event_data = self.valid_event.copy()
@@ -90,7 +90,7 @@ class EventTests(TestCase):
         with self.assertRaises(ValidationError) as cm:
             event = Event(**event_data)
             event.full_clean()
-        self.assertTrue("schedule_duration" in cm.exception.error_dict)
+        self.assertTrue('schedule_duration' in cm.exception.error_dict)
 
     def test_error_end_after_conference(self):
         event_data = self.valid_event.copy()
@@ -98,7 +98,7 @@ class EventTests(TestCase):
         with self.assertRaises(ValidationError) as cm:
             event = Event(**event_data)
             event.full_clean()
-        self.assertTrue("schedule_duration" in cm.exception.error_dict)
+        self.assertTrue('schedule_duration' in cm.exception.error_dict)
 
     def test_error_start_before_conference(self):
         event_data = self.valid_event.copy()
@@ -106,7 +106,7 @@ class EventTests(TestCase):
         with self.assertRaises(ValidationError) as cm:
             event = Event(**event_data)
             event.full_clean()
-        self.assertTrue("schedule_start" in cm.exception.error_dict)
+        self.assertTrue('schedule_start' in cm.exception.error_dict)
 
     def test_no_sos_assembly(self):
         event_data = self.valid_event.copy()
diff --git a/src/core/tests/exportcache.py b/src/core/tests/exportcache.py
index 9ea5bdb06..16f87c1ef 100644
--- a/src/core/tests/exportcache.py
+++ b/src/core/tests/exportcache.py
@@ -1,9 +1,9 @@
 from datetime import timedelta
 
+from django.test import TestCase, override_settings
 from django.utils import timezone
 from django.utils.datetime_safe import datetime
 from django.utils.timezone import now
-from django.test import TestCase, override_settings
 
 from ..models.assemblies import Assembly
 from ..models.conference import Conference, ConferenceExportCache
diff --git a/src/core/tests/markdown.py b/src/core/tests/markdown.py
index e313cb67d..90e781cbc 100644
--- a/src/core/tests/markdown.py
+++ b/src/core/tests/markdown.py
@@ -17,7 +17,6 @@ TEST_CONF_ID = uuid.uuid4()
 @override_settings(ALLOWED_HOSTS=['hub.test'], PLAINUI_CONFERENCE=TEST_CONF_ID)
 class MarkdownTest(TestCase):
     def test_url_class(self):
-
         conf = Conference(name='foo', id=TEST_CONF_ID)
         conf.save()
 
diff --git a/src/core/tests/schedules.py b/src/core/tests/schedules.py
index ec618de47..be0ffc95a 100644
--- a/src/core/tests/schedules.py
+++ b/src/core/tests/schedules.py
@@ -1,5 +1,5 @@
-from datetime import datetime, timedelta, timezone
 import json
+from datetime import datetime, timedelta, timezone
 from pathlib import Path
 
 from django.test import TestCase, override_settings
@@ -7,10 +7,9 @@ from django.test import TestCase, override_settings
 from ..models import Event
 from ..models.assemblies import Assembly
 from ..models.conference import Conference, ConferenceMember
-from ..models.users import PlatformUser
 from ..models.schedules import ScheduleSource, ScheduleSourceImport, ScheduleSourceMapping
-from ..schedules.base import BaseScheduleSupport, ScheduleTypeManager, \
-    filter_additional_data, schedule_time_to_timedelta
+from ..models.users import PlatformUser
+from ..schedules.base import BaseScheduleSupport, ScheduleTypeManager, filter_additional_data, schedule_time_to_timedelta
 from ..schedules.schedulejson import ScheduleJSONSupport
 from ..schedules.schedulexml import ScheduleXMLSupport
 
@@ -25,7 +24,7 @@ class FileBasedScheduleSupport(BaseScheduleSupport):
     def filename(self):
         url = str(self.schedule_source.import_url)
         assert url.startswith('file://')
-        fn = url[len('file://'):]
+        fn = url[len('file://') :]
 
         base_path = Path(__file__).parent
         fn = base_path.joinpath(fn).resolve()
@@ -118,10 +117,10 @@ class ScheduleTests(TestCase):
         self.assertEqual(2, src.mappings.count())
 
         r1_m = src.mappings.get(mapping_type=ScheduleSourceMapping.MappingType.ROOM, source_id='eins')
-        self.assertEqual(r1_m.local_id, r1.id, 'room\'s mapping\'s local_id doesn\'t match room id')
+        self.assertEqual(r1_m.local_id, r1.id, "room's mapping's local_id doesn't match room id")
 
         e1_m = src.mappings.get(mapping_type=ScheduleSourceMapping.MappingType.EVENT, source_id='cej9dwoi')
-        self.assertEqual(e1_m.local_id, e1.id, 'event\'s mapping\'s local_id doesn\'t match event id')
+        self.assertEqual(e1_m.local_id, e1.id, "event's mapping's local_id doesn't match event id")
 
         # check other attributes
         self.assertEqual(e1.room, r1)
@@ -259,7 +258,7 @@ class ScheduleTests(TestCase):
             assembly=self.assembly,
             conference=self.conference,
             import_type=ScheduleXMLSupport.identifier,
-            import_url=f"file://{Path(__file__).parent.joinpath('import_data', 'schedule-2021.xml')}"
+            import_url=f"file://{Path(__file__).parent.joinpath('import_data', 'schedule-2021.xml')}",
         )
         self.assertFalse(src.has_running_import)
 
@@ -305,7 +304,7 @@ class ScheduleTests(TestCase):
             assembly=self.assembly,
             conference=self.conference,
             import_type=ScheduleJSONSupport.identifier,
-            import_url=f"file://{Path(__file__).parent.joinpath('import_data', 'schedule-2020.json')}"
+            import_url=f"file://{Path(__file__).parent.joinpath('import_data', 'schedule-2020.json')}",
         )
         self.check_json(src, rooms=1, events=1, room_name='Yellow Room')
 
@@ -315,7 +314,7 @@ class ScheduleTests(TestCase):
             assembly=self.assembly,
             conference=self.conference,
             import_type=ScheduleJSONSupport.identifier,
-            import_url=f"file://{Path(__file__).parent.joinpath('import_data', 'schedule-2021.json')}"
+            import_url=f"file://{Path(__file__).parent.joinpath('import_data', 'schedule-2021.json')}",
         )
         self.check_json(src, rooms=2, events=1, room_name='Gray Room')
 
diff --git a/src/core/tests/search.py b/src/core/tests/search.py
index ec7c0152a..1d9f4dadb 100644
--- a/src/core/tests/search.py
+++ b/src/core/tests/search.py
@@ -22,17 +22,17 @@ class SearchTests(TestCase):
         self.cocktail_page.save()
         self.cocktail_page_rev1 = self.cocktail_page.revisions.create(
             title=self.cocktail_page.title,
-            body='''Der **Pangalaktische Donnergurgler** ist der angeblich stärkste Drink im Universum.
+            body="""Der **Pangalaktische Donnergurgler** ist der angeblich stärkste Drink im Universum.
 Die Wirkung eines Pangalaktischen Donnergurglers ist in etwa so, „als ob man mit einem Goldbarren,
-der in Zitronenscheiben gehüllt ist, das Gehirn aus dem Kopf gedroschen bekommt“.''',
+der in Zitronenscheiben gehüllt ist, das Gehirn aus dem Kopf gedroschen bekommt“.""",
             author=page_user,
         )
         self.cocktail_page_rev1.save()
         self.cocktail_page_rev2 = self.cocktail_page.revisions.create(
             title=self.cocktail_page.title,
-            body='''Der **Pangalaktische Donnergurgler** ist der angeblich stärkste Drink der Galaxis.
+            body="""Der **Pangalaktische Donnergurgler** ist der angeblich stärkste Drink der Galaxis.
 Die Wirkung eines Pangalaktischen Donnergurglers ist in etwa so, „als ob man mit einem Goldbarren,
-der in Zitronenscheiben gehüllt ist, das Gehirn aus dem Kopf gedroschen bekommt“.''',
+der in Zitronenscheiben gehüllt ist, das Gehirn aus dem Kopf gedroschen bekommt“.""",
             author=page_user,
         )
         self.cocktail_page_rev2.save()
diff --git a/src/core/tests/tags.py b/src/core/tests/tags.py
index a11aaf427..14d4d7d3d 100644
--- a/src/core/tests/tags.py
+++ b/src/core/tests/tags.py
@@ -119,13 +119,13 @@ class TagItemTests(TestCase):
         if value is None:
             return
         if value_type == ConferenceTag.Type.SIMPLE:
-            self.fail("Simple tag item did not return None as value")
+            self.fail('Simple tag item did not return None as value')
         if value_type == ConferenceTag.Type.STRING:
-            self.assertTrue(isinstance(value, str), "String tag item did not return None or a string as value")
+            self.assertTrue(isinstance(value, str), 'String tag item did not return None or a string as value')
         if value_type == ConferenceTag.Type.INT:
-            self.assertTrue(isinstance(value, int), "Int tag item did not return None or an int as value")
+            self.assertTrue(isinstance(value, int), 'Int tag item did not return None or an int as value')
         if value_type == ConferenceTag.Type.BOOL:
-            self.assertTrue(isinstance(value, bool), "Bool tag item did not return None or a bool as value")
+            self.assertTrue(isinstance(value, bool), 'Bool tag item did not return None or a bool as value')
 
     def test_get_value_check_type_uninitialized(self):
         for tag_item in self.tag_items.values():
@@ -135,7 +135,7 @@ class TagItemTests(TestCase):
         for tag_item in self.tag_items.values():
             value_type = tag_item.tag.value_type
             if value_type == ConferenceTag.Type.STRING:
-                tag_item.value = "Test"
+                tag_item.value = 'Test'
             if value_type == ConferenceTag.Type.INT:
                 tag_item.value = 42
             if value_type == ConferenceTag.Type.BOOL:
@@ -147,7 +147,7 @@ class TagItemTests(TestCase):
             value_type = tag_item.tag.value_type
             value = None
             if value_type == ConferenceTag.Type.STRING:
-                value = "Test"
+                value = 'Test'
             if value_type == ConferenceTag.Type.INT:
                 value = 42
             if value_type == ConferenceTag.Type.BOOL:
@@ -156,32 +156,32 @@ class TagItemTests(TestCase):
             self.assertEqual(tag_item.value, value)
 
     def test_value_check_boolean_coerce(self):
-        tag = self.tag_items["guter Geschmack"]
+        tag = self.tag_items['guter Geschmack']
         for v in [False, 0, 'n', 'no', 'nein', 'false', 'off']:
             tag.value = True
             self.assertTrue(tag.value)
             tag.value = v
-            self.assertFalse(tag.value, "Value {} did not yield false".format(v))
+            self.assertFalse(tag.value, f'Value {v} did not yield false')
 
         for v in [True, -2, -1, 1, 2, 3, 'y', 'j', 'yes', 'ja', 'true', 'on']:
             tag.value = False
             self.assertFalse(tag.value)
             tag.value = v
-            self.assertTrue(tag.value, "Value {} did not yield true".format(v))
+            self.assertTrue(tag.value, f'Value {v} did not yield true')
 
     def test_value_set_check_simple_with_wrong_argument(self):
-        tag = self.tag_items["foo"]
+        tag = self.tag_items['foo']
         with self.assertRaises(ValueError):
             tag.value = self.user
         with self.assertRaises(ValueError):
-            tag.value = "Test"
+            tag.value = 'Test'
         with self.assertRaises(ValueError):
             tag.value = 42
         with self.assertRaises(ValueError):
             tag.value = True
 
     def test_value_set_check_string_with_wrong_argument(self):
-        tag = self.tag_items["Autor des besten Gedichts"]
+        tag = self.tag_items['Autor des besten Gedichts']
         with self.assertRaises(ValueError):
             tag.value = None
         with self.assertRaises(ValueError):
@@ -192,26 +192,26 @@ class TagItemTests(TestCase):
             tag.value = True
 
     def test_value_set_check_int_with_wrong_argument(self):
-        tag = self.tag_items["michelinstars"]
+        tag = self.tag_items['michelinstars']
         with self.assertRaises(ValueError):
             tag.value = None
         with self.assertRaises(ValueError):
             tag.value = self.user
         with self.assertRaises(ValueError):
-            tag.value = "Test"
+            tag.value = 'Test'
         # Python coerces bool to int by itself
 
     def test_value_set_check_bool_with_wrong_argument(self):
-        tag = self.tag_items["guter Geschmack"]
+        tag = self.tag_items['guter Geschmack']
         with self.assertRaises(ValueError):
             tag.value = None
         with self.assertRaises(ValueError):
             tag.value = self.user
         with self.assertRaises(ValueError):
-            tag.value = "Test"
+            tag.value = 'Test'
         with self.assertRaises(ValueError):
-            tag.value = "foo"
+            tag.value = 'foo'
         with self.assertRaises(ValueError):
-            tag.value = ""
+            tag.value = ''
         with self.assertRaises(ValueError):
-            tag.value = "bar"
+            tag.value = 'bar'
diff --git a/src/core/tests/tickets.py b/src/core/tests/tickets.py
index c108a39f6..1a7dfd5b1 100644
--- a/src/core/tests/tickets.py
+++ b/src/core/tests/tickets.py
@@ -1,12 +1,13 @@
-from datetime import datetime, timedelta, UTC
-from django.test import TestCase, override_settings
+from datetime import UTC, datetime, timedelta
+
 import jwt
 
+from django.test import TestCase, override_settings
+
 from ..models.conference import Conference
 from ..models.ticket import ConferenceMemberTicket, TicketValidationError
 from ..models.users import PlatformUser
 
-
 _GOOD_SECRET = 'F00Preti%'
 _BAD_SECRET = 'DämlicherFlanders'
 
@@ -37,7 +38,7 @@ class PretixTests(TestCase):
         self.assertEqual(ticket.user_id, self.foo_user.id)
 
         # trying the same token again must fail
-        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', "WARNING"):
+        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', 'WARNING'):
             ConferenceMemberTicket.redeem_pretix_ticket(conference=self.conference, user=self.foo_user, token=token)
 
     def test_two_per_user(self):
@@ -46,7 +47,7 @@ class PretixTests(TestCase):
 
         # validating a second token must fail (even if the token itself is valid)
         token2 = jwt.encode({**self.pretix_jwt_basepayload, 'uid': 'jklö5678'}, _GOOD_SECRET)
-        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', "WARNING"):
+        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', 'WARNING'):
             ConferenceMemberTicket.redeem_pretix_ticket(conference=self.conference, user=self.foo_user, token=token2)
 
         # verify that the second token works on another user
@@ -57,16 +58,16 @@ class PretixTests(TestCase):
     def test_invalid_signature(self):
         token = jwt.encode({**self.pretix_jwt_basepayload, 'uid': 'asdf1234'}, _BAD_SECRET)
 
-        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', "WARNING"):
+        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', 'WARNING'):
             ConferenceMemberTicket.redeem_pretix_ticket(conference=self.conference, user=self.foo_user, token=token)
 
     def test_invalid_fields(self):
         # misspell 'uid'
         token = jwt.encode({**self.pretix_jwt_basepayload, 'userid': 'asdf1234'}, _GOOD_SECRET)
-        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', "WARNING"):
+        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', 'WARNING'):
             ConferenceMemberTicket.redeem_pretix_ticket(conference=self.conference, user=self.foo_user, token=token)
 
         # unmatching 'aud' (audience, conference slug)
         token = jwt.encode({**self.pretix_jwt_basepayload, 'aud': 'FNORD', 'userid': 'asdf1234'}, _GOOD_SECRET)
-        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', "WARNING"):
+        with self.assertRaises(TicketValidationError), self.assertLogs('core.models.ticket', 'WARNING'):
             ConferenceMemberTicket.redeem_pretix_ticket(conference=self.conference, user=self.foo_user, token=token)
diff --git a/src/core/tests/users.py b/src/core/tests/users.py
index 8956403c9..7d6eb7650 100644
--- a/src/core/tests/users.py
+++ b/src/core/tests/users.py
@@ -14,7 +14,7 @@ class UserCommunicationChannelStaticTests(TestCase):
                 self.fail(f'{g} should be accepted as {what} but raised an exception: {err}')
 
         for b in bad:
-            with self.assertRaises(ValidationError, msg=f'{b} shouldn\'t be accepted as {what}'):
+            with self.assertRaises(ValidationError, msg=f"{b} shouldn't be accepted as {what}"):
                 func(b)
 
     def test_mail_validation(self):
@@ -37,7 +37,7 @@ class UserCommunicationChannelStaticTests(TestCase):
             '#matrix:example.org',
             '!4rguxf:matrix.org',
             'https://matrix.to/#/%23somewhere%3Aexample.org',
-            'https://matrix.to/#/!somewhere%3Aexample.org'
+            'https://matrix.to/#/!somewhere%3Aexample.org',
         ]
         bad_uris = [
             '',
diff --git a/src/core/tests/utils.py b/src/core/tests/utils.py
index 89e7b1719..1a7ff7a53 100644
--- a/src/core/tests/utils.py
+++ b/src/core/tests/utils.py
@@ -2,7 +2,7 @@ from datetime import timedelta
 
 from django.test import TestCase
 
-from ..utils import mask_url, str2timedelta, scheme_and_netloc_from_url, GitRepo
+from ..utils import GitRepo, mask_url, scheme_and_netloc_from_url, str2timedelta
 
 
 class UtilsTests(TestCase):
@@ -59,10 +59,10 @@ class GitRepoOfflineTests(TestCase):
         self.assertEqual(repo.repo_hash, 'develop')
 
     def test_url_params(self):
-        repo_test_baseurl = "https://git.cccv.de/hub/wiki-import-test.git"
+        repo_test_baseurl = 'https://git.cccv.de/hub/wiki-import-test.git'
 
-        repo = GitRepo(f"https://{repo_test_baseurl}")
-        self.assertEqual(f"https://{repo_test_baseurl}", repo.repo_url)
+        repo = GitRepo(f'https://{repo_test_baseurl}')
+        self.assertEqual(f'https://{repo_test_baseurl}', repo.repo_url)
         self.assertIsNone(repo.repo_branch)
         self.assertIsNone(repo.repo_path)
         self.assertIsNone(repo.repo_hash)
diff --git a/src/core/tests/validators.py b/src/core/tests/validators.py
index 567324956..cd3f4c795 100644
--- a/src/core/tests/validators.py
+++ b/src/core/tests/validators.py
@@ -24,9 +24,7 @@ class ValidateFileSizeTests(TestCase):
 class ValidateImageSizeTests(TestCase):
     def test_does_not_raise_when_image_meets_requirements(self):
         mock_image = Mock(width=10, height=10)
-        validator = ImageDimensionValidator(
-            min_size=(10, 10), max_size=(10, 10), square=True
-        )
+        validator = ImageDimensionValidator(min_size=(10, 10), max_size=(10, 10), square=True)
 
         self.assertEqual(mock_image, validator(mock_image))
 
diff --git a/src/core/tests/workadventure.py b/src/core/tests/workadventure.py
index 7b0940c5b..2e91edea3 100644
--- a/src/core/tests/workadventure.py
+++ b/src/core/tests/workadventure.py
@@ -58,15 +58,19 @@ class WorkadventureSessionTests(TestCase):
         self.user.refresh_from_db()
         self.assertFalse(self.user.receive_audio)
 
-        session.update_userdata({'wa_userconfig': {
-            'silentMode': False,
-            'disableCamera': False,
-            'noAnimations': True,
-            'blockExternalContent': True,
-            'blockAmbientSounds': True,
-            'ignoreFollowRequests': True,
-            'forceWebsiteTrigger': True,
-        }})
+        session.update_userdata(
+            {
+                'wa_userconfig': {
+                    'silentMode': False,
+                    'disableCamera': False,
+                    'noAnimations': True,
+                    'blockExternalContent': True,
+                    'blockAmbientSounds': True,
+                    'ignoreFollowRequests': True,
+                    'forceWebsiteTrigger': True,
+                }
+            }
+        )
         self.user.refresh_from_db()
         self.assertTrue(self.user.receive_audio)
         self.assertTrue(self.user.receive_video)
diff --git a/src/core/tokens.py b/src/core/tokens.py
index 6ee5a5155..635f82d2d 100644
--- a/src/core/tokens.py
+++ b/src/core/tokens.py
@@ -1,17 +1,16 @@
 from django.contrib.auth.tokens import PasswordResetTokenGenerator
 
-
 # adapted from https://simpleisbetterthancomplex.com/tutorial/2017/02/18/how-to-create-user-sign-up-view.html
 
 
 class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
     def _make_hash_value(self, user, timestamp):
-        return (str(user.pk) + str(timestamp) + str(user.profile.mail_verified))
+        return str(user.pk) + str(timestamp) + str(user.profile.mail_verified)
 
 
 class ChannelActivationTokenGenerator(PasswordResetTokenGenerator):
     def _make_hash_value(self, channel, timestamp):
-        return (str(channel.user.pk) + str(timestamp) + str(channel.channel) + str(channel.address))
+        return str(channel.user.pk) + str(timestamp) + str(channel.channel) + str(channel.address)
 
 
 account_activation_token = AccountActivationTokenGenerator()
diff --git a/src/core/translation.py b/src/core/translation.py
index 8aef618ba..85d7992c1 100644
--- a/src/core/translation.py
+++ b/src/core/translation.py
@@ -20,7 +20,10 @@ from core.models import (
 
 @register(Assembly)
 class AssemblyTranslationOptions(TranslationOptions):
-    fields = ('description', 'description_html', )
+    fields = (
+        'description',
+        'description_html',
+    )
 
 
 @register(Badge)
@@ -58,39 +61,58 @@ admin.site.register(BadgeCategory, TranslatedBadgeCategoryAdmin)
 
 @register(BulletinBoardEntry)
 class BulletinBoardEntryTranslationOptions(TranslationOptions):
-    fields = ('text', 'text_html', )
+    fields = (
+        'text',
+        'text_html',
+    )
 
 
 @register(ConferenceNavigationItem)
 class ConferenceNavigationItemTranslationOptions(TranslationOptions):
-    fields = ('label', 'title', )
+    fields = (
+        'label',
+        'title',
+    )
 
 
 @register(Event)
 class EventTranslationOptions(TranslationOptions):
-    fields = ('description', 'description_html', )
+    fields = (
+        'description',
+        'description_html',
+    )
 
 
 @register(ConferenceMember)
 class ConferenceMemberTranslationOptions(TranslationOptions):
-    fields = ('description', 'description_html', )
+    fields = (
+        'description',
+        'description_html',
+    )
 
 
 @register(Room)
 class RoomTranslationOptions(TranslationOptions):
-    fields = ('description', 'description_html', )
+    fields = (
+        'description',
+        'description_html',
+    )
 
 
 @register(MapFloor)
 class MapFloorTranslationOptions(TranslationOptions):
-    fields = ('name', )
+    fields = ('name',)
 
 
 @register(MapPOI)
 class MapPOITranslationOptions(TranslationOptions):
-    fields = ('name', 'description', 'description_html', )
+    fields = (
+        'name',
+        'description',
+        'description_html',
+    )
 
 
 @register(MetaNavItem)
 class MetaNavItemTranslationOptions(TranslationOptions):
-    fields = ('title', )
+    fields = ('title',)
diff --git a/src/core/utils.py b/src/core/utils.py
index ab233151e..82ef8ba43 100644
--- a/src/core/utils.py
+++ b/src/core/utils.py
@@ -50,7 +50,7 @@ def render_markdown_as_text(markup: str):
     """
     text_only = strip_tags(markup)
     link_urls = {}
-    for (link_text, link_url) in RE_MARKDOWN_LINKS.findall(markup):
+    for link_text, link_url in RE_MARKDOWN_LINKS.findall(markup):
         if link_url not in link_urls:
             link_urls[link_url] = len(link_urls) + 1
         idx = link_urls[link_url]
@@ -73,7 +73,7 @@ def generate_token(length: int = 50, char_set: str = ''.join([ascii_letters, dig
 
 def int_to_custom_string(number: int, alphabet: str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
     if alphabet is None or len(alphabet) < 2:
-        raise ValueError("Alphabet must consist of at least two characters.")
+        raise ValueError('Alphabet must consist of at least two characters.')
 
     base = len(alphabet)
     result = []
@@ -156,7 +156,7 @@ class GitCheckoutError(Exception):
     pass
 
 
-class GitRepo(object):
+class GitRepo:
     working_copy_path: Path
 
     def __init__(self, repo_url):
@@ -178,27 +178,25 @@ class GitRepo(object):
         # note down URL without custom optional parts
         self.repo_url = parsed._replace(query=None, fragment=None).geturl()
         if '$' in self.repo_url:
-            raise ValueError('Env variables not allowed in GitRepo\'s repo_url.')
+            raise ValueError("Env variables not allowed in GitRepo's repo_url.")
 
     def __enter__(self):
         if self.working_copy_path is not None:
-            raise NotImplementedError("Accessing GitRepo twice is not supported!")
+            raise NotImplementedError('Accessing GitRepo twice is not supported!')
         self.working_copy_path = Path(tempfile.mkdtemp())
 
         # assemble git-clone call
         cmd = ['git', 'clone', '-c', 'core.symlinks=false']
         if not self.repo_hash:
             cmd.extend(
-                ['--depth', '1',]
+                [
+                    '--depth',
+                    '1',
+                ]
             )
         if self.repo_branch:
-            cmd.extend(
-                ['--branch', self.repo_branch]
-            )
-        cmd.extend([
-            self.repo_url,
-            str(self.working_copy_path)
-        ])
+            cmd.extend(['--branch', self.repo_branch])
+        cmd.extend([self.repo_url, str(self.working_copy_path)])
 
         # this fails with an exception on non-zero exitcode
         logger.info(' '.join(cmd))
@@ -223,11 +221,11 @@ class GitRepo(object):
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         if not shutil.rmtree.avoids_symlink_attacks:
-            raise NotImplementedError("Your OS does not support avoiding symlink attacks.")
+            raise NotImplementedError('Your OS does not support avoiding symlink attacks.')
         try:
             shutil.rmtree(self.working_copy_path)
         except Exception:
-            logger.exception("Failed to remove temporary files.")
+            logger.exception('Failed to remove temporary files.')
 
     @cached_property
     def base_path(self) -> Path:
@@ -240,11 +238,11 @@ class GitRepo(object):
             base_path = (base_path / self.repo_path).absolute()
             # sanity check that we are not being tricked into some path traversal
             if not base_path.is_relative_to(self.working_copy_path):
-                raise ValueError(f"Provided path would result in a directory traversal: {self.repo_path}")
+                raise ValueError(f'Provided path would result in a directory traversal: {self.repo_path}')
 
         return base_path
 
-    def get_documents(self, glob: str = "*.md", encoding: str = 'utf-8') -> Dict[str, str]:
+    def get_documents(self, glob: str = '*.md', encoding: str = 'utf-8') -> Dict[str, str]:
         """
         Read all documents matching the configured glob as text.
         :param glob: a filter to use when searching the documents to read, defaults to Markdown file extension
@@ -260,7 +258,7 @@ class GitRepo(object):
             # read and store files matching the glob
             try:
                 documents[file.name] = file.read_text(encoding=encoding)
-            except (IOError, UnicodeDecodeError):
+            except (OSError, UnicodeDecodeError):
                 documents[file.name] = None
                 logger.exception('Failed to read document "%s".', file.name)
 
@@ -278,7 +276,7 @@ class GitRepo(object):
 
         # sanity check that nobody is trying to do any shenanigans
         if not file_path.is_relative_to(self.working_copy_path):
-            raise ValueError(f"Provided file path would result in a directory traversal: {filename}")
+            raise ValueError(f'Provided file path would result in a directory traversal: {filename}')
 
         # read the file (or raise if the file does not exist)
         return file_path.read_bytes()
diff --git a/src/core/validators.py b/src/core/validators.py
index 3fc396b20..d7750f148 100644
--- a/src/core/validators.py
+++ b/src/core/validators.py
@@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
 
 
 @deconstructible
-class FileSizeValidator(object):
+class FileSizeValidator:
     """
     A validator class that will check a file size against the given limit
 
@@ -28,9 +28,9 @@ class FileSizeValidator(object):
     def __call__(self, file):
         if file.size > self.max_size * 1024 * 1024:
             raise ValidationError(
-                _("Validation__error_file_size_MB_exceeded %(max_size)d"),
-                code="max_file_size",
-                params={"max_size": self.max_size},
+                _('Validation__error_file_size_MB_exceeded %(max_size)d'),
+                code='max_file_size',
+                params={'max_size': self.max_size},
             )
         else:
             return file
@@ -40,7 +40,7 @@ class FileSizeValidator(object):
 
 
 @deconstructible
-class ImageDimensionValidator(object):
+class ImageDimensionValidator:
     """
     A validator class that will check a image dimension against the given limits
 
@@ -52,13 +52,7 @@ class ImageDimensionValidator(object):
     max_size: tuple[int | None, int | None]
     square: bool
 
-    def __init__(
-        self,
-        *,
-        min_size: tuple[int | None, int | None] = (None, None),
-        max_size: tuple[int | None, int | None] = (None, None),
-        square: bool = False
-    ):
+    def __init__(self, *, min_size: tuple[int | None, int | None] = (None, None), max_size: tuple[int | None, int | None] = (None, None), square: bool = False):
         """Return a model field validator for ImageFields that raises an ValidationError
             if the image has incorrect dimensions
 
@@ -76,55 +70,51 @@ class ImageDimensionValidator(object):
         if self.min_size[0] is not None and image.width < self.min_size[0]:
             errors.append(
                 ValidationError(
-                    _("Validation__error_image_width_low %(size)d"),
-                    code="min_width",
-                    params={"size": self.min_size[0]},
+                    _('Validation__error_image_width_low %(size)d'),
+                    code='min_width',
+                    params={'size': self.min_size[0]},
                 )
             )
         if self.min_size[1] is not None and image.height < self.min_size[1]:
             errors.append(
                 ValidationError(
-                    _("Validation__error_image_height_low %(size)d"),
-                    code="min_height",
-                    params={"size": self.min_size[1]},
+                    _('Validation__error_image_height_low %(size)d'),
+                    code='min_height',
+                    params={'size': self.min_size[1]},
                 )
             )
         if self.max_size[0] is not None and image.width > self.max_size[0]:
             errors.append(
                 ValidationError(
-                    _("Validation__error_image_width_high %(size)d"),
-                    code="max_width",
-                    params={"size": self.max_size[0]},
+                    _('Validation__error_image_width_high %(size)d'),
+                    code='max_width',
+                    params={'size': self.max_size[0]},
                 )
             )
         if self.max_size[1] is not None and image.height > self.max_size[1]:
             errors.append(
                 ValidationError(
-                    _("Validation__error_image_height_high %(size)d"),
-                    code="max_height",
-                    params={"size": self.max_size[1]},
+                    _('Validation__error_image_height_high %(size)d'),
+                    code='max_height',
+                    params={'size': self.max_size[1]},
                 )
             )
         if errors:
             errors.append(
                 ValidationError(
-                    _(
-                        "Validation__image_dimensions "
-                        "%(min_width)s %(min_height)s "
-                        "%(max_width)s %(max_height)s"
-                    ),
-                    code="image_dimensions",
+                    _('Validation__image_dimensions ' '%(min_width)s %(min_height)s ' '%(max_width)s %(max_height)s'),
+                    code='image_dimensions',
                     params={
-                        "min_width": self.min_size[0],
-                        "min_height": self.min_size[1],
-                        "max_width": self.max_size[0],
-                        "max_height": self.max_size[1],
+                        'min_width': self.min_size[0],
+                        'min_height': self.min_size[1],
+                        'max_width': self.max_size[0],
+                        'max_height': self.max_size[1],
                     },
                 )
             )
 
         if self.square and image.width != image.height:
-            errors.append(ValidationError(_("Validation__error_image_square")))
+            errors.append(ValidationError(_('Validation__error_image_square')))
         if errors:
             raise ValidationError(errors)
         else:
diff --git a/src/core/views/auth.py b/src/core/views/auth.py
index 8d9e469e5..427f923ad 100644
--- a/src/core/views/auth.py
+++ b/src/core/views/auth.py
@@ -1,8 +1,9 @@
-
 import logging
 from smtplib import SMTPException
 from typing import Any
 
+from django_ratelimit.decorators import ratelimit
+
 from django.contrib import messages
 from django.contrib.auth.views import LoginView, PasswordResetConfirmView, PasswordResetView
 from django.core.exceptions import NON_FIELD_ERRORS, ImproperlyConfigured, ValidationError
@@ -16,7 +17,6 @@ from django.utils.translation import gettext_lazy as _
 from django.views.decorators.cache import never_cache
 from django.views.decorators.debug import sensitive_post_parameters
 from django.views.generic import FormView, View
-from django_ratelimit.decorators import ratelimit
 
 from core.forms import LoginForm, PasswordResetForm, RegistrationForm
 from core.models import PlatformUser, UserCommunicationChannel
diff --git a/src/hub/settings/base.py b/src/hub/settings/base.py
index a9053c10b..683623572 100644
--- a/src/hub/settings/base.py
+++ b/src/hub/settings/base.py
@@ -10,15 +10,15 @@ For the full list of settings and their values, see
 https://docs.djangoproject.com/en/3.1/ref/settings/
 """
 
-from email.utils import getaddresses
 import json
 import os
-from pathlib import Path
 import re
+from email.utils import getaddresses
+from pathlib import Path
 
-from django.utils.translation import gettext_lazy as _
 import environ
 
+from django.utils.translation import gettext_lazy as _
 
 SCRIPT_NAME = os.getenv('SCRIPT_NAME', '')
 
@@ -38,28 +38,22 @@ env = environ.FileAwareEnv(
     CLIENT_IP_HEADER=(str, None),
     DISABLE_RATELIMIT=(bool, False),
     BADGE_RATE_LIMIT=(str, '180/h'),
-
     SENTRY_ENDPOINT=(str, None),
     SENTRY_TRACES_SAMPLE_RATE=(float, 0.05),  # create a trace for this percentage of all requests
     SENTRY_PROFILES_SAMPLE_RATE=(float, 0.50),  # do a profiling for this percentage of _traced_ requests
-
     SSO_SECRET=(str, None),
     SSO_SECRET_GENERATE=(bool, False),
     PRETIX_ISSUER=(str, 'tickets.events.ccc.de'),
     PRETIX_SECRET=(str, None),
-
     METRICS_SERVER_IPS=(list, ['*']),
     TIMEZONE=(str, 'Europe/Berlin'),
-
     BIGBLUEBUTTON=(bool, False),
     BIGBLUEBUTTON_API_URL=(str, None),
     BIGBLUEBUTTON_API_TOKEN=(str, None),
     BIGBLUEBUTTON_END_MEETING_CALLBACK=(str, None),
     BIGBLUEBUTTON_INITIAL_PRESENTATION_URL=(str, None),
-
     HANGAR=(bool, False),
     HANGAR_URL=(str, None),
-
     WORKADVENTURE=(bool, False),
     WORKADVENTURE_URL_SCHEME_GENERAL=(str, 'http://visit.at.localhost:8080/'),
     WORKADVENTURE_URL_SCHEME_ASSEMBLY=(
@@ -84,7 +78,6 @@ env = environ.FileAwareEnv(
     WORKADVENTURE_MAPSERVICE_LOGLEVEL_URL=(str, None),
     WORKADVENTURE_MAPSERVICE_LOGLEVEL_AUTHORIZATION=(str, None),
     WORKADVENTURE_MAPSERVICE_LOGLEVEL_NOVERIFY=(bool, True),
-
     ADMIN_BASE_URL=(str, '/c3admin'),
     API_BASE_URL=(str, '/api'),
     BACKOFFICE_BASE_URL=(str, '/backoffice'),
@@ -94,22 +87,17 @@ env = environ.FileAwareEnv(
     PLAINUI_DEREFERER_ALLOWLIST=(list, []),
     PLAINUI_DEREFERER_COUNTLIST=(list, []),
     PLAINUI_THEME_SUPPORT=(bool, False),
-
     SHIBBOLEET_WA_ROOM_ID=('str', None),
-
     ASSEMBLY_BANNER_FILE_SIZE_LIMIT=(int, 2),
     ASSEMBLY_BANNER_WIDTH_MINIMUM=(int, 100),
     ASSEMBLY_BANNER_HEIGHT_MINIMUM=(int, 100),
     ASSEMBLY_BANNER_WIDTH_MAXIMUM=(int, 2048),
     ASSEMBLY_BANNER_HEIGHT_MAXIMUM=(int, 1024),
-
     BADGE_IMAGE_WIDTH_MINIMUM=(int, 256),
     BADGE_IMAGE_HEIGHT_MINIMUM=(int, 256),
     BADGE_IMAGE_WIDTH_MAXIMUM=(int, 512),
     BADGE_IMAGE_HEIGHT_MAXIMUM=(int, 512),
-
     API_USERS=(list, []),
-
     DISABLE_REQUEST_LOGGING=(bool, False),
 )
 
@@ -147,9 +135,7 @@ BASE_DIR = Path(__file__).resolve().parents[2]
 
 if env('DJANGO_ENV_FILE'):
     DJANGO_ENV_FILE_PATH = Path(env('DJANGO_ENV_FILE'))
-    assert (
-        DJANGO_ENV_FILE_PATH.exists()
-    ), f'DJANGO_ENV_FILE path {DJANGO_ENV_FILE_PATH} does not exist!'
+    assert DJANGO_ENV_FILE_PATH.exists(), f'DJANGO_ENV_FILE path {DJANGO_ENV_FILE_PATH} does not exist!'
     environ.Env.read_env(DJANGO_ENV_FILE_PATH)
 
 # prepare SECRET_KEY but must be overridden, i.e. in default.py
@@ -200,7 +186,8 @@ MIDDLEWARE = [
     'django.contrib.auth.middleware.AuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'core.middleware.TimezoneMiddleware',
-    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',  # TODO drüber nachdenken ob wir die brauchen (ist default an in Django)
+    # TODO drüber nachdenken ob wir die brauchen (ist default an in Django)
+    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',  # noqa: ERA001
 ]
 
 ROOT_URLCONF = 'hub.urls'
@@ -271,9 +258,7 @@ SESSION_COOKIE_HTTPONLY = True  # session cookie is unavailable to JavaScript (d
 SESSION_COOKIE_SAMESITE = 'Lax'  # set SameSite=Lax (default)
 SESSION_COOKIE_PATH = env('COOKIE_PATH') or '/'  # use configured path, SESSION_NAME or default '/'
 SESSION_COOKIE_SECURE = True  # mark session cookie as https-only
-SESSION_SAVE_EVERY_REQUEST = (
-    False  # no need to update a session on each request (default)
-)
+SESSION_SAVE_EVERY_REQUEST = False  # no need to update a session on each request (default)
 
 # CSRF Cookie configuration
 CSRF_COOKIE_NAME = env('CSRF_COOKIE_NAME', default=SESSION_COOKIE_NAME.replace('_SESSION', '_CSRF') if '_SESSION' in SESSION_COOKIE_NAME else 'HUB_CSRF')
@@ -291,7 +276,9 @@ OAUTH2_PROVIDER = {
 # https://docs.djangoproject.com/en/3.1/topics/i18n/
 
 LANGUAGE_CODE = 'en'
-LANGUAGE_COOKIE_NAME = env('LANGUAGE_COOKIE_NAME', default=SESSION_COOKIE_NAME.replace('_SESSION', '_LANG') if '_SESSION' in SESSION_COOKIE_NAME else 'HUB_LANG')  # noqa:E501
+LANGUAGE_COOKIE_NAME = env(
+    'LANGUAGE_COOKIE_NAME', default=SESSION_COOKIE_NAME.replace('_SESSION', '_LANG') if '_SESSION' in SESSION_COOKIE_NAME else 'HUB_LANG'
+)  # noqa:E501
 LANGUAGE_COOKIE_PATH = SESSION_COOKIE_PATH
 LANGUAGE_COOKIE_SECURE = SESSION_COOKIE_SECURE
 
@@ -299,8 +286,8 @@ TIME_ZONE = env('TIMEZONE')
 USE_I18N = True
 USE_TZ = True
 LANGUAGES = [
-    ('de', _("German")),
-    ('en', _("English")),
+    ('de', _('German')),
+    ('en', _('English')),
 ]
 
 MODELTRANSLATION_FALLBACK_LANGUAGES = ['en', 'de']
@@ -316,13 +303,13 @@ STATIC_ROOT = BASE_DIR / 'static.dist'
 
 # setup storage, we assume the default
 STORAGES = {
-    "staticfiles": {
-        "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
+    'staticfiles': {
+        'BACKEND': 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage',
     },
 }
 
-if env.bool("USE_PLAIN_STATICFILES", False):
-    STORAGES["staticfiles"]["BACKEND"] = "django.contrib.staticfiles.storage.StaticFilesStorage"
+if env.bool('USE_PLAIN_STATICFILES', False):
+    STORAGES['staticfiles']['BACKEND'] = 'django.contrib.staticfiles.storage.StaticFilesStorage'
 
 storage_type = env('STORAGE_TYPE').lower()
 if storage_type == 's3':
@@ -330,7 +317,7 @@ if storage_type == 's3':
         'BACKEND': 'storages.backends.s3boto3.S3Boto3Storage',
         'OPTIONS': {
             'file_overwrite': False,
-        }
+        },
     }
 
     AWS_STORAGE_BUCKET_NAME = env.str('S3_BUCKET', 'static')
@@ -351,17 +338,18 @@ elif storage_type == 'sftp':
     SFTP_STORAGE_INTERACTIVE = False
     SFTP_STORAGE_PARAMS = env.dict('SFTP_PARAMS')
 
-elif storage_type == 'local' or storage_type == '':
+elif storage_type in ('local', ''):
     if storage_type == '':
         import warnings
+
         warnings.warn('No STORAGE_TYPE selected, defaulting to "local". Verify if that is what you want!', RuntimeWarning)
 
     STORAGES['default'] = {
-        "BACKEND": "django.core.files.storage.FileSystemStorage",
+        'BACKEND': 'django.core.files.storage.FileSystemStorage',
         'OPTIONS': {
             'base_url': env('MEDIA_URL'),
             'location': env.path('MEDIA_PATH', BASE_DIR / 'media'),
-        }
+        },
     }
 
 else:
@@ -412,11 +400,11 @@ FORBIDDEN_ASSEMBLY_SLUGS = ['admin', 'visit', 'maps', 'api', 'pusher']
 
 # Logging configuration
 LOGGING = {
-    "version": 1,
-    "disable_existing_loggers": False,
-    "handlers": {
-        "console": {
-            "class": "logging.StreamHandler",
+    'version': 1,
+    'disable_existing_loggers': False,
+    'handlers': {
+        'console': {
+            'class': 'logging.StreamHandler',
         },
         'null': {
             'class': 'logging.NullHandler',
@@ -428,9 +416,9 @@ LOGGING = {
             'propagate': False,
         },
     },
-    "root": {
-        "handlers": ["console"],
-        "level": env('LOG_LEVEL'),
+    'root': {
+        'handlers': ['console'],
+        'level': env('LOG_LEVEL'),
     },
 }
 
@@ -481,7 +469,7 @@ PRETIX_SECRET_KEY = env('PRETIX_SECRET')  # the JWT shared secret with Pretix
 
 # Restrict the access to /metrics/ to certain IP addresses (e.g. allowe the Prometheus/Grafana server only).
 # You can add one or multiple IP addresse by setting the `METRICS_SERVER_IPS` env var.
-# e.g. METRICS_SERVER_IPS="127.0.0.1,80.147.140.51"
+# e.g. METRICS_SERVER_IPS="127.0.0.1,80.147.140.51" # noqa: ERA001
 # The default is to allow every IP address (METRICS_SERVER_IPS="*").
 METRICS_SERVER_IPS = env('METRICS_SERVER_IPS')
 
@@ -506,14 +494,8 @@ SCHEDULES_SUPPORT_FILE_PROTOCOL = False
 INTEGRATIONS_BBB = env('BIGBLUEBUTTON')
 BIGBLUEBUTTON_API_URL = env('BIGBLUEBUTTON_API_URL')
 BIGBLUEBUTTON_API_TOKEN = env('BIGBLUEBUTTON_API_TOKEN')
-BIGBLUEBUTTON_END_MEETING_CALLBACK = env(
-    'BIGBLUEBUTTON_END_MEETING_CALLBACK'
-)  # url bbb will call to notify us of ending meetings
-# BIGBLUEBUTTON_END_MEETING_CALLBACK = 'https://rc3.world/api/bbb_meeting_end'
-BIGBLUEBUTTON_INITIAL_PRESENTATION_URL = env(
-    'BIGBLUEBUTTON_INITIAL_PRESENTATION_URL'
-)  # url to tell bbb to use as initial presentation
-# BIGBLUEBUTTON_INITIAL_PRESENTATION_URL = 'https://rc3.world/static/plainui/bbb-background.jpg'
+BIGBLUEBUTTON_END_MEETING_CALLBACK = env('BIGBLUEBUTTON_END_MEETING_CALLBACK')  # url bbb will call to notify us of ending meetings
+BIGBLUEBUTTON_INITIAL_PRESENTATION_URL = env('BIGBLUEBUTTON_INITIAL_PRESENTATION_URL')  # url to tell bbb to use as initial presentation
 
 # Hangar
 INTEGRATIONS_HANGAR = env('HANGAR')
@@ -530,40 +512,26 @@ WORKADVENTURE_TERMINATE_OLD_SESSIONS_ON_REGISTER = False
 
 # URL (and optional Authorization-Header value) for pushing maps to exneuland, accepts "%CONFSLUG% variable
 WORKADVENTURE_BACKEND_MAP_PUSH_URL = env('WORKADVENTURE_BACKEND_MAP_PUSH_URL')
-WORKADVENTURE_BACKEND_MAP_PUSH_AUTHORIZATION = env(
-    'WORKADVENTURE_BACKEND_MAP_PUSH_AUTHORIZATION'
-)
+WORKADVENTURE_BACKEND_MAP_PUSH_AUTHORIZATION = env('WORKADVENTURE_BACKEND_MAP_PUSH_AUTHORIZATION')
 
 # URL (and optional Authorization-Header value) for pushing userinfo to exneuland, accepts "%CONFSLUG% and %USERID% variables
 WORKADVENTURE_BACKEND_USERINFO_PUSH_URL = env('WORKADVENTURE_BACKEND_USERINFO_PUSH_URL')
-WORKADVENTURE_BACKEND_USERINFO_PUSH_AUTHORIZATION = env(
-    'WORKADVENTURE_BACKEND_USERINFO_PUSH_AUTHORIZATION'
-)
+WORKADVENTURE_BACKEND_USERINFO_PUSH_AUTHORIZATION = env('WORKADVENTURE_BACKEND_USERINFO_PUSH_AUTHORIZATION')
 
 # URL (and optional Authorization-Header value) for requesting mapservice sync, accepts "%CONFSLUG% variable
 WORKADVENTURE_MAPSERVICE_SYNC_URL = env('WORKADVENTURE_MAPSERVICE_SYNC_URL')
-WORKADVENTURE_MAPSERVICE_SYNC_AUTHORIZATION = env(
-    'WORKADVENTURE_MAPSERVICE_SYNC_AUTHORIZATION'
-)
+WORKADVENTURE_MAPSERVICE_SYNC_AUTHORIZATION = env('WORKADVENTURE_MAPSERVICE_SYNC_AUTHORIZATION')
 WORKADVENTURE_MAPSERVICE_SYNC_NOVERIFY = env('WORKADVENTURE_MAPSERVICE_SYNC_NOVERIFY')
 
 # URL (and optional Authorization-Header value) for requesting mapservice sync of a single room, accepts "%CONFSLUG% and %ROOMID% variable
 WORKADVENTURE_MAPSERVICE_SYNCROOM_URL = env('WORKADVENTURE_MAPSERVICE_SYNCROOM_URL')
-WORKADVENTURE_MAPSERVICE_SYNCROOM_AUTHORIZATION = env(
-    'WORKADVENTURE_MAPSERVICE_SYNCROOM_AUTHORIZATION'
-)
-WORKADVENTURE_MAPSERVICE_SYNCROOM_NOVERIFY = env(
-    'WORKADVENTURE_MAPSERVICE_SYNCROOM_NOVERIFY'
-)
+WORKADVENTURE_MAPSERVICE_SYNCROOM_AUTHORIZATION = env('WORKADVENTURE_MAPSERVICE_SYNCROOM_AUTHORIZATION')
+WORKADVENTURE_MAPSERVICE_SYNCROOM_NOVERIFY = env('WORKADVENTURE_MAPSERVICE_SYNCROOM_NOVERIFY')
 
 # URL (and optional Authorization-Header value) for setting mapservice's loglevel, accepts "%CONFSLUG% variable
 WORKADVENTURE_MAPSERVICE_LOGLEVEL_URL = env('WORKADVENTURE_MAPSERVICE_LOGLEVEL_URL')
-WORKADVENTURE_MAPSERVICE_LOGLEVEL_AUTHORIZATION = env(
-    'WORKADVENTURE_MAPSERVICE_LOGLEVEL_AUTHORIZATION'
-)
-WORKADVENTURE_MAPSERVICE_LOGLEVEL_NOVERIFY = env(
-    'WORKADVENTURE_MAPSERVICE_LOGLEVEL_NOVERIFY'
-)
+WORKADVENTURE_MAPSERVICE_LOGLEVEL_AUTHORIZATION = env('WORKADVENTURE_MAPSERVICE_LOGLEVEL_AUTHORIZATION')
+WORKADVENTURE_MAPSERVICE_LOGLEVEL_NOVERIFY = env('WORKADVENTURE_MAPSERVICE_LOGLEVEL_NOVERIFY')
 
 # push maps to backend upon new data from mapservice?
 WORKADVENTURE_BACKEND_PUSH_ON_MAPSERVICE_DATA = True
@@ -614,15 +582,15 @@ WORKADVENTURE_LOBBY_ROOM_SLUG = 'lobby'
 # flag if newly created pages are localized by default (or not)
 STATIC_PAGE_LOCALIZED_BY_DEFAULT = env.bool('STATICPAGES_LOCALIZED_BY_DEFAULT', False)
 
-PLAINUI_THEME_SUPPORT = env("PLAINUI_THEME_SUPPORT")
+PLAINUI_THEME_SUPPORT = env('PLAINUI_THEME_SUPPORT')
 
-ASSEMBLY_BANNER_FILE_SIZE_LIMIT = env("ASSEMBLY_BANNER_FILE_SIZE_LIMIT")
-ASSEMBLY_BANNER_WIDTH_MINIMUM = env("ASSEMBLY_BANNER_WIDTH_MINIMUM")
-ASSEMBLY_BANNER_HEIGHT_MINIMUM = env("ASSEMBLY_BANNER_HEIGHT_MINIMUM")
-ASSEMBLY_BANNER_WIDTH_MAXIMUM = env("ASSEMBLY_BANNER_WIDTH_MAXIMUM")
-ASSEMBLY_BANNER_HEIGHT_MAXIMUM = env("ASSEMBLY_BANNER_HEIGHT_MAXIMUM")
+ASSEMBLY_BANNER_FILE_SIZE_LIMIT = env('ASSEMBLY_BANNER_FILE_SIZE_LIMIT')
+ASSEMBLY_BANNER_WIDTH_MINIMUM = env('ASSEMBLY_BANNER_WIDTH_MINIMUM')
+ASSEMBLY_BANNER_HEIGHT_MINIMUM = env('ASSEMBLY_BANNER_HEIGHT_MINIMUM')
+ASSEMBLY_BANNER_WIDTH_MAXIMUM = env('ASSEMBLY_BANNER_WIDTH_MAXIMUM')
+ASSEMBLY_BANNER_HEIGHT_MAXIMUM = env('ASSEMBLY_BANNER_HEIGHT_MAXIMUM')
 
-BADGE_IMAGE_WIDTH_MINIMUM = env("BADGE_IMAGE_WIDTH_MINIMUM")
-BADGE_IMAGE_HEIGHT_MINIMUM = env("BADGE_IMAGE_HEIGHT_MINIMUM")
-BADGE_IMAGE_WIDTH_MAXIMUM = env("BADGE_IMAGE_WIDTH_MAXIMUM")
-BADGE_IMAGE_HEIGHT_MAXIMUM = env("BADGE_IMAGE_HEIGHT_MAXIMUM")
+BADGE_IMAGE_WIDTH_MINIMUM = env('BADGE_IMAGE_WIDTH_MINIMUM')
+BADGE_IMAGE_HEIGHT_MINIMUM = env('BADGE_IMAGE_HEIGHT_MINIMUM')
+BADGE_IMAGE_WIDTH_MAXIMUM = env('BADGE_IMAGE_WIDTH_MAXIMUM')
+BADGE_IMAGE_HEIGHT_MAXIMUM = env('BADGE_IMAGE_HEIGHT_MAXIMUM')
diff --git a/src/hub/settings/build.py b/src/hub/settings/build.py
index aedac262f..bda3e5fa7 100644
--- a/src/hub/settings/build.py
+++ b/src/hub/settings/build.py
@@ -1,6 +1,6 @@
 from hub.settings.base import *  # noqa: F403
 
-SECRET_KEY = "BUILD_KEY"
+SECRET_KEY = 'BUILD_KEY'
 
 # Load apps, so static files can be loaded during build
 INSTALLED_APPS += ['backoffice', 'django_bootstrap5', 'widget_tweaks']  # noqa: F405
diff --git a/src/hub/settings/default.py b/src/hub/settings/default.py
index 84a050b1c..75566aa50 100644
--- a/src/hub/settings/default.py
+++ b/src/hub/settings/default.py
@@ -17,7 +17,7 @@ default_env = environ.FileAwareEnv(
 )
 
 
-def print_banner(message: str, filler: str = "*"):
+def print_banner(message: str, filler: str = '*'):
     print(filler * 72)
     print(filler, message)
     print(filler * 72)
@@ -25,8 +25,8 @@ def print_banner(message: str, filler: str = "*"):
 
 # configure database using environment variable "DATABASE_URL"
 DATABASES['default'] = default_env.db(default='postgis://localhost/hub')  # noqa: F405
-if DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql', 'django.contrib.gis.db.backends.postgis'] and default_env("HUB_DB_SCHEMA"):   # noqa:F405
-    DATABASES['default']['OPTIONS'] = {   # noqa:F405
+if DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql', 'django.contrib.gis.db.backends.postgis'] and default_env('HUB_DB_SCHEMA'):  # noqa:F405
+    DATABASES['default']['OPTIONS'] = {  # noqa:F405
         'options': f'-c search_path={default_env("HUB_DB_SCHEMA")}'
     }
 
@@ -36,7 +36,7 @@ if (django_host := default_env('DJANGO_HOST')) is not None:
         ALLOWED_HOSTS.append(django_host)  # noqa:F405
 
 # set flags
-SERVE_MODULE_OUTPUT = default_env("SERVE_MODULE_OUTPUT")
+SERVE_MODULE_OUTPUT = default_env('SERVE_MODULE_OUTPUT')
 IS_ADMIN = default_env('SERVE_ADMIN')
 if SERVE_MODULE_OUTPUT and IS_ADMIN:
     print_banner('Admin module has been selected and will be served', '-')
@@ -67,19 +67,19 @@ except ImportError:
 
 # read SECRET_KEY from a file not stored in git
 if DEBUG and SECRET_KEY is None:  # noqa: F405
-    SECRET_FILE = BASE_DIR.joinpath("hub", ".settings.secret")  # noqa: F405
+    SECRET_FILE = BASE_DIR.joinpath('hub', '.settings.secret')  # noqa: F405
     try:
         with SECRET_FILE.open() as secret_file:
             SECRET_KEY = secret_file.read().strip()
-    except IOError:
+    except OSError:
         try:
             SECRET_KEY = gen_secret_key()  # noqa: F405
-            with SECRET_FILE.open("w") as secret:
+            with SECRET_FILE.open('w') as secret:
                 secret.write(SECRET_KEY)
                 print('*' * 72)
                 print('*', 'Written SECRET_KEY file:', SECRET_FILE)
                 print('*' * 72)
-        except IOError:
+        except OSError:
             Exception('Please create a %s file with random characters to serve as your Django SECRET_KEY!' % SECRET_FILE)
 
 # include API django api
@@ -148,23 +148,20 @@ if SENTRY_ENDPOINT is not None:  # noqa: F405
     sentry_sdk.init(
         dsn=SENTRY_ENDPOINT,  # noqa: F405
         integrations=[DjangoIntegration()],
-
         # trace sample rate in percent
         traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,  # noqa: F405
-
         # profiling rate (percentage of traces)
         profiles_sample_rate=SENTRY_PROFILES_SAMPLE_RATE,  # noqa: F405
-
         # do not send user information (django.contrib.auth)
-        send_default_pii=False
+        send_default_pii=False,
     )
 
 if IS_API and SSO_SECRET is None:  # noqa: F405
     SSO_SECRET = SECRET_KEY[::-1]
-    if default_env("SSO_WARNING"):
+    if default_env('SSO_WARNING'):
         print_banner('Generated a SSO_SECRET from your SECRET_KEY, this is probably not what you want!')
 
-if '*' in METRICS_SERVER_IPS:   # noqa: F405
+if '*' in METRICS_SERVER_IPS:  # noqa: F405
     print_banner('Warning: /metrics/ is accessible from every IP address ("*"). Set METRICS_SERVER_IPS="<prometheus server ip>".')
 
 TEST_RUNNER = 'core.tests.runner.PostgresSchemaTestRunner'
diff --git a/src/hub/settings/dev.py b/src/hub/settings/dev.py
index 258a2a0ba..6948b660a 100644
--- a/src/hub/settings/dev.py
+++ b/src/hub/settings/dev.py
@@ -2,19 +2,17 @@ import os
 
 import environ
 
-dev_env = environ.FileAwareEnv(
-    SERVE_DEBUGPY=(bool, False)
-)
-
-if dev_env("SERVE_DEBUGPY"):
+dev_env = environ.FileAwareEnv(SERVE_DEBUGPY=(bool, False))
 
+if dev_env('SERVE_DEBUGPY'):
     # Disable debug warnings for python 3.11 see https://github.com/microsoft/debugpy/issues/861
-    os.environ.setdefault('PYDEVD_DISABLE_FILE_VALIDATION', "1")
+    os.environ.setdefault('PYDEVD_DISABLE_FILE_VALIDATION', '1')
 
     try:
         import debugpy
-        debugpy.listen(("0.0.0.0", 5678))
-        print("debugpy ready for connection at 0.0.0.0:5678")
+
+        debugpy.listen(('0.0.0.0', 5678))
+        print('debugpy ready for connection at 0.0.0.0:5678')
 
     except ImportError:
         print('!' * 72)
@@ -38,7 +36,7 @@ os.environ.setdefault('SERVE_BACKOFFICE', 'yes')
 os.environ.setdefault('SERVE_FRONTEND', 'yes')
 
 # serve metrics locally only (and silence the warning)
-os.environ.setdefault("METRICS_SERVER_IPS", "127.0.0.1")
+os.environ.setdefault('METRICS_SERVER_IPS', '127.0.0.1')
 
 # store media files locally
 os.environ.setdefault('STORAGE_TYPE', 'local')
diff --git a/src/hub/views.py b/src/hub/views.py
index e62038e62..c92cfcc14 100644
--- a/src/hub/views.py
+++ b/src/hub/views.py
@@ -1,11 +1,12 @@
 import json
 
+from django_ratelimit.exceptions import Ratelimited
+
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.http import HttpResponse, HttpResponseForbidden, HttpResponseServerError, JsonResponse
 from django.utils.cache import add_never_cache_headers
 from django.views.decorators.http import require_safe
-from django_ratelimit.exceptions import Ratelimited
 
 from hub.http import HttpResponseRateLimited
 
@@ -44,7 +45,7 @@ def index(request):
 def debug_headers(request):
     param = request.GET if request.method not in ['POST', 'DELETE', 'PUT', 'PATCH'] else request.POST
     token = param.get('token', '')
-    if settings.HEADERS_DEBUG_ENDPOINT_TOKEN != token:
+    if token != settings.HEADERS_DEBUG_ENDPOINT_TOKEN:
         return HttpResponse('Wrong token.', 'text/plain', status=403)
 
     request_headers = dict(request.headers)
diff --git a/src/manage.py b/src/manage.py
index b0e2fe530..1558aee21 100755
--- a/src/manage.py
+++ b/src/manage.py
@@ -14,8 +14,8 @@ def main():
     except ImportError as exc:
         raise ImportError(
             "Couldn't import Django. Are you sure it's installed and "
-            "available on your PYTHONPATH environment variable? Did you "
-            "forget to activate a virtual environment?"
+            'available on your PYTHONPATH environment variable? Did you '
+            'forget to activate a virtual environment?'
         ) from exc
     execute_from_command_line(sys.argv)
 
diff --git a/src/plainui/forms.py b/src/plainui/forms.py
index 8d4a9c34c..5d717a2e7 100644
--- a/src/plainui/forms.py
+++ b/src/plainui/forms.py
@@ -5,12 +5,22 @@ from django.contrib.auth import forms as auth_forms
 from django.forms import ValidationError
 from django.utils.formats import localize
 from django.utils.timezone import localtime, now
-from django.utils.translation import gettext_lazy as _, gettext
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
 
 from core.abuse import REPORT_CATEGORIES, report_content
 from core.base_forms import TranslatedFieldsForm
-from core.models import BadgeToken, BulletinBoardEntry, ConferenceMember, ConferenceMemberTicket, DirectMessage, \
-    PlatformUser, StaticPage, UserCommunicationChannel, UserBadge
+from core.models import (
+    BadgeToken,
+    BulletinBoardEntry,
+    ConferenceMember,
+    ConferenceMemberTicket,
+    DirectMessage,
+    PlatformUser,
+    StaticPage,
+    UserBadge,
+    UserCommunicationChannel,
+)
 from core.models.badges import TOKEN_VALIDATOR
 
 
@@ -18,7 +28,7 @@ class UsernameField(forms.CharField):
     def to_python(self, value):
         user = PlatformUser.objects.filter(username=value).first()
         if user is None:
-            raise ValidationError(gettext("Unknown User!"), code='invalid')
+            raise ValidationError(gettext('Unknown User!'), code='invalid')
         return user
 
 
@@ -30,15 +40,15 @@ class NewDirectMessageForm(forms.Form):
         super().__init__(*args, **kwargs)
 
     in_reply_to = forms.UUIDField(required=False)
-    recipient = UsernameField(widget=forms.TextInput(attrs={'placeholder': _("Please enter the recipient name")}))
-    subject = forms.CharField(max_length=200, min_length=1, strip=True, widget=forms.TextInput(attrs={'placeholder': _("Please enter a subject")}))
+    recipient = UsernameField(widget=forms.TextInput(attrs={'placeholder': _('Please enter the recipient name')}))
+    subject = forms.CharField(max_length=200, min_length=1, strip=True, widget=forms.TextInput(attrs={'placeholder': _('Please enter a subject')}))
     body = forms.CharField(widget=forms.Textarea)
 
     def clean(self):
         if self.conf.send_pn_disabled:
-            raise ValidationError(_("Sending Messages is currently disabled"))
+            raise ValidationError(_('Sending Messages is currently disabled'))
         if self.request.limited:
-            raise ValidationError(_("rate-limited"))
+            raise ValidationError(_('rate-limited'))
 
 
 class BulletinBoardEntryForm(TranslatedFieldsForm):
@@ -52,19 +62,20 @@ class BulletinBoardEntryForm(TranslatedFieldsForm):
         self.user = user
         super().__init__(*args, **kwargs)
 
-    title = forms.CharField(max_length=200, min_length=1, strip=True, widget=forms.TextInput(attrs={'placeholder': _("Please enter a title")}))
+    title = forms.CharField(max_length=200, min_length=1, strip=True, widget=forms.TextInput(attrs={'placeholder': _('Please enter a title')}))
     is_public = forms.BooleanField(required=False)
 
     def clean(self):
         if self.conf.board_disabled:
-            raise ValidationError(_("Bulletin Board is currently disabled"))
+            raise ValidationError(_('Bulletin Board is currently disabled'))
         if self.request.limited:
-            raise ValidationError(_("rate-limited"))
+            raise ValidationError(_('rate-limited'))
 
 
 class ExampleForm(forms.Form):
-    """ used in the component gallery """
-    text = forms.CharField(min_length=1, help_text='Help Text', widget=forms.TextInput(attrs={'placeholder': _("Placeholder text")}))
+    """used in the component gallery"""
+
+    text = forms.CharField(min_length=1, help_text='Help Text', widget=forms.TextInput(attrs={'placeholder': _('Placeholder text')}))
     password = forms.CharField(help_text='Help Text', widget=forms.PasswordInput)
     checkbox = forms.BooleanField(help_text='Help Text')
     textarea = forms.CharField(widget=forms.Textarea, help_text='Help Text')
@@ -72,40 +83,39 @@ class ExampleForm(forms.Form):
     def clean(self):
         super().clean()
         if 'text' not in self.cleaned_data:
-            raise ValidationError("Some general error message of the form")
+            raise ValidationError('Some general error message of the form')
 
 
 class ProfileEditForm(TranslatedFieldsForm):
     class Meta:
         model = PlatformUser
         fields = [
-            'pronouns', 'timezone',
+            'pronouns',
+            'timezone',
         ]
 
 
 class ProfileDescriptionEditForm(TranslatedFieldsForm):
     class Meta:
         model = ConferenceMember
-        fields = [
-            'description'
-        ]
+        fields = ['description']
 
 
 class RedeemBadgeForm(forms.Form):
-    token = forms.CharField(required=True, label="", widget=forms.TextInput(attrs={'placeholder': 'Token'}), validators=[TOKEN_VALIDATOR])
-    purpose = forms.CharField(required=True, label="", widget=forms.HiddenInput(), initial="redeem_token")
+    token = forms.CharField(required=True, label='', widget=forms.TextInput(attrs={'placeholder': 'Token'}), validators=[TOKEN_VALIDATOR])
+    purpose = forms.CharField(required=True, label='', widget=forms.HiddenInput(), initial='redeem_token')
     conference = None
 
     def __init__(self, *args, **kwargs) -> None:
-        self.conference = kwargs.pop("conference", None)
+        self.conference = kwargs.pop('conference', None)
         super().__init__(*args, **kwargs)
 
     def clean_token(self):
-        token = self.cleaned_data["token"]
+        token = self.cleaned_data['token']
         try:
             badge_token = BadgeToken.objects.get(token=token, badge__conference=self.conference)
         except BadgeToken.DoesNotExist:
-            raise ValidationError(_('BadgeToken__does_not_exist %(token)s') % {"token": token})
+            raise ValidationError(_('BadgeToken__does_not_exist %(token)s') % {'token': token})
 
         return badge_token
 
@@ -117,7 +127,7 @@ class ManageBadgeForm(forms.ModelForm):
 
 
 class InputTokenForm(forms.Form):
-    jwt = forms.CharField(max_length=None, required=True, label=_("Token"))
+    jwt = forms.CharField(max_length=None, required=True, label=_('Token'))
 
     def __init__(self, conf, user, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -126,7 +136,7 @@ class InputTokenForm(forms.Form):
 
     def clean_jwt(self):
         if self.conf.end <= now():
-            raise ValidationError(gettext("The Conference is over!"))
+            raise ValidationError(gettext('The Conference is over!'))
         try:
             ConferenceMemberTicket.validate_pretix_ticket(self.conf, self.user, self.cleaned_data['jwt'])
             return self.cleaned_data['jwt']
@@ -143,9 +153,9 @@ class RedeemTokenAddToUserForm(auth_forms.AuthenticationForm):
 
     def clean(self):
         if self.conf.end <= now():
-            raise ValidationError(gettext("The Conference is over!"))
+            raise ValidationError(gettext('The Conference is over!'))
         if self.request.limited:
-            raise ValidationError(_("rate-limited"))
+            raise ValidationError(_('rate-limited'))
         return super().clean()
 
 
@@ -155,16 +165,17 @@ class WorkadventureCompatibleUsernameField(auth_forms.UsernameField):
     Django on the otherhand has a default of max_length=150 for usernames.
     (see https://git.cccv.de/hub/hub/-/issues/228)
     """
+
     def __init__(self, *args, **kwargs):
         # overwrite or set the maximum length
         kwargs['max_length'] = 20
-        return super().__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
 
 class RedeemTokenUserCreateForm(auth_forms.UserCreationForm):
     class Meta:
         model = PlatformUser
-        fields = ("username",)
+        fields = ('username',)
         # Overwrite the auth_forms.UsernameField with its extended version that
         # enforces usernames with less than 20 characters.
         field_classes = {'username': WorkadventureCompatibleUsernameField}
@@ -174,20 +185,23 @@ class RedeemTokenUserCreateForm(auth_forms.UserCreationForm):
     def __init__(self, conf, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.conf = conf
-        self['username'].help_text = \
-            gettext("Required. 20 characters or fewer. Letters, digits and @/./+/-/_ only.") + '<br><b>' \
-            + gettext("Your username is visible to others and cannot be changed later.") + '</b><br>' \
-            + gettext("The username will also be displayed next to your avatar inside the 2D world.")
+        self['username'].help_text = (
+            gettext('Required. 20 characters or fewer. Letters, digits and @/./+/-/_ only.')
+            + '<br><b>'
+            + gettext('Your username is visible to others and cannot be changed later.')
+            + '</b><br>'
+            + gettext('The username will also be displayed next to your avatar inside the 2D world.')
+        )
 
     def clean(self):
         if self.conf.end <= now():
-            raise ValidationError(gettext("The Conference is over!"))
+            raise ValidationError(gettext('The Conference is over!'))
         return super().clean()
 
 
 class TokenPasswortResetForm(auth_forms.SetPasswordForm):
     jwt = forms.CharField(max_length=None, required=True, widget=forms.HiddenInput())
-    username = forms.CharField(required=True, label=_("Username"))
+    username = forms.CharField(required=True, label=_('Username'))
 
     def __init__(self, conf, user, *args, **kwargs):
         super().__init__(user, *args, **kwargs)
@@ -205,13 +219,13 @@ class TokenPasswortResetForm(auth_forms.SetPasswordForm):
 
         user = PlatformUser.objects.filter(username=self.cleaned_data['username']).first()
         if user is None:
-            raise ValidationError(gettext("Unknown User"))
+            raise ValidationError(gettext('Unknown User'))
 
         if not user.is_active:
-            raise ValidationError(gettext("User is not active!"))
+            raise ValidationError(gettext('User is not active!'))
 
         if user.email or user.communication_channels.filter(channel=UserCommunicationChannel.Channel.MAIL, is_verified=True).exists():
-            raise ValidationError(gettext("User can use password reset by email!"))
+            raise ValidationError(gettext('User can use password reset by email!'))
 
         pretix_ident, ticket_data = ConferenceMemberTicket.validate_pretix_ticket(self.conf, self.user, self.cleaned_data['jwt'], validate_only=True)
         if not ConferenceMemberTicket.objects.filter(conference=self.conf, user=user, ident=pretix_ident).exists():
@@ -226,22 +240,25 @@ class RoomChoiceField(forms.ModelChoiceField):
 
     def label_from_instance(self, obj):
         if obj.pk in self.blocked_rooms:
-            return markupsafe.Markup('%s [%s]') % (obj, gettext("blocked"))
+            return markupsafe.Markup('%s [%s]') % (obj, gettext('blocked'))
 
         return str(obj)
 
 
 class ReportForm(forms.Form):
-    kind = forms.ChoiceField(choices=[
-        ('url', 'url'),
-        ('pn', 'pn'),
-        ('board', 'board'),
-    ], widget=forms.HiddenInput())
+    kind = forms.ChoiceField(
+        choices=[
+            ('url', 'url'),
+            ('pn', 'pn'),
+            ('board', 'board'),
+        ],
+        widget=forms.HiddenInput(),
+    )
     kind_data = forms.CharField(min_length=1, widget=forms.HiddenInput())
     next = forms.CharField(widget=forms.HiddenInput(), required=False)
     category = forms.ChoiceField(choices=[(k, v[0]) for k, v in REPORT_CATEGORIES.items()], initial='security')
-    message = forms.CharField(min_length=1, widget=forms.Textarea(), label=_("describe the problem"), required=True)
-    message2 = forms.CharField(min_length=1, widget=forms.Textarea(), label=_("describe a solution"), required=True)
+    message = forms.CharField(min_length=1, widget=forms.Textarea(), label=_('describe the problem'), required=True)
+    message2 = forms.CharField(min_length=1, widget=forms.Textarea(), label=_('describe a solution'), required=True)
 
     def __init__(self, *args, request, conf, **kwargs):
         super().__init__(*args, **kwargs)
@@ -250,7 +267,7 @@ class ReportForm(forms.Form):
 
     def clean(self):
         if self.request.limited:
-            raise ValidationError(_("rate-limited"))
+            raise ValidationError(_('rate-limited'))
         return super().clean()
 
     def send_report_mail(self, request):
@@ -288,7 +305,7 @@ class ReportForm(forms.Form):
             }
 
         else:
-            raise Exception("Unknown kind")
+            raise Exception('Unknown kind')
 
         report_content(
             request=request,
diff --git a/src/plainui/jinja2.py b/src/plainui/jinja2.py
index ae9717c94..8c155ff7e 100644
--- a/src/plainui/jinja2.py
+++ b/src/plainui/jinja2.py
@@ -1,4 +1,9 @@
 from datetime import datetime, timedelta
+
+from jinja2 import Environment, pass_context
+from jinja2.runtime import Context
+
+from django.contrib.humanize.templatetags.humanize import NaturalTimeFormatter
 from django.contrib.messages import get_messages
 from django.templatetags.static import static
 from django.urls import reverse
@@ -7,12 +12,7 @@ from django.utils.formats import localize
 from django.utils.functional import LazyObject
 from django.utils.html import json_script
 from django.utils.timezone import localdate, localtime
-from django.utils.translation import gettext, ngettext, get_language
-from django.contrib.humanize.templatetags.humanize import NaturalTimeFormatter
-
-from jinja2 import Environment, pass_context
-from jinja2.runtime import Context
-
+from django.utils.translation import get_language, gettext, ngettext
 from modeltranslation.fields import build_localized_fieldname
 from modeltranslation.settings import AVAILABLE_LANGUAGES
 
@@ -122,8 +122,8 @@ def show_vars(ctx, var=_UNSET):
             var = var.__dict__
 
         ret = ''
-        for (k, v) in var.items():
-            ret += '%r: %r\n' % (k, v)
+        for k, v in var.items():
+            ret += f'{k!r}: {v!r}\n'
         return ret
 
     except (AttributeError, TypeError):
@@ -182,29 +182,28 @@ class MyEnvironment(Environment):
 
 
 def environment(**options):
-    env = MyEnvironment(**{
-        'extensions': ["jinja2.ext.i18n", "jinja2.ext.debug"],
-        **options
-    })
-    env.globals.update({
-        'browser_type': browser_type,
-        'css_scope': css_scope,
-        'active_theme': active_theme,
-        'get_language': get_language,
-        'get_messages': get_messages,
-        'hub_absolute': hub_absolute,
-        'json_script': json_script,
-        'num_of_notifications': num_of_notifications,
-        'num_of_unread_messages': num_of_unread_messages,
-        'num_of_pending_badges': num_of_pending_badges,
-        'static': static,
-        'url': url,
-        'show_vars': show_vars,
-        'unique_id': unique_id,
-        'translated_fields_for_field': translated_fields_for_field,
-        'field_translation_languages': field_translation_languages,
-        'now': timezone.now(),
-    })
+    env = MyEnvironment(**{'extensions': ['jinja2.ext.i18n', 'jinja2.ext.debug'], **options})
+    env.globals.update(
+        {
+            'browser_type': browser_type,
+            'css_scope': css_scope,
+            'active_theme': active_theme,
+            'get_language': get_language,
+            'get_messages': get_messages,
+            'hub_absolute': hub_absolute,
+            'json_script': json_script,
+            'num_of_notifications': num_of_notifications,
+            'num_of_unread_messages': num_of_unread_messages,
+            'num_of_pending_badges': num_of_pending_badges,
+            'static': static,
+            'url': url,
+            'show_vars': show_vars,
+            'unique_id': unique_id,
+            'translated_fields_for_field': translated_fields_for_field,
+            'field_translation_languages': field_translation_languages,
+            'now': timezone.now(),
+        }
+    )
     env.filters['strftdelta'] = custom_timedelta
     env.filters['strftdelta_short'] = custom_timedelta_short
     env.filters['strftimehm'] = custom_strftimehm
diff --git a/src/plainui/management/commands/makemessages.py b/src/plainui/management/commands/makemessages.py
index a9f8a891a..80aa8fe8f 100644
--- a/src/plainui/management/commands/makemessages.py
+++ b/src/plainui/management/commands/makemessages.py
@@ -1,10 +1,10 @@
 import re
+from io import StringIO
+from pathlib import Path
 
 from babel.messages.extract import extract
 from babel.messages.pofile import read_po, write_po
 from jinja2.ext import babel_extract
-from io import StringIO
-from pathlib import Path
 
 from django.core.management.commands import makemessages
 
diff --git a/src/plainui/tests/test_views.py b/src/plainui/tests/test_views.py
index 35add5230..2d0423526 100644
--- a/src/plainui/tests/test_views.py
+++ b/src/plainui/tests/test_views.py
@@ -57,7 +57,7 @@ from plainui.views import ConferenceRequiredMixin, LandingView
 
 
 # from https://github.com/Grollicus/unittest_patterns/blob/master/unittest_patterns/__init__.py
-class Pattern(object):
+class Pattern:
     def __req__(self, lhs):
         return self.__eq__(lhs)
 
@@ -66,7 +66,7 @@ class Pattern(object):
 
 # from https://github.com/Grollicus/unittest_patterns/blob/master/unittest_patterns/__init__.py
 class Any(Pattern):
-    """ Equals everything """
+    """Equals everything"""
 
     def __eq__(self, rhs):
         return True
@@ -200,7 +200,7 @@ class ViewsTestBase(TestCase):
 
     def assertSetsMessage(self, request, msg: str, level=messages.SUCCESS):
         msgs = list(messages.get_messages(request.wsgi_request))
-        self.assertEqual(len(msgs), 1, "Expected exactly one Message")
+        self.assertEqual(len(msgs), 1, 'Expected exactly one Message')
         self.assertEqual(str(msgs[0].message), msg)
         self.assertEqual(msgs[0].level, level)
 
@@ -218,20 +218,35 @@ class ViewsTest(ViewsTestBase):
         tag_hidden.save()
 
         event = Event(
-            conference=self.conf, slug='event1', assembly=assembly, name='Event1_1', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45),
+            conference=self.conf,
+            slug='event1',
+            assembly=assembly,
+            name='Event1_1',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
             kind=Event.Kind.OFFICIAL,
         )
         event.save()
         suggested_event = Event(
-            conference=self.conf, slug='suggested', assembly=assembly, name='Event1_2', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 1, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45),
+            conference=self.conf,
+            slug='suggested',
+            assembly=assembly,
+            name='Event1_2',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 1, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
             kind=Event.Kind.OFFICIAL,
         )
         suggested_event.save()
         suggested_event2 = Event(
-            conference=self.conf, slug='suggested2', assembly=assembly, name='Event1_3', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 2, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45),
+            conference=self.conf,
+            slug='suggested2',
+            assembly=assembly,
+            name='Event1_3',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 2, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
             kind=Event.Kind.OFFICIAL,
         )
         suggested_event2.save()
@@ -280,13 +295,21 @@ class ViewsTest(ViewsTestBase):
         tag.save()
 
         event1 = Event(
-            conference=self.conf, assembly=assembly1, name='Event1_1', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly1,
+            name='Event1_1',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event1.save()
         event2 = Event(
-            conference=self.conf, assembly=assembly1, name='Event1_2', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 1, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly1,
+            name='Event1_2',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 1, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event2.save()
 
@@ -319,13 +342,21 @@ class ViewsTest(ViewsTestBase):
         tag_item = TagItem(tag=tag, target_type=ContentType.objects.get_for_model(Assembly), target_id=assembly.pk)
         tag_item.save()
         event1 = Event(
-            conference=self.conf, assembly=assembly, name='Event1_1', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event1_1',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event1.save()
         event2 = Event(
-            conference=self.conf, assembly=assembly, name='Event1_2', is_public=False,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 1, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event1_2',
+            is_public=False,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 1, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event2.save()
         AssemblyLikeCount(assembly1=assembly, assembly2=assembly, likes=10, like_ratio=1).save()
@@ -390,23 +421,51 @@ class ViewsTest(ViewsTestBase):
         room_public.save()
 
         event = Event(
-            conference=self.conf, assembly=assembly, room=room, slug='Event1_1', name='Event1_1', is_public=True, kind=Event.Kind.ASSEMBLY,
-            schedule_start=datetime(2020, 1, 2, 0, 45, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            room=room,
+            slug='Event1_1',
+            name='Event1_1',
+            is_public=True,
+            kind=Event.Kind.ASSEMBLY,
+            schedule_start=datetime(2020, 1, 2, 0, 45, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event.save()
         event2 = Event(
-            conference=self.conf, assembly=assembly, room=room, slug='Event1_2', name='Event1_2', is_public=True, kind=Event.Kind.OFFICIAL,
-            schedule_start=datetime(2020, 1, 2, 0, 50, 1, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            room=room,
+            slug='Event1_2',
+            name='Event1_2',
+            is_public=True,
+            kind=Event.Kind.OFFICIAL,
+            schedule_start=datetime(2020, 1, 2, 0, 50, 1, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event2.save()
         event3 = Event(
-            conference=self.conf, assembly=assembly, room=room_public, slug='Event1_3', name='Event1_3', is_public=True, kind=Event.Kind.OFFICIAL,
-            schedule_start=datetime(2020, 1, 2, 0, 55, 1, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            room=room_public,
+            slug='Event1_3',
+            name='Event1_3',
+            is_public=True,
+            kind=Event.Kind.OFFICIAL,
+            schedule_start=datetime(2020, 1, 2, 0, 55, 1, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event3.save()
         event4 = Event(
-            conference=self.conf, assembly=assembly, room=room, slug='Event1_4', name='Event1_4', is_public=False, kind=Event.Kind.OFFICIAL,
-            schedule_start=datetime(2020, 1, 2, 1, 0, 1, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            room=room,
+            slug='Event1_4',
+            name='Event1_4',
+            is_public=False,
+            kind=Event.Kind.OFFICIAL,
+            schedule_start=datetime(2020, 1, 2, 1, 0, 1, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event4.save()
 
@@ -748,9 +807,9 @@ class ViewsTest(ViewsTestBase):
         self.assertEqual(sp_de.revisions.count(), 1)
 
         # Preview
-        resp = self.client.post(reverse('plainui:static_page_edit', kwargs={'page_slug': sp.slug}), {
-                'preview': 'true', 'title': 'New Title Preview', 'body': 'New Body Preview'
-            })
+        resp = self.client.post(
+            reverse('plainui:static_page_edit', kwargs={'page_slug': sp.slug}), {'preview': 'true', 'title': 'New Title Preview', 'body': 'New Body Preview'}
+        )
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['page'], sp)
         self.assertEqual(resp.context_data['page_slug'], sp.slug)
@@ -851,8 +910,9 @@ class ViewsTest(ViewsTestBase):
         self.assertEqual(spr.author, self.user)
 
         with override_settings(STATIC_PAGE_LOCALIZED_BY_DEFAULT=True):
-            resp = self.client.post(reverse('plainui:static_page_edit', kwargs={'page_slug': 'test_new2'}),
-                                    {'title': 'Some New Page!', 'body': 'Some New Page!'})
+            resp = self.client.post(
+                reverse('plainui:static_page_edit', kwargs={'page_slug': 'test_new2'}), {'title': 'Some New Page!', 'body': 'Some New Page!'}
+            )
             self.assertRedirects(resp, reverse('plainui:static_page', kwargs={'page_slug': 'test_new2'}))
 
             sp_new2 = StaticPage.objects.get(slug='test_new2')
@@ -860,19 +920,18 @@ class ViewsTest(ViewsTestBase):
             self.assertEqual(sp_new2.language, 'en')
 
         # page links to nonexistent page, that then gets created, after which the link should be updated to refrect that the link target now exists
-        resp = self.client.post(reverse('plainui:static_page_edit', kwargs={'page_slug': 'test_linking'}), {
-            'title': 'Linking to other Pages test!',
-            'body': '[[test_linking_second_page]]'
-        })
+        resp = self.client.post(
+            reverse('plainui:static_page_edit', kwargs={'page_slug': 'test_linking'}),
+            {'title': 'Linking to other Pages test!', 'body': '[[test_linking_second_page]]'},
+        )
         self.assertRedirects(resp, reverse('plainui:static_page', kwargs={'page_slug': 'test_linking'}))
         self.assertSetsMessage(resp, 'Created Wiki Page', level=messages.SUCCESS)
         sp_new = StaticPage.objects.get(slug='test_linking')
         self.assertIn('internal-nonexist', sp_new.body_html)
         self.assertEqual(MarkdownMeta.objects.filter(src_type=ContentType.objects.get_for_model(StaticPage), src_object_id=sp_new.pk).count(), 1)
-        resp = self.client.post(reverse('plainui:static_page_edit', kwargs={'page_slug': 'test_linking_second_page'}), {
-            'title': 'Linked to test!',
-            'body': 'asdf'
-        })
+        resp = self.client.post(
+            reverse('plainui:static_page_edit', kwargs={'page_slug': 'test_linking_second_page'}), {'title': 'Linked to test!', 'body': 'asdf'}
+        )
         sp_new.refresh_from_db()
         self.assertNotIn('internal-nonexist', sp_new.body_html)
         self.assertIn('internal', sp_new.body_html)
@@ -990,8 +1049,12 @@ class ViewsTest(ViewsTestBase):
         assembly = Assembly(conference=self.conf, slug='assembly1', name='asdf', state_assembly=Assembly.State.PLACED)
         assembly.save()
         event = Event(
-            conference=self.conf, assembly=assembly, name='asdf', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='asdf',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event.save()
         tag = ConferenceTag(conference=self.conf, slug='aSdf', is_public=True)
@@ -1005,31 +1068,40 @@ class ViewsTest(ViewsTestBase):
         revision.set_public()
 
         self.assertNeedsLogin(reverse('plainui:search'), data={'q': 'asdf'}, post=True)
-        resp = self.client.post(reverse('plainui:search'), {
-            'q': 'asdf'
-        })
+        resp = self.client.post(reverse('plainui:search'), {'q': 'asdf'})
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['search_query'], 'asdf')
-        self.assertEqual(sorted(resp.context_data['search_results'], key=lambda r: r['type']), [
-            {'type': 'Assembly', 'item': assembly},
-            {'type': 'ConferenceTag', 'item': tag},
-            {'type': 'ConferenceTrack', 'item': track},
-            {'type': 'Event', 'item': event},
-            {'type': 'StaticPage', 'item': page},
-        ])
+        self.assertEqual(
+            sorted(resp.context_data['search_results'], key=lambda r: r['type']),
+            [
+                {'type': 'Assembly', 'item': assembly},
+                {'type': 'ConferenceTag', 'item': tag},
+                {'type': 'ConferenceTrack', 'item': track},
+                {'type': 'Event', 'item': event},
+                {'type': 'StaticPage', 'item': page},
+            ],
+        )
 
     @override_locale('en')
     def test_ProfileView_get(self):
         assembly = Assembly(conference=self.conf, slug='assembly1', name='Assembly1', state_assembly=Assembly.State.PLACED)
         assembly.save()
         event = Event(
-            conference=self.conf, assembly=assembly, name='Event1_1', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event1_1',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event.save()
         event2 = Event(
-            conference=self.conf, assembly=assembly, name='Event1_2', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 1, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event1_2',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 1, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event2.save()
         badge = Badge(conference=self.conf, name='badge-name', issuing_assembly=assembly)
@@ -1075,18 +1147,25 @@ class ViewsTest(ViewsTestBase):
         assembly = Assembly(conference=self.conf, slug='assembly1', name='Assembly1', state_assembly=Assembly.State.PLACED)
         assembly.save()
         event = Event(
-            conference=self.conf, assembly=assembly, name='Event1_1', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event1_1',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event.save()
 
         self.assertNeedsLogin(reverse('plainui:userprofile'), post=True, check_user=True)
-        resp = self.client.post(reverse('plainui:userprofile'), {
-            'description_de': 'new_description de',
-            'description_en': 'new_description en',
-            'pronouns': 'they',
-            'timezone': 'Europe/Berlin',
-        })
+        resp = self.client.post(
+            reverse('plainui:userprofile'),
+            {
+                'description_de': 'new_description de',
+                'description_en': 'new_description en',
+                'pronouns': 'they',
+                'timezone': 'Europe/Berlin',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:userprofile'))
         self.user.refresh_from_db()
         self.conference_member.refresh_from_db()
@@ -1107,29 +1186,38 @@ class ViewsTest(ViewsTestBase):
         self.assertNeedsLogin(reverse('plainui:modify_theme'), post=True)
 
         # setting theme works
-        resp = self.client.post(reverse('plainui:modify_theme'), {
-            'theme': PlatformUser.Theme.LIGHT,
-            'next': reverse('plainui:landing'),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_theme'),
+            {
+                'theme': PlatformUser.Theme.LIGHT,
+                'next': reverse('plainui:landing'),
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:landing'))
         self.user.refresh_from_db()
         self.assertEqual(self.user.theme, PlatformUser.Theme.LIGHT)
         self.assertEqual(self.client.session['theme'], PlatformUser.Theme.LIGHT)
 
         # invalid destination redirects to index
-        resp = self.client.post(reverse('plainui:modify_theme'), {
-            'theme': PlatformUser.Theme.LIGHT,
-            'next': 'https://www.google.de/',
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_theme'),
+            {
+                'theme': PlatformUser.Theme.LIGHT,
+                'next': 'https://www.google.de/',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:index'))
         self.user.refresh_from_db()
         self.assertEqual(self.user.theme, PlatformUser.Theme.LIGHT)
         self.assertEqual(self.client.session['theme'], PlatformUser.Theme.LIGHT)
 
         # invalid theme does not change anything, next defaults to index
-        resp = self.client.post(reverse('plainui:modify_theme'), {
-            'theme': 'doesnotexist',
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_theme'),
+            {
+                'theme': 'doesnotexist',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:index'))
         self.user.refresh_from_db()
         self.assertEqual(self.user.theme, PlatformUser.Theme.LIGHT)
@@ -1142,13 +1230,16 @@ class ViewsTest(ViewsTestBase):
         self.conf.start = datetime(2020, 12, 27, 0, 0, 0, tzinfo=UTC)
         self.conf.end = datetime(2020, 12, 31, 23, 59, 0, tzinfo=UTC)
         self.conf.save()
-        valid_token = jwt.encode({
-            'aud': self.conf.slug,
-            'exp': datetime.now(UTC) + timedelta(hours=1),
-            'iat': datetime.now(UTC) - timedelta(hours=1),
-            'iss': 'tickets.events.ccc.de',
-            'uid': 'blblbl123',
-        }, key='^v^')
+        valid_token = jwt.encode(
+            {
+                'aud': self.conf.slug,
+                'exp': datetime.now(UTC) + timedelta(hours=1),
+                'iat': datetime.now(UTC) - timedelta(hours=1),
+                'iss': 'tickets.events.ccc.de',
+                'uid': 'blblbl123',
+            },
+            key='^v^',
+        )
         invalid_token = (
             'fyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJzbHVnMSIsImV4cCI6MTYwODc2ODMwNywiaWF0IjoxNjA'
             '4NzYxMTA3LCJpc3MiOiJ0aWNrZXRzLmV2ZW50cy5jY2MuZGUiLCJ1aWQiOiJibGJsYmwxMjMifQ.QglU9dLHbIAOJ2e8hj_HSc6rZBpAcTrPbvY2H7HXnFw'
@@ -1211,13 +1302,16 @@ class ViewsTest(ViewsTestBase):
         self.user.set_password('test')
         self.user.save()
         ConferenceMember.objects.all().delete()
-        valid_token = jwt.encode({
-            'aud': self.conf.slug,
-            'exp': datetime.now(UTC) + timedelta(hours=1),
-            'iat': datetime.now(UTC) - timedelta(hours=1),
-            'iss': 'tickets.events.ccc.de',
-            'uid': 'blblbl123',
-        }, key='^v^')
+        valid_token = jwt.encode(
+            {
+                'aud': self.conf.slug,
+                'exp': datetime.now(UTC) + timedelta(hours=1),
+                'iat': datetime.now(UTC) - timedelta(hours=1),
+                'iss': 'tickets.events.ccc.de',
+                'uid': 'blblbl123',
+            },
+            key='^v^',
+        )
 
         # get-redirect
         resp = self.client.get(reverse('plainui:redeem_token_add_to_user'))
@@ -1228,11 +1322,7 @@ class ViewsTest(ViewsTestBase):
             'fyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJzbHVnMSIsImV4cCI6MTYwODc2ODMwNywiaWF0IjoxNjA'
             '4NzYxMTA3LCJpc3MiOiJ0aWNrZXRzLmV2ZW50cy5jY2MuZGUiLCJ1aWQiOiJibGJsYmwxMjMifQ.QglU9dLHbIAOJ2e8hj_HSc6rZBpAcTrPbvY2H7HXnFw'
         )
-        resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {
-            'token': invalid_token,
-            'username': self.user.username,
-            'password': 'test'
-        })
+        resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {'token': invalid_token, 'username': self.user.username, 'password': 'test'})
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['step'], 'user')
         self.assertEqual(list(resp.context_data['form_add'].non_field_errors()), ['Could not verify ticket. (Invalid Ticket)'])
@@ -1241,11 +1331,7 @@ class ViewsTest(ViewsTestBase):
         # conference already over
         self.conf.end = datetime(2020, 12, 28, 0, 0, 0, tzinfo=UTC)
         self.conf.save()
-        resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {
-            'token': valid_token,
-            'username': self.user.username,
-            'password': 'test'
-        })
+        resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {'token': valid_token, 'username': self.user.username, 'password': 'test'})
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['step'], 'user')
         self.assertEqual(list(resp.context_data['form_add'].non_field_errors()), ['The Conference is over!'])
@@ -1254,11 +1340,7 @@ class ViewsTest(ViewsTestBase):
         self.conf.save()
 
         # submit valid token and join conference
-        resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {
-            'token': valid_token,
-            'username': self.user.username,
-            'password': 'test'
-        })
+        resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {'token': valid_token, 'username': self.user.username, 'password': 'test'})
         self.assertRedirects(resp, reverse('plainui:index'))
         self.assertTrue(ConferenceMemberTicket.objects.filter(conference=self.conf, user=self.user, ident='blblbl123').exists())
         self.assertTrue(ConferenceMember.objects.filter(conference=self.conf, user=self.user).exists())
@@ -1266,11 +1348,7 @@ class ViewsTest(ViewsTestBase):
         # user already has a ConferenceMember but no ticket -> add ticket but don't create second ConferenceMember
         ConferenceMemberTicket.objects.all().delete()
         self.assertEqual(ConferenceMember.objects.filter(conference=self.conf, user=self.user).count(), 1)
-        resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {
-            'token': valid_token,
-            'username': self.user.username,
-            'password': 'test'
-        })
+        resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {'token': valid_token, 'username': self.user.username, 'password': 'test'})
         self.assertRedirects(resp, reverse('plainui:index'))
         self.assertTrue(ConferenceMemberTicket.objects.filter(conference=self.conf, user=self.user, ident='blblbl123').exists())
         self.assertEqual(ConferenceMember.objects.filter(conference=self.conf, user=self.user).count(), 1)
@@ -1280,20 +1358,12 @@ class ViewsTest(ViewsTestBase):
             for i in range(5):
                 ConferenceMemberTicket.objects.all().delete()
                 ConferenceMember.objects.all().delete()
-                resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {
-                    'token': valid_token,
-                    'username': self.user.username,
-                    'password': 'test'
-                })
+                resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {'token': valid_token, 'username': self.user.username, 'password': 'test'})
                 self.assertRedirects(resp, reverse('plainui:index'))
 
             ConferenceMemberTicket.objects.all().delete()
             ConferenceMember.objects.all().delete()
-            resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {
-                'token': valid_token,
-                'username': self.user.username,
-                'password': 'test'
-            })
+            resp = self.client.post(reverse('plainui:redeem_token_add_to_user'), {'token': valid_token, 'username': self.user.username, 'password': 'test'})
             self.assertIsInstance(resp, HttpResponseRateLimited)
 
     @override_settings(LANGUAGE_CODE='en', PRETIX_SECRET_KEY='^v^', PRETIX_ISSUER='tickets.events.ccc.de', AUTH_PASSWORD_VALIDATORS=[])
@@ -1308,37 +1378,34 @@ class ViewsTest(ViewsTestBase):
             'fyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJzbHVnMSIsImV4cCI6MTYwODc2ODMwNywiaWF0IjoxNjA'
             '4NzYxMTA3LCJpc3MiOiJ0aWNrZXRzLmV2ZW50cy5jY2MuZGUiLCJ1aWQiOiJibGJsYmwxMjMifQ.QglU9dLHbIAOJ2e8hj_HSc6rZBpAcTrPbvY2H7HXnFw'
         )
-        valid_token = jwt.encode({
-            'aud': self.conf.slug,
-            'exp': datetime.now(UTC) + timedelta(hours=1),
-            'iat': datetime.now(UTC) - timedelta(hours=1),
-            'iss': 'tickets.events.ccc.de',
-            'uid': 'blblbl123',
-        }, key='^v^')
+        valid_token = jwt.encode(
+            {
+                'aud': self.conf.slug,
+                'exp': datetime.now(UTC) + timedelta(hours=1),
+                'iat': datetime.now(UTC) - timedelta(hours=1),
+                'iss': 'tickets.events.ccc.de',
+                'uid': 'blblbl123',
+            },
+            key='^v^',
+        )
 
         # get-redirect
         resp = self.client.get(reverse('plainui:redeem_token_create_user'))
         self.assertRedirects(resp, reverse('plainui:redeem_token'))
 
         # invalid token
-        resp = self.client.post(reverse('plainui:redeem_token_create_user'), {
-            'token': invalid_token,
-            'username': 'testuser2',
-            'password1': 'test',
-            'password2': 'test'
-        })
+        resp = self.client.post(
+            reverse('plainui:redeem_token_create_user'), {'token': invalid_token, 'username': 'testuser2', 'password1': 'test', 'password2': 'test'}
+        )
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['step'], 'user')
         self.assertTrue(resp.context_data['form_add'])
         self.assertEqual(list(resp.context_data['form_create'].non_field_errors()), ['Could not verify ticket. (Invalid Ticket)'])
 
         # submit valid token but username exists already
-        resp = self.client.post(reverse('plainui:redeem_token_create_user'), {
-            'token': valid_token,
-            'username': self.user.username,
-            'password1': 'test',
-            'password2': 'test'
-        })
+        resp = self.client.post(
+            reverse('plainui:redeem_token_create_user'), {'token': valid_token, 'username': self.user.username, 'password1': 'test', 'password2': 'test'}
+        )
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['step'], 'user')
         self.assertTrue(resp.context_data['form_add'])
@@ -1347,12 +1414,9 @@ class ViewsTest(ViewsTestBase):
         # valid token but conference already over
         self.conf.end = datetime(2020, 12, 28, 0, 0, 0, tzinfo=UTC)
         self.conf.save()
-        resp = self.client.post(reverse('plainui:redeem_token_create_user'), {
-            'token': valid_token,
-            'username': self.user.username,
-            'password1': 'test',
-            'password2': 'test'
-        })
+        resp = self.client.post(
+            reverse('plainui:redeem_token_create_user'), {'token': valid_token, 'username': self.user.username, 'password1': 'test', 'password2': 'test'}
+        )
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['step'], 'user')
         self.assertTrue(resp.context_data['form_add'])
@@ -1361,12 +1425,9 @@ class ViewsTest(ViewsTestBase):
         self.conf.save()
 
         # submit valid token and join conference
-        resp = self.client.post(reverse('plainui:redeem_token_create_user'), {
-            'token': valid_token,
-            'username': 'testuser2',
-            'password1': 'test',
-            'password2': 'test'
-        })
+        resp = self.client.post(
+            reverse('plainui:redeem_token_create_user'), {'token': valid_token, 'username': 'testuser2', 'password1': 'test', 'password2': 'test'}
+        )
         self.assertRedirects(resp, reverse('plainui:index'))
         user = PlatformUser.objects.get(username='testuser2')
         self.assertTrue(ConferenceMemberTicket.objects.filter(conference=self.conf, user=user, ident='blblbl123').exists())
@@ -1384,13 +1445,16 @@ class ViewsTest(ViewsTestBase):
             'fyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJzbHVnMSIsImV4cCI6MTYwODc2ODMwNywiaWF0IjoxNjA'
             '4NzYxMTA3LCJpc3MiOiJ0aWNrZXRzLmV2ZW50cy5jY2MuZGUiLCJ1aWQiOiJibGJsYmwxMjMifQ.QglU9dLHbIAOJ2e8hj_HSc6rZBpAcTrPbvY2H7HXnFw'
         )
-        valid_token = jwt.encode({
-            'aud': self.conf.slug,
-            'exp': datetime.now(UTC) + timedelta(hours=1),
-            'iat': datetime.now(UTC) - timedelta(hours=1),
-            'iss': 'tickets.events.ccc.de',
-            'uid': 'blblbl123',
-        }, key='^v^')
+        valid_token = jwt.encode(
+            {
+                'aud': self.conf.slug,
+                'exp': datetime.now(UTC) + timedelta(hours=1),
+                'iat': datetime.now(UTC) - timedelta(hours=1),
+                'iss': 'tickets.events.ccc.de',
+                'uid': 'blblbl123',
+            },
+            key='^v^',
+        )
 
         self.assertNeedsLogin(reverse('plainui:redeem_token_loggedin'), check_conference_member=False, check_user=True)
         resp = self.client.get(reverse('plainui:redeem_token_loggedin'))
@@ -1399,25 +1463,19 @@ class ViewsTest(ViewsTestBase):
         # invalid token
         self.conf.end = datetime(2020, 12, 28, 0, 0, 0, tzinfo=UTC)
         self.conf.save()
-        resp = self.client.post(reverse('plainui:redeem_token_loggedin'), {
-            'token': invalid_token
-        })
+        resp = self.client.post(reverse('plainui:redeem_token_loggedin'), {'token': invalid_token})
         self.assertRedirects(resp, reverse('plainui:redeem_token'))
         self.assertSetsMessage(resp, 'The Conference is over!', level=messages.ERROR)
         self.conf.end = datetime(2020, 12, 31, 23, 59, 0, tzinfo=UTC)
         self.conf.save()
 
         # valid token but conference over
-        resp = self.client.post(reverse('plainui:redeem_token_loggedin'), {
-            'token': invalid_token
-        })
+        resp = self.client.post(reverse('plainui:redeem_token_loggedin'), {'token': invalid_token})
         self.assertRedirects(resp, reverse('plainui:redeem_token'))
         self.assertSetsMessage(resp, 'Could not verify ticket. (Invalid Ticket)', level=messages.ERROR)
 
         # valid token
-        resp = self.client.post(reverse('plainui:redeem_token_loggedin'), {
-            'token': valid_token
-        })
+        resp = self.client.post(reverse('plainui:redeem_token_loggedin'), {'token': valid_token})
         self.assertTrue(ConferenceMemberTicket.objects.filter(conference=self.conf, user=self.user, ident='blblbl123').exists())
         self.assertTrue(ConferenceMember.objects.filter(conference=self.conf, user=self.user).exists())
         self.assertRedirects(resp, reverse('plainui:index'))
@@ -1444,42 +1502,54 @@ class ViewsTest(ViewsTestBase):
             'fyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJzbHVnMSIsImV4cCI6MTYwODc2ODMwNywiaWF0IjoxNjA'
             '4NzYxMTA3LCJpc3MiOiJ0aWNrZXRzLmV2ZW50cy5jY2MuZGUiLCJ1aWQiOiJibGJsYmwxMjMifQ.QglU9dLHbIAOJ2e8hj_HSc6rZBpAcTrPbvY2H7HXnFw'
         )
-        resp = self.client.post(reverse('plainui:token_password_reset'), {
-            'jwt': invalid_token,
-            'username': self.user.username,
-            'new_password1': 'new',
-            'new_password2': 'new',
-        })
+        resp = self.client.post(
+            reverse('plainui:token_password_reset'),
+            {
+                'jwt': invalid_token,
+                'username': self.user.username,
+                'new_password1': 'new',
+                'new_password2': 'new',
+            },
+        )
         self.assertTrue(resp.context_data['form'].errors)
         self.user.refresh_from_db()
         self.assertFalse(self.user.check_password('new'))
 
-        resp = self.client.post(reverse('plainui:token_password_reset'), {
-            'jwt': valid_token,
-            'username': 'doesnotexist',
-            'new_password1': 'new',
-            'new_password2': 'new',
-        })
+        resp = self.client.post(
+            reverse('plainui:token_password_reset'),
+            {
+                'jwt': valid_token,
+                'username': 'doesnotexist',
+                'new_password1': 'new',
+                'new_password2': 'new',
+            },
+        )
         self.assertTrue(resp.context_data['form'].errors)
         self.user.refresh_from_db()
         self.assertFalse(self.user.check_password('new'))
 
-        resp = self.client.post(reverse('plainui:token_password_reset'), {
-            'jwt': valid_token,
-            'username': self.user.username,
-            'new_password1': 'new',
-            'new_password2': 'new2',
-        })
+        resp = self.client.post(
+            reverse('plainui:token_password_reset'),
+            {
+                'jwt': valid_token,
+                'username': self.user.username,
+                'new_password1': 'new',
+                'new_password2': 'new2',
+            },
+        )
         self.assertTrue(resp.context_data['form'].errors)
         self.user.refresh_from_db()
         self.assertFalse(self.user.check_password('new'))
 
-        resp = self.client.post(reverse('plainui:token_password_reset'), {
-            'jwt': valid_token,
-            'username': self.user.username,
-            'new_password1': 'new',
-            'new_password2': 'new',
-        })
+        resp = self.client.post(
+            reverse('plainui:token_password_reset'),
+            {
+                'jwt': valid_token,
+                'username': self.user.username,
+                'new_password1': 'new',
+                'new_password2': 'new',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:login'))
         self.user.refresh_from_db()
         self.assertTrue(self.user.check_password('new'))
@@ -1498,22 +1568,33 @@ class ViewsTest(ViewsTestBase):
         assembly2 = Assembly(conference=self.conf, slug='assembly2', name='Assembly2', state_assembly=Assembly.State.PLACED)
         assembly2.save()
         event1 = Event(
-            conference=self.conf, assembly=assembly1, name='Event1_1', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly1,
+            name='Event1_1',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event1.save()
         event2 = Event(
-            conference=self.conf, assembly=assembly1, name='Event1_2', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 1, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly1,
+            name='Event1_2',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 1, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event2.save()
 
         self.assertNeedsLogin(reverse('plainui:modify_favorites'), post=True, check_user=True)
-        resp = self.client.post(reverse('plainui:modify_favorites'), {
-            'mode': 'add',
-            'type': 'assembly',
-            'id': str(assembly1.pk),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_favorites'),
+            {
+                'mode': 'add',
+                'type': 'assembly',
+                'id': str(assembly1.pk),
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:userprofile'))
         self.assertTrue(self.user.favorite_assemblies.filter(pk=assembly1.pk).exists())
         self.assertEqual(self.client.session['fav_a'], [str(assembly1.pk)])
@@ -1522,23 +1603,29 @@ class ViewsTest(ViewsTestBase):
         self.assertEqual(lc.assembly2, assembly1)
         self.assertEqual(lc.likes, 1)
 
-        resp = self.client.post(reverse('plainui:modify_favorites'), {
-            'mode': 'add',
-            'type': 'assembly',
-            'id': str(assembly2.pk),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_favorites'),
+            {
+                'mode': 'add',
+                'type': 'assembly',
+                'id': str(assembly2.pk),
+            },
+        )
         self.assertEqual(AssemblyLikeCount.objects.count(), 4)
         self.assertEqual(AssemblyLikeCount.objects.get(assembly1=assembly1, assembly2=assembly1).likes, 1)
         self.assertEqual(AssemblyLikeCount.objects.get(assembly1=assembly2, assembly2=assembly2).likes, 1)
         self.assertEqual(AssemblyLikeCount.objects.get(assembly1=assembly1, assembly2=assembly2).likes, 1)
         self.assertEqual(AssemblyLikeCount.objects.get(assembly1=assembly2, assembly2=assembly1).likes, 1)
 
-        resp = self.client.post(reverse('plainui:modify_favorites'), {
-            'mode': 'remove',
-            'type': 'assembly',
-            'id': str(assembly1.pk),
-            'next': reverse('plainui:index'),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_favorites'),
+            {
+                'mode': 'remove',
+                'type': 'assembly',
+                'id': str(assembly1.pk),
+                'next': reverse('plainui:index'),
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:index'))
         self.assertFalse(self.user.favorite_assemblies.filter(pk=assembly1.pk).exists())
         self.assertEqual(self.client.session['fav_a'], [str(assembly2.pk)])
@@ -1547,12 +1634,15 @@ class ViewsTest(ViewsTestBase):
         self.assertEqual(AssemblyLikeCount.objects.get(assembly1=assembly1, assembly2=assembly2).likes, 0)
         self.assertEqual(AssemblyLikeCount.objects.get(assembly1=assembly2, assembly2=assembly1).likes, 0)
 
-        resp = self.client.post(reverse('plainui:modify_favorites'), {
-            'mode': 'add',
-            'type': 'event',
-            'id': str(event1.pk),
-            'next': reverse('plainui:index'),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_favorites'),
+            {
+                'mode': 'add',
+                'type': 'event',
+                'id': str(event1.pk),
+                'next': reverse('plainui:index'),
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:index'))
         self.assertTrue(self.user.favorite_events.filter(pk=event1.pk).exists())
         self.assertEqual(self.client.session['fav_e'], [str(event1.pk)])
@@ -1561,23 +1651,29 @@ class ViewsTest(ViewsTestBase):
         self.assertEqual(lc.event2, event1)
         self.assertEqual(lc.likes, 1)
 
-        resp = self.client.post(reverse('plainui:modify_favorites'), {
-            'mode': 'add',
-            'type': 'event',
-            'id': str(event2.pk),
-            'next': reverse('plainui:index'),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_favorites'),
+            {
+                'mode': 'add',
+                'type': 'event',
+                'id': str(event2.pk),
+                'next': reverse('plainui:index'),
+            },
+        )
         self.assertEqual(EventLikeCount.objects.count(), 4)
         self.assertEqual(EventLikeCount.objects.get(event1=event1, event2=event1).likes, 1)
         self.assertEqual(EventLikeCount.objects.get(event1=event2, event2=event2).likes, 1)
         self.assertEqual(EventLikeCount.objects.get(event1=event1, event2=event2).likes, 1)
         self.assertEqual(EventLikeCount.objects.get(event1=event2, event2=event1).likes, 1)
 
-        resp = self.client.post(reverse('plainui:modify_favorites'), {
-            'mode': 'remove',
-            'type': 'event',
-            'id': str(event1.pk),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_favorites'),
+            {
+                'mode': 'remove',
+                'type': 'event',
+                'id': str(event1.pk),
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:userprofile'))
         self.assertFalse(self.user.favorite_events.filter(pk=event1.pk).exists())
         self.assertEqual(self.client.session['fav_e'], [str(event2.pk)])
@@ -1596,16 +1692,22 @@ class ViewsTest(ViewsTestBase):
     @override_locale('en')
     def test_ModifyLanguageView_post(self):
         self.assertNeedsLogin(reverse('plainui:modify_language'), post=True)
-        resp = self.client.post(reverse('plainui:modify_language'), {
-            'language': 'en',
-            'next': reverse('plainui:landing'),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_language'),
+            {
+                'language': 'en',
+                'next': reverse('plainui:landing'),
+            },
+        )
         self.assertRedirects(resp, translate_url(reverse('plainui:landing'), 'en'))
         self.assertEqual(self.client.cookies['language_cookie'].value, 'en')
 
-        resp = self.client.post(reverse('plainui:modify_language'), {
-            'language': 'de',
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_language'),
+            {
+                'language': 'de',
+            },
+        )
         self.assertRedirects(resp, translate_url(reverse('plainui:index'), 'de'))
         self.assertEqual(self.client.cookies['language_cookie'].value, 'de')
 
@@ -1620,25 +1722,35 @@ class ViewsTest(ViewsTestBase):
         assembly = Assembly(conference=self.conf, slug='assembly1', name='Assembly1', state_assembly=Assembly.State.PLACED)
         assembly.save()
         event = Event(
-            conference=self.conf, assembly=assembly, name='Event1_1', is_public=True,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event1_1',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event.save()
 
         self.assertNeedsLogin(reverse('plainui:modify_personal_calendar'), post=True, check_user=True)
-        resp = self.client.post(reverse('plainui:modify_personal_calendar'), {
-            'mode': 'add',
-            'id': str(event.pk),
-            'next': reverse('plainui:index'),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_personal_calendar'),
+            {
+                'mode': 'add',
+                'id': str(event.pk),
+                'next': reverse('plainui:index'),
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:index'))
         self.assertTrue(self.user.calendar_events.filter(pk=event.pk).exists())
         self.assertEqual(self.client.session['sch_e'], [str(event.pk)])
 
-        resp = self.client.post(reverse('plainui:modify_personal_calendar'), {
-            'mode': 'remove',
-            'id': str(event.pk),
-        })
+        resp = self.client.post(
+            reverse('plainui:modify_personal_calendar'),
+            {
+                'mode': 'remove',
+                'id': str(event.pk),
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:userprofile'))
         self.assertFalse(self.user.calendar_events.filter(pk=event.pk).exists())
         self.assertEqual(self.client.session['sch_e'], [])
@@ -1694,12 +1806,15 @@ class ViewsTest(ViewsTestBase):
         user2.save()
 
         self.assertNeedsLogin(reverse('plainui:personal_message_send'), post=True, check_user=True)
-        resp = self.client.post(reverse('plainui:personal_message_send'), {
-            'in_reply_to': '',
-            'recipient': 'testuser2',
-            'subject': 'Subj',
-            'body': 'Body',
-        })
+        resp = self.client.post(
+            reverse('plainui:personal_message_send'),
+            {
+                'in_reply_to': '',
+                'recipient': 'testuser2',
+                'subject': 'Subj',
+                'body': 'Body',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:personal_message'))
         new_dm = DirectMessage.objects.get()
         self.assertEqual(new_dm.conference, self.conf)
@@ -1711,12 +1826,15 @@ class ViewsTest(ViewsTestBase):
 
         received_dm = DirectMessage(conference=self.conf, sender=user2, recipient=self.user)
         received_dm.save()
-        resp = self.client.post(reverse('plainui:personal_message_send'), {
-            'in_reply_to': str(received_dm.pk),
-            'recipient': 'testuser2',
-            'subject': 'Subj2',
-            'body': 'Body2',
-        })
+        resp = self.client.post(
+            reverse('plainui:personal_message_send'),
+            {
+                'in_reply_to': str(received_dm.pk),
+                'recipient': 'testuser2',
+                'subject': 'Subj2',
+                'body': 'Body2',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:personal_message'))
         new_dm = DirectMessage.objects.get(in_reply_to=received_dm)
         self.assertEqual(new_dm.conference, self.conf)
@@ -1729,12 +1847,15 @@ class ViewsTest(ViewsTestBase):
         self.assertTrue(received_dm.has_responded)
         self.assertSetsMessage(resp, 'Message sent.')
 
-        resp = self.client.post(reverse('plainui:personal_message_send'), {
-            'in_reply_to': '',
-            'recipient': 'Unknown User',
-            'subject': '',
-            'body': '',
-        })
+        resp = self.client.post(
+            reverse('plainui:personal_message_send'),
+            {
+                'in_reply_to': '',
+                'recipient': 'Unknown User',
+                'subject': '',
+                'body': '',
+            },
+        )
         form = resp.context_data['form']
         self.assertEqual(form.errors['recipient'], ['Unknown User!'])
         self.assertEqual(form.errors['subject'], ['This field is required.'])
@@ -1743,12 +1864,15 @@ class ViewsTest(ViewsTestBase):
         DirectMessage.objects.all().delete()
         self.user.shadow_banned = True
         self.user.save()
-        resp = self.client.post(reverse('plainui:personal_message_send'), {
-            'in_reply_to': '',
-            'recipient': 'testuser2',
-            'subject': 'subject',
-            'body': 'body',
-        })
+        resp = self.client.post(
+            reverse('plainui:personal_message_send'),
+            {
+                'in_reply_to': '',
+                'recipient': 'testuser2',
+                'subject': 'subject',
+                'body': 'body',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:personal_message'))
         new_dm = DirectMessage.objects.get(subject='subject')
         self.user.refresh_from_db()
@@ -1759,20 +1883,26 @@ class ViewsTest(ViewsTestBase):
         self.user.save()
         with override_settings(RATELIMIT_ENABLE=True):
             for i in range(3):
-                resp = self.client.post(reverse('plainui:personal_message_send'), {
-                    'in_reply_to': '',
-                    'recipient': 'testuser2',
-                    'subject': 'subject' + str(i),
-                    'body': 'body',
-                })
+                resp = self.client.post(
+                    reverse('plainui:personal_message_send'),
+                    {
+                        'in_reply_to': '',
+                        'recipient': 'testuser2',
+                        'subject': 'subject' + str(i),
+                        'body': 'body',
+                    },
+                )
                 self.assertRedirects(resp, reverse('plainui:personal_message'))
 
-            resp = self.client.post(reverse('plainui:personal_message_send'), {
-                'in_reply_to': '',
-                'recipient': 'testuser2',
-                'subject': 'subject-ratelimited',
-                'body': 'body-ratelimited',
-            })
+            resp = self.client.post(
+                reverse('plainui:personal_message_send'),
+                {
+                    'in_reply_to': '',
+                    'recipient': 'testuser2',
+                    'subject': 'subject-ratelimited',
+                    'body': 'body-ratelimited',
+                },
+            )
             self.assertIsInstance(resp, HttpResponseForbidden)
 
     @override_locale('en')
@@ -1844,11 +1974,11 @@ class ViewsTest(ViewsTestBase):
         other_user = PlatformUser(username='other_user')
         other_user.save()
 
-        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, title="Title1", text="some text")
+        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, title='Title1', text='some text')
         e1.save()
-        e2 = BulletinBoardEntry(conference=self.conf, owner=self.user, is_public=False, title="Title2", text="other text")
+        e2 = BulletinBoardEntry(conference=self.conf, owner=self.user, is_public=False, title='Title2', text='other text')
         e2.save()
-        e3 = BulletinBoardEntry(conference=self.conf, owner=self.user, title="Title3", text="different text")
+        e3 = BulletinBoardEntry(conference=self.conf, owner=self.user, title='Title3', text='different text')
         e3.save()
 
         self.assertNeedsLogin(reverse('plainui:board'))
@@ -1862,9 +1992,9 @@ class ViewsTest(ViewsTestBase):
 
         self.user.shadow_banned = True
         self.user.save()
-        e4 = BulletinBoardEntry(conference=self.conf, owner=self.user, title="Title4", text="some text", hidden=True)
+        e4 = BulletinBoardEntry(conference=self.conf, owner=self.user, title='Title4', text='some text', hidden=True)
         e4.save()
-        e5 = BulletinBoardEntry(conference=self.conf, owner=other_user, title="Title5", text="some text", hidden=True)
+        e5 = BulletinBoardEntry(conference=self.conf, owner=other_user, title='Title5', text='some text', hidden=True)
         e5.save()
         resp = self.client.get(reverse('plainui:board'))
         self.assertEqual(list(resp.context_data['board']), [e4, e3, e1])
@@ -1873,9 +2003,9 @@ class ViewsTest(ViewsTestBase):
     def test_BoardPrivateView(self):
         user2 = PlatformUser(username='testuser2')
         user2.save()
-        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, title="Title1", text="some text", hidden=True)
+        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, title='Title1', text='some text', hidden=True)
         e1.save()
-        e2 = BulletinBoardEntry(conference=self.conf, owner=self.user, is_public=False, title="Title2", text="other text")
+        e2 = BulletinBoardEntry(conference=self.conf, owner=self.user, is_public=False, title='Title2', text='other text')
         e2.save()
         e_other_owner = BulletinBoardEntry(conference=self.conf, owner=user2)
         e_other_owner.save()
@@ -1888,7 +2018,7 @@ class ViewsTest(ViewsTestBase):
     @override_locale('en')
     @patch('modeltranslation.settings.AVAILABLE_LANGUAGES', ['en', 'de'])
     def test_BoardEntryEditView_get(self):
-        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, is_public=False, title="Title1", text_de="some de text", text_en="some en text")
+        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, is_public=False, title='Title1', text_de='some de text', text_en='some en text')
         e1.save()
 
         self.assertNeedsLogin(reverse('plainui:board_entry_edit', kwargs={'id': str(e1.pk)}), check_user=True)
@@ -1913,19 +2043,22 @@ class ViewsTest(ViewsTestBase):
     def test_BoardEntryEditView_post(self):
         user2 = PlatformUser(username='testuser2')
         user2.save()
-        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, title="Title1", text="some text")
+        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, title='Title1', text='some text')
         e1.save()
         e2 = BulletinBoardEntry(conference=self.conf, owner=user2)
         e2.save()
 
         # edit entry with checked is_public
         self.assertNeedsLogin(reverse('plainui:board_entry_edit', kwargs={'id': str(e1.pk)}), post=True, check_user=True)
-        resp = self.client.post(reverse('plainui:board_entry_edit', kwargs={'id': str(e1.pk)}), {
-            'title': 'Edit Title',
-            'text_de': 'blblblblbl',
-            'text_en': 'blblblbl EN',
-            'is_public': 'on',
-        })
+        resp = self.client.post(
+            reverse('plainui:board_entry_edit', kwargs={'id': str(e1.pk)}),
+            {
+                'title': 'Edit Title',
+                'text_de': 'blblblblbl',
+                'text_en': 'blblblbl EN',
+                'is_public': 'on',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:board_entry_edit', kwargs={'id': str(e1.pk)}))
         e1.refresh_from_db()
         self.assertEqual(e1.is_public, True)
@@ -1936,30 +2069,39 @@ class ViewsTest(ViewsTestBase):
         self.assertSetsMessage(resp, 'Bulletin Board Entry updated.')
 
         # unchecked is_public
-        resp = self.client.post(reverse('plainui:board_entry_edit', kwargs={'id': str(e1.pk)}), {
-            'title': 'Edit Title',
-            'text_de': 'blblblblbl',
-            'text_en': 'blblblblbl ENL',
-        })
+        resp = self.client.post(
+            reverse('plainui:board_entry_edit', kwargs={'id': str(e1.pk)}),
+            {
+                'title': 'Edit Title',
+                'text_de': 'blblblblbl',
+                'text_en': 'blblblblbl ENL',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:board_entry_edit', kwargs={'id': str(e1.pk)}))
         e1.refresh_from_db()
         self.assertEqual(e1.is_public, False)
 
         # can't edit other users entries
-        resp = self.client.post(reverse('plainui:board_entry_edit', kwargs={'id': str(e2.pk)}), {
-            'title': 'Edit Title',
-            'text_de': 'blblblblbl',
-            'text_en': 'blblblblbl e',
-        })
+        resp = self.client.post(
+            reverse('plainui:board_entry_edit', kwargs={'id': str(e2.pk)}),
+            {
+                'title': 'Edit Title',
+                'text_de': 'blblblblbl',
+                'text_en': 'blblblblbl e',
+            },
+        )
         self.assertEqual(resp.status_code, 404)
 
         # create new entry
         self.assertNeedsLogin(reverse('plainui:board_entry_new'), post=True, check_user=True)
-        resp = self.client.post(reverse('plainui:board_entry_new'), {
-            'title': 'New Entry',
-            'text_de': 'some texty text de',
-            'text_en': 'some texty text',
-        })
+        resp = self.client.post(
+            reverse('plainui:board_entry_new'),
+            {
+                'title': 'New Entry',
+                'text_de': 'some texty text de',
+                'text_en': 'some texty text',
+            },
+        )
         new_entry = BulletinBoardEntry.objects.exclude(pk__in=[e1.pk, e2.pk]).get()
         self.assertRedirects(resp, reverse('plainui:board_entry_edit', kwargs={'id': str(new_entry.pk)}))
         self.assertEqual(new_entry.conference, self.conf)
@@ -1974,11 +2116,14 @@ class ViewsTest(ViewsTestBase):
         BulletinBoardEntry.objects.all().delete()
         self.user.shadow_banned = True
         self.user.save()
-        resp = self.client.post(reverse('plainui:board_entry_new'), {
-            'title': 'New Entry',
-            'text_de': 'some texty text ed',
-            'text_en': 'some texty text ne',
-        })
+        resp = self.client.post(
+            reverse('plainui:board_entry_new'),
+            {
+                'title': 'New Entry',
+                'text_de': 'some texty text ed',
+                'text_en': 'some texty text ne',
+            },
+        )
         new_entry = BulletinBoardEntry.objects.get()
         self.assertRedirects(resp, reverse('plainui:board_entry_edit', kwargs={'id': str(new_entry.pk)}))
         self.assertEqual(new_entry.hidden, True)
@@ -1988,17 +2133,23 @@ class ViewsTest(ViewsTestBase):
         self.user.save()
         with override_settings(RATELIMIT_ENABLE=True):
             for i in range(3):
-                resp = self.client.post(reverse('plainui:board_entry_new'), {
-                    'title': 'New Entry' + str(i),
-                    'text': 'some texty text',
-                })
+                resp = self.client.post(
+                    reverse('plainui:board_entry_new'),
+                    {
+                        'title': 'New Entry' + str(i),
+                        'text': 'some texty text',
+                    },
+                )
                 new_entry = BulletinBoardEntry.objects.get(title='New Entry' + str(i))
                 self.assertRedirects(resp, reverse('plainui:board_entry_edit', kwargs={'id': str(new_entry.pk)}))
 
-            resp = self.client.post(reverse('plainui:board_entry_new'), {
+            resp = self.client.post(
+                reverse('plainui:board_entry_new'),
+                {
                     'title': 'New Entry-Ratelimited',
                     'text': 'some texty text-Ratelimited',
-                })
+                },
+            )
             self.assertIsInstance(resp, HttpResponseRateLimited)
 
     @override_locale('en')
@@ -2012,7 +2163,7 @@ class ViewsTest(ViewsTestBase):
     def test_BoardEntryDeleteView_post(self):
         user2 = PlatformUser(username='testuser2')
         user2.save()
-        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, title="Title1", text="some text")
+        e1 = BulletinBoardEntry(conference=self.conf, owner=self.user, title='Title1', text='some text')
         e1.save()
         e2 = BulletinBoardEntry(conference=self.conf, owner=user2)
         e2.save()
@@ -2045,14 +2196,26 @@ class ViewsTest(ViewsTestBase):
         track = ConferenceTrack(conference=self.conf, is_public=True, name='Track1')
         track.save()
         event1 = Event(
-            conference=self.conf, assembly=assembly, track=track, name='Event1_1', is_public=True, room=room,
-            schedule_start=datetime(2020, 1, 2, 0, 15, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            track=track,
+            name='Event1_1',
+            is_public=True,
+            room=room,
+            schedule_start=datetime(2020, 1, 2, 0, 15, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event1.save()
         EventParticipant(is_public=True, participant=self.user, event=event1, role=EventParticipant.Role.SPEAKER).save()
         event2 = Event(
-            conference=self.conf, assembly=assembly, track=track, name='Event1_2', is_public=True, room=room,
-            schedule_start=datetime(2020, 1, 2, 1, 15, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            track=track,
+            name='Event1_2',
+            is_public=True,
+            room=room,
+            schedule_start=datetime(2020, 1, 2, 1, 15, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event2.save()
 
@@ -2069,23 +2232,31 @@ class ViewsTest(ViewsTestBase):
         resp = self.client.get(reverse('plainui:fahrplan'), {'mode': 'calendar'})
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['mode'], 'calendar')
-        self.assertEqual(resp.context_data['events'], {
-            'rooms_with_events': [(room, [
-                {'type': 'space', 'minutes': 15.0},
-                {'type': 'event', 'event': event1, 'minutes': 45.0},
-                {'type': 'space', 'minutes': 15.0},
-                {'type': 'event', 'event': event2, 'minutes': 45.0}
-            ])],
-            'calendar_start': datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
-            'calendar_end': datetime(2020, 1, 2, 2, 0, 0, tzinfo=UTC),
-            'calendar_time_steps': [
-                {'newdate': True, 'ts': datetime(2020, 1, 2, 0, 0, tzinfo=UTC)},
-                {'newdate': False, 'ts': datetime(2020, 1, 2, 0, 30, tzinfo=UTC)},
-                {'newdate': False, 'ts': datetime(2020, 1, 2, 1, 0, tzinfo=UTC)},
-                {'newdate': False, 'ts': datetime(2020, 1, 2, 1, 30, tzinfo=UTC)},
-            ],
-            'calendar_step_minutes': 30,
-        })
+        self.assertEqual(
+            resp.context_data['events'],
+            {
+                'rooms_with_events': [
+                    (
+                        room,
+                        [
+                            {'type': 'space', 'minutes': 15.0},
+                            {'type': 'event', 'event': event1, 'minutes': 45.0},
+                            {'type': 'space', 'minutes': 15.0},
+                            {'type': 'event', 'event': event2, 'minutes': 45.0},
+                        ],
+                    )
+                ],
+                'calendar_start': datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+                'calendar_end': datetime(2020, 1, 2, 2, 0, 0, tzinfo=UTC),
+                'calendar_time_steps': [
+                    {'newdate': True, 'ts': datetime(2020, 1, 2, 0, 0, tzinfo=UTC)},
+                    {'newdate': False, 'ts': datetime(2020, 1, 2, 0, 30, tzinfo=UTC)},
+                    {'newdate': False, 'ts': datetime(2020, 1, 2, 1, 0, tzinfo=UTC)},
+                    {'newdate': False, 'ts': datetime(2020, 1, 2, 1, 30, tzinfo=UTC)},
+                ],
+                'calendar_step_minutes': 30,
+            },
+        )
 
         # some random requests that test building the filter content
         resp = self.client.get(reverse('plainui:fahrplan'), {'mode': 'calendar', 'show_day_filters': 'y'})
@@ -2120,20 +2291,35 @@ class ViewsTest(ViewsTestBase):
         room2 = Room(conference=self.conf, assembly=assembly, name='Some Other Room', is_public_fahrplan=False)
         room2.save()
         event = Event(
-            conference=self.conf, assembly=assembly, name='Event1_1', is_public=True, room=room,
-            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45),
+            conference=self.conf,
+            assembly=assembly,
+            name='Event1_1',
+            is_public=True,
+            room=room,
+            schedule_start=datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
             kind=Event.Kind.OFFICIAL,
         )
         event.save()
         event2 = Event(
-            conference=self.conf, assembly=assembly, name='Event1_2', is_public=False, room=room,
-            schedule_start=datetime(2020, 1, 2, 1, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45),
+            conference=self.conf,
+            assembly=assembly,
+            name='Event1_2',
+            is_public=False,
+            room=room,
+            schedule_start=datetime(2020, 1, 2, 1, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
             kind=Event.Kind.OFFICIAL,
         )
         event2.save()
         event3 = Event(
-            conference=self.conf, assembly=assembly, name='Event1_3', is_public=True, room=room2,
-            schedule_start=datetime(2020, 1, 2, 2, 0, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45),
+            conference=self.conf,
+            assembly=assembly,
+            name='Event1_3',
+            is_public=True,
+            room=room2,
+            schedule_start=datetime(2020, 1, 2, 2, 0, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
             kind=Event.Kind.ASSEMBLY,
         )
         event3.save()
@@ -2197,23 +2383,43 @@ class ViewsTest(ViewsTestBase):
         room = Room(conference=self.conf, assembly=assembly, name='Some Room')
         room.save()
         event_pre = Event(
-            conference=self.conf, assembly=assembly, name='Event_pre', is_public=True, room=room,
-            schedule_start=now - timedelta(hours=1), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event_pre',
+            is_public=True,
+            room=room,
+            schedule_start=now - timedelta(hours=1),
+            schedule_duration=timedelta(minutes=45),
         )
         event_pre.save()
         event_still_running = Event(
-            conference=self.conf, assembly=assembly, name='Event_still_running', is_public=True, room=room,
-            schedule_start=now - timedelta(minutes=30), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event_still_running',
+            is_public=True,
+            room=room,
+            schedule_start=now - timedelta(minutes=30),
+            schedule_duration=timedelta(minutes=45),
         )
         event_still_running.save()
         event_soon = Event(
-            conference=self.conf, assembly=assembly, name='Event_soon', is_public=True, room=room,
-            schedule_start=now + timedelta(minutes=15), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event_soon',
+            is_public=True,
+            room=room,
+            schedule_start=now + timedelta(minutes=15),
+            schedule_duration=timedelta(minutes=45),
         )
         event_soon.save()
         event_post = Event(
-            conference=self.conf, assembly=assembly, name='Event_post', is_public=True, room=room,
-            schedule_start=now + timedelta(minutes=35), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            name='Event_post',
+            is_public=True,
+            room=room,
+            schedule_start=now + timedelta(minutes=35),
+            schedule_duration=timedelta(minutes=45),
         )
         event_post.save()
 
@@ -2244,19 +2450,38 @@ class ViewsTest(ViewsTestBase):
         room_link2.save()
 
         event = Event(
-            conference=self.conf, assembly=assembly, room=room1, slug='Event1_1', name='Event1_1', is_public=True, kind=Event.Kind.ASSEMBLY,
-            schedule_start=datetime(2020, 1, 1, 0, 45, 0, tzinfo=UTC), schedule_duration=timedelta(minutes=45),
+            conference=self.conf,
+            assembly=assembly,
+            room=room1,
+            slug='Event1_1',
+            name='Event1_1',
+            is_public=True,
+            kind=Event.Kind.ASSEMBLY,
+            schedule_start=datetime(2020, 1, 1, 0, 45, 0, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
             additional_data={'persons': [{'public_name': 'Gottfried Fluffke'}, {'public_name': 'Ferdinand Federviech'}]},
         )
         event.save()
         event2 = Event(
-            conference=self.conf, assembly=assembly, room=room1, slug='Event1_2', name='Event1_2', is_public=True,
-            schedule_start=datetime(2020, 1, 1, 1, 50, 1, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            room=room1,
+            slug='Event1_2',
+            name='Event1_2',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 1, 1, 50, 1, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event2.save()
         event_upcoming = Event(
-            conference=self.conf, assembly=assembly, room=room2, slug='Event2_1', name='Event2_1', is_public=True,
-            schedule_start=datetime(2020, 1, 1, 1, 50, 1, tzinfo=UTC), schedule_duration=timedelta(minutes=45)
+            conference=self.conf,
+            assembly=assembly,
+            room=room2,
+            slug='Event2_1',
+            name='Event2_1',
+            is_public=True,
+            schedule_start=datetime(2020, 1, 1, 1, 50, 1, tzinfo=UTC),
+            schedule_duration=timedelta(minutes=45),
         )
         event_upcoming.save()
 
@@ -2265,16 +2490,26 @@ class ViewsTest(ViewsTestBase):
         self.assertNeedsLogin(reverse('plainui:channel_events'))
         resp = self.client.get(reverse('plainui:channel_events'))
         self.assertEqual(resp.context_data['conf'], self.conf)
-        self.assertEqual(resp.context_data['rooms_active'], [{
-            'room': room1,
-            'current_event': event,
-            'next_event': event2,
-        }])
-        self.assertEqual(resp.context_data['rooms_inactive'], [{
-            'room': room2,
-            'current_event': None,
-            'next_event': event_upcoming,
-        }])
+        self.assertEqual(
+            resp.context_data['rooms_active'],
+            [
+                {
+                    'room': room1,
+                    'current_event': event,
+                    'next_event': event2,
+                }
+            ],
+        )
+        self.assertEqual(
+            resp.context_data['rooms_inactive'],
+            [
+                {
+                    'room': room2,
+                    'current_event': None,
+                    'next_event': event_upcoming,
+                }
+            ],
+        )
 
     @override_locale('en')
     def test_WorldView(self):
@@ -2310,7 +2545,8 @@ class ViewsTest(ViewsTestBase):
     @override_locale('en')
     def _dereferrer_test(self, my_endpoint):
         from django.core.signing import loads
-        my_salt = '%sdereferrer' % (self.user.pk,)
+
+        my_salt = f'{self.user.pk}dereferrer'
         anonymous_salt = '000000dereferrer'
 
         # local links are always allowed
@@ -2426,6 +2662,7 @@ class ViewsTest(ViewsTestBase):
     @override_locale('en')
     def test_ReportContentView(self):
         from plainui.forms import REPORT_CATEGORIES
+
         category_id = iter(REPORT_CATEGORIES.keys()).__next__()
 
         user2 = PlatformUser(username='testuser2')
@@ -2436,132 +2673,154 @@ class ViewsTest(ViewsTestBase):
         bbe.save()
 
         self.assertNeedsLogin(reverse('plainui:report_content'), check_user=True)
-        resp = self.client.get(reverse('plainui:report_content'), {
-            'kind': 'url', 'kind_data': reverse('plainui:index')
-        })
+        resp = self.client.get(reverse('plainui:report_content'), {'kind': 'url', 'kind_data': reverse('plainui:index')})
         self.assertEqual(resp.context_data['conf'], self.conf)
         self.assertEqual(resp.context_data['form']['kind'].value(), 'url')
         self.assertEqual(resp.context_data['form']['kind_data'].value(), reverse('plainui:index'))
 
-        resp = self.client.post(reverse('plainui:report_content'), {
-            'kind': 'url',
-            'kind_data': reverse('plainui:index'),
-            'next': '',
-            'category': category_id,
-            'message': 'Some Message',
-            'message2': 'Some Resolution',
-        })
+        resp = self.client.post(
+            reverse('plainui:report_content'),
+            {
+                'kind': 'url',
+                'kind_data': reverse('plainui:index'),
+                'next': '',
+                'category': category_id,
+                'message': 'Some Message',
+                'message2': 'Some Resolution',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:index'))
         self.assertEqual(len(mail.outbox), 1)
         sent_mail = mail.outbox[0]
         self.assertEqual(sent_mail.to, [REPORT_CATEGORIES[category_id][2]])
-        self.assertEqual(sent_mail.subject, f"New {REPORT_CATEGORIES[category_id][1]} Report")
-        self.assertEqual(sent_mail.body, (
-            f"testuser ({self.user.id!s}) sent a new {REPORT_CATEGORIES[category_id][1]} Report.\n"
-            f"Profile of the Report Author: {reverse('plainui:user_by_uuid', kwargs={'uuid': str(self.user.uuid)})}\n"
-            f"\n"
-            f"Problem description:\n"
-            f"\n"
-            f"Some Message\n"
-            f"\n"
-            f"Solution description:\n"
-            f"\n"
-            f"Some Resolution\n"
-            f"\n"
-            f"Problematic content:\n"
-            f"\n"
-            f"{reverse('plainui:index')}"
-        ))
+        self.assertEqual(sent_mail.subject, f'New {REPORT_CATEGORIES[category_id][1]} Report')
+        self.assertEqual(
+            sent_mail.body,
+            (
+                f"testuser ({self.user.id!s}) sent a new {REPORT_CATEGORIES[category_id][1]} Report.\n"
+                f"Profile of the Report Author: {reverse('plainui:user_by_uuid', kwargs={'uuid': str(self.user.uuid)})}\n"
+                f"\n"
+                f"Problem description:\n"
+                f"\n"
+                f"Some Message\n"
+                f"\n"
+                f"Solution description:\n"
+                f"\n"
+                f"Some Resolution\n"
+                f"\n"
+                f"Problematic content:\n"
+                f"\n"
+                f"{reverse('plainui:index')}"
+            ),
+        )
         mail.outbox.clear()
 
-        resp = self.client.post(reverse('plainui:report_content'), {
-            'kind': 'pn',
-            'kind_data': str(dm_recv.pk),
-            'next': reverse('plainui:personal_message_show', kwargs={'msg_id': str(dm_recv.pk)}),
-            'category': category_id,
-            'message': 'Some Message',
-            'message2': 'Some Resolution',
-        })
+        resp = self.client.post(
+            reverse('plainui:report_content'),
+            {
+                'kind': 'pn',
+                'kind_data': str(dm_recv.pk),
+                'next': reverse('plainui:personal_message_show', kwargs={'msg_id': str(dm_recv.pk)}),
+                'category': category_id,
+                'message': 'Some Message',
+                'message2': 'Some Resolution',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:personal_message_show', kwargs={'msg_id': str(dm_recv.pk)}))
         self.assertEqual(len(mail.outbox), 1)
         sent_mail = mail.outbox[0]
         self.assertEqual(sent_mail.to, [REPORT_CATEGORIES[category_id][2]])
-        self.assertEqual(sent_mail.subject, f"New {REPORT_CATEGORIES[category_id][1]} Report")
-        self.assertEqual(sent_mail.body, (
-            f'testuser ({self.user.id!s}) sent a new {REPORT_CATEGORIES[category_id][1]} Report.\n'
-            f"Profile of the Report Author: {reverse('plainui:user_by_uuid', kwargs={'uuid': str(self.user.uuid)})}\n"
-            f'\n'
-            f'Problem description:\n'
-            f'\n'
-            f'Some Message\n'
-            f'\n'
-            f"Solution description:\n"
-            f"\n"
-            f"Some Resolution\n"
-            f"\n"
-            f'Problematic content:\n'
-            f'\n'
-            f'DM by testuser2 ({user2.id!s}) to testuser ({self.user.id!s}) at {localize(localtime(dm_recv.timestamp))} ({dm_recv.pk!s}).\n'
-            f'Subject: "sj1"\n'
-            f'Message: "bd1"'
-        ))
+        self.assertEqual(sent_mail.subject, f'New {REPORT_CATEGORIES[category_id][1]} Report')
+        self.assertEqual(
+            sent_mail.body,
+            (
+                f'testuser ({self.user.id!s}) sent a new {REPORT_CATEGORIES[category_id][1]} Report.\n'
+                f"Profile of the Report Author: {reverse('plainui:user_by_uuid', kwargs={'uuid': str(self.user.uuid)})}\n"
+                f'\n'
+                f'Problem description:\n'
+                f'\n'
+                f'Some Message\n'
+                f'\n'
+                f"Solution description:\n"
+                f"\n"
+                f"Some Resolution\n"
+                f"\n"
+                f'Problematic content:\n'
+                f'\n'
+                f'DM by testuser2 ({user2.id!s}) to testuser ({self.user.id!s}) at {localize(localtime(dm_recv.timestamp))} ({dm_recv.pk!s}).\n'
+                f'Subject: "sj1"\n'
+                f'Message: "bd1"'
+            ),
+        )
         mail.outbox.clear()
 
-        resp = self.client.post(reverse('plainui:report_content'), {
-            'kind': 'board',
-            'kind_data': str(bbe.pk),
-            'next': '',
-            'category': category_id,
-            'message': 'Other Message',
-            'message2': 'Other Resolution',
-        })
+        resp = self.client.post(
+            reverse('plainui:report_content'),
+            {
+                'kind': 'board',
+                'kind_data': str(bbe.pk),
+                'next': '',
+                'category': category_id,
+                'message': 'Other Message',
+                'message2': 'Other Resolution',
+            },
+        )
         self.assertRedirects(resp, reverse('plainui:index'))
         self.assertEqual(len(mail.outbox), 1)
         sent_mail = mail.outbox[0]
         self.assertEqual(sent_mail.to, [REPORT_CATEGORIES[category_id][2]])
-        self.assertEqual(sent_mail.subject, f"New {REPORT_CATEGORIES[category_id][1]} Report")
-        self.assertEqual(sent_mail.body, (
-            f'testuser ({self.user.id!s}) sent a new {REPORT_CATEGORIES[category_id][1]} Report.\n'
-            f"Profile of the Report Author: {reverse('plainui:user_by_uuid', kwargs={'uuid': str(self.user.uuid)})}\n"
-            f'\n'
-            f'Problem description:\n'
-            f'\n'
-            f'Other Message\n'
-            f'\n'
-            f"Solution description:\n"
-            f"\n"
-            f"Other Resolution\n"
-            f"\n"
-            f'Problematic content:\n'
-            f'\n'
-            f'Board Entry by testuser ({self.user.id!s}) at {localize(localtime(dm_recv.timestamp))} ({bbe.pk!s})\n'
-            f'Title: "board entry title"\n'
-            f'Text: "board entry text"'
-        ))
+        self.assertEqual(sent_mail.subject, f'New {REPORT_CATEGORIES[category_id][1]} Report')
+        self.assertEqual(
+            sent_mail.body,
+            (
+                f'testuser ({self.user.id!s}) sent a new {REPORT_CATEGORIES[category_id][1]} Report.\n'
+                f"Profile of the Report Author: {reverse('plainui:user_by_uuid', kwargs={'uuid': str(self.user.uuid)})}\n"
+                f'\n'
+                f'Problem description:\n'
+                f'\n'
+                f'Other Message\n'
+                f'\n'
+                f"Solution description:\n"
+                f"\n"
+                f"Other Resolution\n"
+                f"\n"
+                f'Problematic content:\n'
+                f'\n'
+                f'Board Entry by testuser ({self.user.id!s}) at {localize(localtime(dm_recv.timestamp))} ({bbe.pk!s})\n'
+                f'Title: "board entry title"\n'
+                f'Text: "board entry text"'
+            ),
+        )
         mail.outbox.clear()
 
         # testing the rate limiting: Will block after 1 allowed requests
         with override_settings(RATELIMIT_ENABLE=True):
-            resp = self.client.post(reverse('plainui:report_content'), {
-                'kind': 'board',
-                'kind_data': str(bbe.pk),
-                'next': '',
-                'category': category_id,
-                'message': 'Other Message',
-                'message2': 'Other Resolution',
-            })
+            resp = self.client.post(
+                reverse('plainui:report_content'),
+                {
+                    'kind': 'board',
+                    'kind_data': str(bbe.pk),
+                    'next': '',
+                    'category': category_id,
+                    'message': 'Other Message',
+                    'message2': 'Other Resolution',
+                },
+            )
             self.assertRedirects(resp, reverse('plainui:index'))
             self.assertEqual(len(mail.outbox), 1)
             mail.outbox.clear()
 
-            resp = self.client.post(reverse('plainui:report_content'), {
-                'kind': 'board',
-                'kind_data': str(bbe.pk),
-                'next': '',
-                'category': category_id,
-                'message': 'Other Message',
-                'message2': 'Other Resolution',
-            })
+            resp = self.client.post(
+                reverse('plainui:report_content'),
+                {
+                    'kind': 'board',
+                    'kind_data': str(bbe.pk),
+                    'next': '',
+                    'category': category_id,
+                    'message': 'Other Message',
+                    'message2': 'Other Resolution',
+                },
+            )
             self.assertIsInstance(resp, HttpResponseRateLimited)
             self.assertEqual(len(mail.outbox), 0)
 
@@ -2606,8 +2865,8 @@ class ViewsTest(ViewsTestBase):
         badge = Badge(conference=self.conf, name='badge-name', issuing_assembly=assembly)
         badge.save()
         badge_token = BadgeToken.objects.create(badge=badge)
-        resp = self.client.get(reverse('plainui:manage_badges'), {"redeem_token": badge_token.token})
-        self.assertRedirects(resp, reverse("plainui:manage_badges"))
+        resp = self.client.get(reverse('plainui:manage_badges'), {'redeem_token': badge_token.token})
+        self.assertRedirects(resp, reverse('plainui:manage_badges'))
         self.assertTrue(UserBadge.objects.filter(user=self.user, badge=badge).exists())
 
     @override_settings(RATELIMIT_ENABLE=False)
@@ -2617,8 +2876,8 @@ class ViewsTest(ViewsTestBase):
         badge = Badge(conference=self.conf, name='badge-name', issuing_assembly=assembly)
         badge.save()
         badge_token = BadgeToken.objects.create(badge=badge)
-        resp = self.client.post(reverse('plainui:manage_badges'), {"token": badge_token.token, 'purpose': 'redeem_token'})
-        self.assertRedirects(resp, reverse("plainui:manage_badges"))
+        resp = self.client.post(reverse('plainui:manage_badges'), {'token': badge_token.token, 'purpose': 'redeem_token'})
+        self.assertRedirects(resp, reverse('plainui:manage_badges'))
         self.assertTrue(UserBadge.objects.filter(user=self.user, badge=badge).exists())
 
     def test_badge_redeem_view_rate_limit_post(self):
@@ -2690,7 +2949,6 @@ class ViewsTest(ViewsTestBase):
 
 # @override_settings(PRETIX_SECRET_KEY=_GOOD_SECRET)
 class TestWorkadventurCompattibleUsernameField(TestCase):
-
     def setUp(self):
         class MyTestForm(forms.Form):
             username = WorkadventureCompatibleUsernameField(max_length=123)
@@ -2709,7 +2967,7 @@ class TestWorkadventurCompattibleUsernameField(TestCase):
 
 class TestConferenceDetection(TestCase):
     def setUp(self):
-        self.request = RequestFactory().get("/")
+        self.request = RequestFactory().get('/')
         self.view = LandingView()
 
     def test_no_conference_config(self):
@@ -2718,20 +2976,14 @@ class TestConferenceDetection(TestCase):
 
     @override_settings(PLAINUI_CONFERENCE=None)
     def test_valid_conference_config(self):
-        self.conf = Conference(
-            id=TEST_CONF_ID, name="conf_asdf", slug="slug1"
-        )
+        self.conf = Conference(id=TEST_CONF_ID, name='conf_asdf', slug='slug1')
         self.conf.save()
         self.view.dispatch(self.request)
 
     def setUpTwoDatabases(self):
-        self.conf = Conference(
-            id=TEST_CONF_ID, name="slug1", slug="slug1"
-        )
+        self.conf = Conference(id=TEST_CONF_ID, name='slug1', slug='slug1')
         self.conf.save()
-        self.conf2 = Conference(
-            id=TEST_CONF_ID_2, name="slug2", slug="slug2"
-        )
+        self.conf2 = Conference(id=TEST_CONF_ID_2, name='slug2', slug='slug2')
         self.conf2.save()
 
     @override_settings(PLAINUI_CONFERENCE=TEST_CONF_ID)
@@ -2751,7 +3003,5 @@ class TestConferenceDetection(TestCase):
     def test_invalid_conference_config_no_setting(self):
         self.setUpTwoDatabases()
 
-        with self.assertRaisesMessage(
-                ImproperlyConfigured,
-                expected_message="Multiple Conferences found, set PLAINUI_CONFERENCE in settings!"):
+        with self.assertRaisesMessage(ImproperlyConfigured, expected_message='Multiple Conferences found, set PLAINUI_CONFERENCE in settings!'):
             self.view.dispatch(self.request)
diff --git a/src/plainui/urls.py b/src/plainui/urls.py
index 7061a3ba6..99d091126 100644
--- a/src/plainui/urls.py
+++ b/src/plainui/urls.py
@@ -18,7 +18,11 @@ urlpatterns = [
     path('login', views.LoginView.as_view(), name='login'),
     path('signup', views.RegistrationView.as_view(), name='signup'),
     path('signup/done', views.RegistrationDoneView.as_view(), name='signup_done'),
-    re_path(r'^signup/activate/(?P<uid_b64>[0-9A-Za-z_\-]+)/(?P<channel_id>\d+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,40})/$', views.RegistrationActivationView.as_view(), name='signup_activate'),  # noqa: E501
+    re_path(
+        r'^signup/activate/(?P<uid_b64>[0-9A-Za-z_\-]+)/(?P<channel_id>\d+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,40})/$',
+        views.RegistrationActivationView.as_view(),
+        name='signup_activate',
+    ),  # noqa: E501
     path('password_reset', views.PasswordResetView.as_view(), name='password_reset'),
     path('password_reset_sent', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
     path('password_change', views.PasswordChangeView.as_view(), name='password_change'),
@@ -33,7 +37,6 @@ urlpatterns = [
     path('redeem_token_create', views.RedeemTokenUserCreateView.as_view(), name='redeem_token_create_user'),
     path('redeem_token_loggedin', views.RedeemTokenLoggedIn.as_view(), name='redeem_token_loggedin'),
     path('token_password_reset', views.TokenPasswordResetView.as_view(), name='token_password_reset'),
-
     path('', views.LandingView.as_view(), name='landing'),
     path('board', views.BoardView.as_view(), name='board'),
     path('my_board', views.BoardPrivateView.as_view(), name='board_private'),
@@ -82,7 +85,6 @@ urlpatterns = [
     path('user-by-uuid/<uuid:uuid>/', views.UserByUuidView.as_view(), name='user_by_uuid'),
     path('wa_contact', views.WorkadventureContactPage.as_view(), name='wa_contact'),
     path('world_vcard/<uuid:wa_session_id>', views.WorkadventureVCard.as_view(), name='wa_vcard'),
-
     path('shibboleet', views.ShibboleetView.as_view(), name='shibboleet'),
 ]
 
diff --git a/src/plainui/utils.py b/src/plainui/utils.py
index 6a01bb4b6..25ee46962 100644
--- a/src/plainui/utils.py
+++ b/src/plainui/utils.py
@@ -25,19 +25,22 @@ def check_message_content(conf, request, text, kind, kind_data):
         request.user.shadow_banned = True
         request.user.save(update_fields=['shadow_banned'])
 
-        report_form = ReportForm(conf=conf, data={
-            'kind': kind,
-            'kind_data': str(kind_data),
-            'category': 'abuse',
-            'message': 'Autoban triggered',
-            'message2': '.',
-        })
+        report_form = ReportForm(
+            conf=conf,
+            data={
+                'kind': kind,
+                'kind_data': str(kind_data),
+                'category': 'abuse',
+                'message': 'Autoban triggered',
+                'message2': '.',
+            },
+        )
         report_form.is_valid()
 
         report_form.send_report_mail(request)
         return True
     except Exception:
-        raise Exception("Boom")  # don't allow leaking eg. ValidationErrors that might be handled in a View
+        raise Exception('Boom')  # don't allow leaking eg. ValidationErrors that might be handled in a View
 
 
 class StaticPageDiff(HtmlDiff):
@@ -45,16 +48,16 @@ class StaticPageDiff(HtmlDiff):
     def _format_line(self, side, flag, linenum, text):
         try:
             linenum = '%d' % linenum
-            id = ' id="%s%s"' % (self._prefix[side], linenum)
+            id = f' id="{self._prefix[side]}{linenum}"'
         except TypeError:
             # handle blank lines where linenum is '>' or ''
             id = ''
 
         # replace those things that would get confused with HTML symbols
-        text = text.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;")
+        text = text.replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;')
 
         # make space non-breakable so they don't get compressed or line wrapped
         text = text.replace(' ', '&nbsp;').rstrip()
 
         # vvv ---- removed `nowrap="nowrap"` --- vvv
-        return '<td class="diff_header"%s>%s</td><td class="diff_content">%s</td>' % (id, linenum, text)
+        return f'<td class="diff_header"{id}>{linenum}</td><td class="diff_content">{text}</td>'
diff --git a/src/plainui/views.py b/src/plainui/views.py
index 01907b4e2..4c264cb97 100644
--- a/src/plainui/views.py
+++ b/src/plainui/views.py
@@ -1,56 +1,93 @@
-from datetime import datetime, time, timedelta, UTC
 import logging
 import threading
-
-from typing import ClassVar, List, Optional, Dict, Any
-from urllib.parse import urlparse, ParseResult, unquote
+from datetime import UTC, datetime, time, timedelta
+from typing import Any, ClassVar, Dict, List, Optional
+from urllib.parse import ParseResult, unquote, urlparse
 from uuid import UUID  # noqa:F401 as flake8 does not properly support PEP526 yet -.-
-from modeltranslation.fields import build_localized_fieldname
-from modeltranslation.settings import AVAILABLE_LANGUAGES
+
+from django_ratelimit.decorators import ratelimit
 
 from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth import login
-from django.contrib.auth import views as auth_views, mixins as auth_mixins
+from django.contrib.auth import mixins as auth_mixins
+from django.contrib.auth import views as auth_views
 from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import PermissionDenied, ValidationError, ImproperlyConfigured
 from django.core import signing
-from django.core.exceptions import SuspiciousOperation
-from django.db import transaction, connection
-from django.db.models import Q, F, Prefetch
-from django.http import Http404, HttpRequest, HttpResponseRedirect, HttpResponseNotFound, HttpResponse, HttpResponseForbidden
-from django.shortcuts import redirect, get_object_or_404
-from django.urls import reverse, reverse_lazy, translate_url, NoReverseMatch
+from django.core.exceptions import ImproperlyConfigured, PermissionDenied, SuspiciousOperation, ValidationError
+from django.db import connection, transaction
+from django.db.models import F, Prefetch, Q
+from django.http import Http404, HttpRequest, HttpResponse, HttpResponseForbidden, HttpResponseNotFound, HttpResponseRedirect
+from django.shortcuts import get_object_or_404, redirect
+from django.template.response import TemplateResponse
+from django.urls import NoReverseMatch, reverse, reverse_lazy, translate_url
 from django.utils import timezone
 from django.utils.decorators import method_decorator
 from django.utils.http import url_has_allowed_host_and_scheme
 from django.utils.timezone import localtime
-from django.utils.translation import gettext, check_for_language, get_language
+from django.utils.translation import check_for_language, get_language, gettext
+from django.utils.translation import gettext_lazy as _
 from django.views.decorators.debug import sensitive_post_parameters
-from django.views.generic.base import View, TemplateView
+from django.views.generic.base import TemplateView, View
 from django.views.generic.edit import FormView, UpdateView
-from django.template.response import TemplateResponse
-from django.utils.translation import gettext_lazy as _
-from django_ratelimit.decorators import ratelimit
+from modeltranslation.fields import build_localized_fieldname
+from modeltranslation.settings import AVAILABLE_LANGUAGES
 
 from core import integrations
 from core.integrations import WorkAdventureIntegration
-from core.models import Assembly, AssemblyLikeCount, Badge, Conference, ConferenceMember, ConferenceMemberTicket, ConferenceTag, DereferrerStats, UserContact, \
-    ConferenceTrack, DirectMessage, Event, EventAttachment, EventLikeCount, EventParticipant, PlatformUser, Room, RoomLink, StaticPage, StaticPageRevision, \
-    TagItem, UserBadge, BadgeToken,  UserDereferrerAllowlist, WorkadventureSession, BulletinBoardEntry, MetaNavItem
+from core.markdown import refresh_linking_markdown, render_markdown
+from core.models import (
+    Assembly,
+    AssemblyLikeCount,
+    Badge,
+    BadgeToken,
+    BulletinBoardEntry,
+    Conference,
+    ConferenceMember,
+    ConferenceMemberTicket,
+    ConferenceTag,
+    ConferenceTrack,
+    DereferrerStats,
+    DirectMessage,
+    Event,
+    EventAttachment,
+    EventLikeCount,
+    EventParticipant,
+    MetaNavItem,
+    PlatformUser,
+    Room,
+    RoomLink,
+    StaticPage,
+    StaticPageRevision,
+    TagItem,
+    UserBadge,
+    UserContact,
+    UserDereferrerAllowlist,
+    WorkadventureSession,
+)
+from core.models.badges import TokenInvalid
 from core.models.ticket import TicketValidationError
 from core.search import search
 from core.sso import SSO
-from core.markdown import render_markdown, refresh_linking_markdown
 from core.utils import url_in_allowlist
 from core.views import BaseLoginView, BasePasswordResetConfirmView, BasePasswordResetView, BaseRegistrationActivationView, BaseRegistrationView
-from core.models.badges import TokenInvalid
 
 from plainui.utils import StaticPageDiff
 
-from .forms import BulletinBoardEntryForm, ExampleForm, InputTokenForm, NewDirectMessageForm, \
-    ProfileEditForm, ProfileDescriptionEditForm, RedeemTokenAddToUserForm, RedeemTokenUserCreateForm, ReportForm, \
-    RedeemBadgeForm, StaticPageBodyForm, TokenPasswortResetForm
+from .forms import (
+    BulletinBoardEntryForm,
+    ExampleForm,
+    InputTokenForm,
+    NewDirectMessageForm,
+    ProfileDescriptionEditForm,
+    ProfileEditForm,
+    RedeemBadgeForm,
+    RedeemTokenAddToUserForm,
+    RedeemTokenUserCreateForm,
+    ReportForm,
+    StaticPageBodyForm,
+    TokenPasswortResetForm,
+)
 
 
 def _session_refresh_favorite_assemblies(session, user) -> List[str]:
@@ -162,27 +199,26 @@ class ConferenceRequiredMixin(auth_mixins.AccessMixin):
                         ConferenceRequiredMixin._conf = conf = conferences.get()
                         self._conf_timeout = now + timedelta(300)
                     except Conference.MultipleObjectsReturned:
-                        raise ImproperlyConfigured("Multiple Conferences found, set PLAINUI_CONFERENCE in settings!")
+                        raise ImproperlyConfigured('Multiple Conferences found, set PLAINUI_CONFERENCE in settings!')
                     except Conference.DoesNotExist:
                         if Conference.objects.exists():
-                            raise ImproperlyConfigured("PLAINUI_CONFERENCE from settings not found in database!")
+                            raise ImproperlyConfigured('PLAINUI_CONFERENCE from settings not found in database!')
                         else:
-                            raise ImproperlyConfigured("No conference found in database!")
+                            raise ImproperlyConfigured('No conference found in database!')
                     except ValidationError:
-                        raise ImproperlyConfigured("Invalid value for PLAINUI_CONFERENCE in settings!")
+                        raise ImproperlyConfigured('Invalid value for PLAINUI_CONFERENCE in settings!')
                 finally:
                     self._conf_lock.release()
 
         self.conf = conf
 
-        if (self.require_login and conf.require_login
-            or conf.require_ticket and self.require_conference_member
-            or self.require_user) \
-                and not request.user.is_authenticated:
+        if (
+            self.require_login and conf.require_login or conf.require_ticket and self.require_conference_member or self.require_user
+        ) and not request.user.is_authenticated:
             return self.handle_no_permission()
 
         if conf.require_ticket and self.require_conference_member and not self.has_ticket(request):
-            messages.error(request, gettext("Please activate your Ticket to access this conference!"))
+            messages.error(request, gettext('Please activate your Ticket to access this conference!'))
             return redirect(reverse('plainui:redeem_token'))
 
         if self._test_cork:
@@ -243,13 +279,10 @@ class EventView(ConferenceRequiredMixin, TemplateView):
         context['running_state'] = running_state
 
         context['attachments'] = EventAttachment.objects.filter(
-            event=event,
-            visibility__in=[EventAttachment.Visibility.PUBLIC, EventAttachment.Visibility.CONFERENCE]
+            event=event, visibility__in=[EventAttachment.Visibility.PUBLIC, EventAttachment.Visibility.CONFERENCE]
         )
 
-        context['report_info'] = {
-            'url': reverse('plainui:event', kwargs={'event_slug': event.slug})
-        }
+        context['report_info'] = {'url': reverse('plainui:event', kwargs={'event_slug': event.slug})}
         context['share_url'] = reverse('plainui:event', kwargs={'event_slug': event.slug})
         context['schedule_info'] = {'id': event.id, 'is': str(event.id) in personal_calendar}
         context['fav_info'] = {'type': 'event', 'id': event.id, 'is': str(event.id) in favorites}
@@ -268,9 +301,11 @@ class TagView(ConferenceRequiredMixin, TemplateView):
         context['tag'] = tag
 
         # TODO other types. What should we link here?
-        context['events'] = Event.objects.conference_accessible(self.conf).filter(
-            id__in=TagItem.objects.filter(tag=tag, target_type=ContentType.objects.get_for_model(Event)).values_list('target_id')
-        ).filter(schedule_start__isnull=False, schedule_end__isnull=False)
+        context['events'] = (
+            Event.objects.conference_accessible(self.conf)
+            .filter(id__in=TagItem.objects.filter(tag=tag, target_type=ContentType.objects.get_for_model(Event)).values_list('target_id'))
+            .filter(schedule_start__isnull=False, schedule_end__isnull=False)
+        )
         context['my_favorite_events'] = _session_get_favorite_events(self.request.session, self.request.user)
         context['my_scheduled_events'] = _session_get_scheduled_events(self.request.session, self.request.user)
 
@@ -294,34 +329,32 @@ class SosList(ConferenceRequiredMixin, TemplateView):
         context['is_favorite_events'] = _session_get_favorite_events(self.request.session, self.request.user)
         context['is_scheduled_events'] = _session_get_scheduled_events(self.request.session, self.request.user)
 
-        context['report_info'] = {
-            'url': reverse('plainui:sos')
-        }
+        context['report_info'] = {'url': reverse('plainui:sos')}
         context['share_url'] = reverse('plainui:sos')
 
         return context
 
 
 class SosJoin(ConferenceRequiredMixin, View):
-    """ extra view to join BBB rooms from the Workadventure """
+    """extra view to join BBB rooms from the Workadventure"""
 
     require_user = True
 
     def get(self, request, event_slug):
         event = get_object_or_404(Event.objects.conference_accessible(self.conf), slug=event_slug)
         if not event.kind == Event.Kind.SELF_ORGANIZED:
-            raise Http404()
+            raise Http404
 
         try:
             join_url = integrations.BigBlueButton.join_room(event, self.request.user, not self.request.user.show_name)
             if not join_url:
-                error = gettext("Unspecified error, room is probably full?")
+                error = gettext('Unspecified error, room is probably full?')
             else:
                 return HttpResponseRedirect(join_url)
         except integrations.IntegrationError as e:
             error = str(e)
 
-        messages.error(request, gettext("Joining Failed: %s") % (error,))
+        messages.error(request, gettext('Joining Failed: %s') % (error,))
         return redirect(reverse('plainui:event', kwargs={'event_slug': event_slug}))
 
 
@@ -350,15 +383,11 @@ class AssemblyView(ConferenceRequiredMixin, TemplateView):
 
         suggestions = assembly.suggestions.select_related('assembly2')
         suggestions = suggestions.exclude(assembly2=assembly.pk).filter(assembly2__state_assembly__in=Assembly.PUBLIC_STATES).order_by('-like_ratio')[:5]
-        context['suggested'] = [
-            s.assembly2 for s in suggestions
-        ]
+        context['suggested'] = [s.assembly2 for s in suggestions]
 
         context['spokespeople'] = assembly.members.filter(is_representative=True, show_public=True).select_related('member')
 
-        context['report_info'] = {
-            'url': reverse('plainui:assembly', kwargs={'assembly_slug': assembly.slug})
-        }
+        context['report_info'] = {'url': reverse('plainui:assembly', kwargs={'assembly_slug': assembly.slug})}
         context['share_url'] = reverse('plainui:assembly', kwargs={'assembly_slug': assembly.slug})
         context['fav_info'] = {'type': 'assembly', 'id': assembly.id, 'is': str(assembly.id) in favorites}
 
@@ -372,16 +401,14 @@ class AssembliesView(ConferenceRequiredMixin, TemplateView):
         context = super().get_context_data(**kwargs)
         context['conf'] = self.conf
         context['events_upcoming'] = _event_filter(self.request.user, self.conf, upcoming=True)
-        # TODO: reecommended events. TODO: Implement suggestions based on liked assemblies
-        # context['events_recommended'] = _event_filter(self.request.user, self.conf, user_schedule_only=True)[0:4]
+        # TODO: recommended events.
+        # TODO: Implement suggestions based on liked assemblies
         context['is_favorite_events'] = _session_get_favorite_events(self.request.session, self.request.user)
         context['is_scheduled_events'] = _session_get_scheduled_events(self.request.session, self.request.user)
         context['assemblies'] = Assembly.objects.conference_accessible(self.conf).order_by('name')
         context['my_favorite_assemblies'] = _session_get_favorite_assemblies(self.request.session, self.request.user)
 
-        context['report_info'] = {
-            'url': reverse('plainui:assemblies')
-        }
+        context['report_info'] = {'url': reverse('plainui:assemblies')}
         context['share_url'] = reverse('plainui:assemblies')
 
         return context
@@ -402,9 +429,7 @@ class AssembliesAllView(ConferenceRequiredMixin, TemplateView):
             context['assemblies'] = context['assemblies'].filter(is_official=False)
         context['my_favorite_assemblies'] = _session_get_favorite_assemblies(self.request.session, self.request.user)
 
-        context['report_info'] = {
-            'url': reverse('plainui:assemblies_all')
-        }
+        context['report_info'] = {'url': reverse('plainui:assemblies_all')}
         context['share_url'] = reverse('plainui:assemblies_all')
 
         return context
@@ -419,15 +444,11 @@ class AssembliesEventsView(ConferenceRequiredMixin, TemplateView):
 
         context['events_upcoming'] = _event_filter(self.request.user, self.conf, upcoming=True)
         # not used atm
-        # assemblies = Assembly.objects.conference_accessible(self.conf)
-        # context['assemblies'] = assemblies
         context['events_from_assemblies'] = _event_filter(self.request.user, self.conf, kinds=[Event.Kind.ASSEMBLY], public_fahrplan=False)
         context['is_favorite_events'] = _session_get_favorite_events(self.request.session, self.request.user)
         context['is_scheduled_events'] = _session_get_scheduled_events(self.request.session, self.request.user)
 
-        context['report_info'] = {
-            'url': reverse('plainui:assemblies_events')
-        }
+        context['report_info'] = {'url': reverse('plainui:assemblies_events')}
         context['share_url'] = reverse('plainui:assemblies_events')
 
         return context
@@ -467,7 +488,7 @@ class StaticPageView(ConferenceRequiredMixin, TemplateView):
                 if not self.request.user.is_authenticated:
                     return self.handle_no_permission()
                 else:
-                    messages.error(self.request, gettext("You need an active Ticket to access this Page!"))
+                    messages.error(self.request, gettext('You need an active Ticket to access this Page!'))
                     return redirect(reverse('plainui:redeem_token'))
 
         return super().get(request, page_slug=page_slug, **kwargs)
@@ -511,13 +532,11 @@ class StaticPageView(ConferenceRequiredMixin, TemplateView):
                     sanitize_html=static_page.sanitize_html,
                 )
                 if can_edit:
-                    context["edit_url"] = reverse(
-                        "plainui:static_page_edit", kwargs={"page_slug": page_slug}
-                    ) + ("?rev=" + revision if revision else "")
+                    context['edit_url'] = reverse('plainui:static_page_edit', kwargs={'page_slug': page_slug}) + ('?rev=' + revision if revision else '')
                 else:
-                    context["edit_url"] = None
+                    context['edit_url'] = None
             except StaticPageRevision.DoesNotExist:
-                context["revision_not_found"] = True
+                context['revision_not_found'] = True
                 page_body = static_page.body_html
         else:
             page_body = static_page.body_html
@@ -552,16 +571,16 @@ class StaticPageEditView(ConferenceRequiredMixin, TemplateView):
 
         if not static_page:
             # the page does not exist and the user does not have the permission to create it
-            messages.error(request, gettext("You do not have the required permissions to create this page."))
+            messages.error(request, gettext('You do not have the required permissions to create this page.'))
             return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
         if static_page.privacy == StaticPage.Privacy.CONFERENCE and not self.has_ticket(request):
-            messages.error(request, gettext("You do not have the required permissions to edit this page."))
+            messages.error(request, gettext('You do not have the required permissions to edit this page.'))
             # TODO: after redeem, redirect to edit view for page_slug
             return redirect(reverse('plainui:redeem_token'))
 
         if static_page.privacy == StaticPage.Privacy.PERM and not self.request.user.has_conference_staffpermission(self.conf, 'static_pages'):
-            messages.error(request, gettext("You do not have the required permissions to edit this page."))
+            messages.error(request, gettext('You do not have the required permissions to edit this page.'))
             return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
         revision = request.GET.get('rev')
@@ -575,25 +594,31 @@ class StaticPageEditView(ConferenceRequiredMixin, TemplateView):
             page_title = static_page.title
             not_latest_revision = False
 
-        form = StaticPageBodyForm(initial={
-            'title': page_title,
-            'body': instance.body,
-        })
+        form = StaticPageBodyForm(
+            initial={
+                'title': page_title,
+                'body': instance.body,
+            }
+        )
 
         if not writeable:
             form.fields['title'].disabled = True
             form.fields['body'].disabled = True
 
-        return TemplateResponse(request, self.template_name, {
-            'page': static_page,
-            'conf': self.conf,
-            'page_slug': page_slug,
-            'static_page': static_page,
-            'writeable': writeable,
-            'not_latest_revision': not_latest_revision,
-            'revision': revision,
-            'form': form,
-        })
+        return TemplateResponse(
+            request,
+            self.template_name,
+            {
+                'page': static_page,
+                'conf': self.conf,
+                'page_slug': page_slug,
+                'static_page': static_page,
+                'writeable': writeable,
+                'not_latest_revision': not_latest_revision,
+                'revision': revision,
+                'form': form,
+            },
+        )
 
     @method_decorator(ratelimit(key='ip', rate='5/m', method=ratelimit.UNSAFE))
     @method_decorator(ratelimit(key='post:username', rate='5/m', method=ratelimit.UNSAFE))
@@ -623,16 +648,20 @@ class StaticPageEditView(ConferenceRequiredMixin, TemplateView):
             not_latest_revision = False
 
         if not form.is_valid():
-            return TemplateResponse(request, self.template_name, {
-                'page': static_page,
-                'conf': self.conf,
-                'page_slug': page_slug,
-                'static_page': static_page,
-                'writeable': True,  # otherwise, `static_page` would be `None` and we'd have raised `PermissionDenied()` earlier
-                'not_latest_revision': not_latest_revision,
-                'revision': revision,
-                'form': form,
-            })
+            return TemplateResponse(
+                request,
+                self.template_name,
+                {
+                    'page': static_page,
+                    'conf': self.conf,
+                    'page_slug': page_slug,
+                    'static_page': static_page,
+                    'writeable': True,  # otherwise, `static_page` would be `None` and we'd have raised `PermissionDenied` earlier
+                    'not_latest_revision': not_latest_revision,
+                    'revision': revision,
+                    'form': form,
+                },
+            )
 
         title = form.cleaned_data['title']
         body = form.cleaned_data['body']
@@ -645,18 +674,22 @@ class StaticPageEditView(ConferenceRequiredMixin, TemplateView):
                 sanitize_html=static_page.sanitize_html,
             )
 
-            return TemplateResponse(request, self.template_name, {
-                'page': static_page,
-                'preview_title': title,
-                'preview_body': preview_body,
-                'conf': self.conf,
-                'page_slug': page_slug,
-                'static_page': static_page,
-                'writeable': True,  # otherwise, `static_page` would be `None` and we'd error out early
-                'not_latest_revision': not_latest_revision,
-                'revision': revision,
-                'form': form,
-            })
+            return TemplateResponse(
+                request,
+                self.template_name,
+                {
+                    'page': static_page,
+                    'preview_title': title,
+                    'preview_body': preview_body,
+                    'conf': self.conf,
+                    'page_slug': page_slug,
+                    'static_page': static_page,
+                    'writeable': True,  # otherwise, `static_page` would be `None` and we'd error out early
+                    'not_latest_revision': not_latest_revision,
+                    'revision': revision,
+                    'form': form,
+                },
+            )
 
         if not page_exists:
             static_page.save()
@@ -667,9 +700,9 @@ class StaticPageEditView(ConferenceRequiredMixin, TemplateView):
         revision.save()
 
         if not page_exists:
-            messages.success(request, gettext("Created Static Page"))
+            messages.success(request, gettext('Created Static Page'))
         else:
-            messages.success(request, gettext("Updated Static Page"))
+            messages.success(request, gettext('Updated Static Page'))
         return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
 
@@ -678,8 +711,7 @@ class StaticPageHistoryView(ConferenceRequiredMixin, TemplateView):
     require_user = True
 
     def get(self, request, page_slug, **kwargs):
-        self.static_page = StaticPage.objects.conference_accessible(conference=self.conf, language=get_language())\
-            .filter(slug=page_slug).first()
+        self.static_page = StaticPage.objects.conference_accessible(conference=self.conf, language=get_language()).filter(slug=page_slug).first()
         if not self.static_page:
             return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
@@ -730,13 +762,13 @@ class StaticPageDiffView(ConferenceRequiredMixin, TemplateView):
         rev2_id = self.request.GET.get('rev2')
 
         if rev1_id is None or rev2_id is None:
-            raise SuspiciousOperation("Only one revision given, need two")
+            raise SuspiciousOperation('Only one revision given, need two')
 
         try:
             rev1 = self.static_page.revisions.get(revision=rev1_id)
             rev2 = self.static_page.revisions.get(revision=rev2_id)
         except StaticPageRevision.DoesNotExist:
-            raise Http404("One of the selected revisions was not found")
+            raise Http404('One of the selected revisions was not found')
 
         differ = StaticPageDiff(tabsize=4)
         diff = differ.make_table(rev1.body.splitlines(keepends=True), rev2.body.splitlines(keepends=True), context=True, numlines=3)
@@ -770,7 +802,7 @@ class StaticPageGlobalHistoryView(ConferenceRequiredMixin, TemplateView):
 
         context = super().get_context_data(**kwargs)
         context['conf'] = self.conf
-        context['history'] = history[PAGE_SIZE * page: PAGE_SIZE * page + PAGE_SIZE]
+        context['history'] = history[PAGE_SIZE * page : PAGE_SIZE * page + PAGE_SIZE]
         return context
 
 
@@ -791,7 +823,7 @@ class ProfileView(ConferenceRequiredMixin, UpdateView):
         form1 = super().get_form(form_class)
 
         form2_kwargs = super().get_form_kwargs()
-        if hasattr(self, "object"):
+        if hasattr(self, 'object'):
             cm = self.object.conferences.filter(conference=self.conf).first()
             if cm:
                 form2_kwargs['instance'] = cm
@@ -814,11 +846,19 @@ class ProfileView(ConferenceRequiredMixin, UpdateView):
         context['badges'] = UserBadge.objects.filter(user=user, accepted_by_user=True).select_related('badge')
         context['amount_badges_not_accepted'] = len(UserBadge.objects.filter(user=user, accepted_by_user=False).select_related('badge'))
         context['is_favorite_events'] = favorite_events = _session_get_favorite_events(self.request.session, self.request.user)
-        context['my_favorite_events'] = Event.objects.conference_accessible(conference=self.conf).filter(pk__in=favorite_events).filter(schedule_start__isnull=False, schedule_end__isnull=False)  # noqa:E501
+        context['my_favorite_events'] = (
+            Event.objects.conference_accessible(conference=self.conf)
+            .filter(pk__in=favorite_events)
+            .filter(schedule_start__isnull=False, schedule_end__isnull=False)
+        )  # noqa:E501
         context['is_favorite_assemblies'] = favorite_assemblies = _session_get_favorite_assemblies(self.request.session, self.request.user)
         context['my_favorite_assemblies'] = Assembly.objects.conference_accessible(conference=self.conf).filter(pk__in=favorite_assemblies)
         context['is_fahrplan_events'] = scheduled_events = _session_get_scheduled_events(self.request.session, self.request.user)
-        context['my_fahrplan_events'] = Event.objects.conference_accessible(conference=self.conf).filter(pk__in=scheduled_events).filter(schedule_start__isnull=False, schedule_end__isnull=False)  # noqa:E501
+        context['my_fahrplan_events'] = (
+            Event.objects.conference_accessible(conference=self.conf)
+            .filter(pk__in=scheduled_events)
+            .filter(schedule_start__isnull=False, schedule_end__isnull=False)
+        )  # noqa:E501
         context['dereferrer_allowlist'] = UserDereferrerAllowlist.objects.filter(user=self.request.user)
         context['redeem_badge_form'] = RedeemBadgeForm()
         return context
@@ -848,7 +888,7 @@ class ProfileView(ConferenceRequiredMixin, UpdateView):
 
         form2.save()
         self.request.session['theme'] = form1.instance.theme
-        messages.success(self.request, gettext("Updated Profile"))
+        messages.success(self.request, gettext('Updated Profile'))
 
         try:
             WorkAdventureIntegration.push_userinfo(conference=self.conf, user=self.request.user)
@@ -881,7 +921,7 @@ class ModifyThemeView(ConferenceRequiredMixin, View):
         return redirect(redirect_to)
 
 
-@method_decorator(ratelimit(group="redeem_badge", key='user', rate=settings.BADGE_RATE_LIMIT), name='dispatch')
+@method_decorator(ratelimit(group='redeem_badge', key='user', rate=settings.BADGE_RATE_LIMIT), name='dispatch')
 class RedeemBadgeView(ConferenceRequiredMixin, FormView):
     require_user = True
     template_name = 'plainui/manage_badges.html'
@@ -889,7 +929,7 @@ class RedeemBadgeView(ConferenceRequiredMixin, FormView):
     external_context = None
 
     def __init__(self, **kwargs: Any) -> None:
-        self.external_context = kwargs.pop("external_context", {})
+        self.external_context = kwargs.pop('external_context', {})
         super().__init__(**kwargs)
 
     def get_form_kwargs(self) -> dict[str, Any]:
@@ -913,7 +953,7 @@ class RedeemBadgeView(ConferenceRequiredMixin, FormView):
         context = super().get_context_data(**kwargs)
         context['conf'] = self.conf
         context.update(self.external_context)
-        context["redeem_badge_form"] = context.pop("form")
+        context['redeem_badge_form'] = context.pop('form')
         return context
 
     def get_success_url(self):
@@ -938,11 +978,11 @@ class ManageBadgeView(ConferenceRequiredMixin, TemplateView):
     template_name = 'plainui/manage_badges.html'
 
     def get_queryset(self):
-        return UserBadge.objects.filter(user=self.request.user).values_list("badge", flat=True)
+        return UserBadge.objects.filter(user=self.request.user).values_list('badge', flat=True)
 
     def get_context_data(self, *args, **kwargs):
         context = super().get_context_data(*args, **kwargs)
-        if hasattr(self, "conf"):
+        if hasattr(self, 'conf'):
             context['conf'] = self.conf
 
         user = self.request.user
@@ -959,7 +999,7 @@ class ManageBadgeView(ConferenceRequiredMixin, TemplateView):
         return resp
 
     def dispatch(self, request, *args, **kwargs):
-        if hasattr(request, "GET") and request.GET.get('redeem_token') or hasattr(request, "GET") and request.POST.get('purpose', None) == 'redeem_token':
+        if hasattr(request, 'GET') and request.GET.get('redeem_token') or hasattr(request, 'GET') and request.POST.get('purpose', None) == 'redeem_token':
             return RedeemBadgeView.as_view(external_context=self.get_context_data())(request, *args, **kwargs)
         return super().dispatch(request, *args, **kwargs)
 
@@ -972,7 +1012,7 @@ class ManageBadgeView(ConferenceRequiredMixin, TemplateView):
             userbadge = UserBadge.objects.filter(user=user, badge=badge).first()
 
             if not userbadge:
-                raise Exception("The badge is not found.")
+                raise Exception('The badge is not found.')
             if not visibility:
                 # Change acceptanceState
                 if userbadge.accepted_by_user:
@@ -1118,7 +1158,7 @@ class RedeemTokenLoggedIn(ConferenceRequiredMixin, View):
         try:
             with transaction.atomic():
                 if self.conf.end <= self.now:
-                    raise TicketValidationError(gettext("The Conference is over!"))
+                    raise TicketValidationError(gettext('The Conference is over!'))
                 ConferenceMemberTicket.redeem_pretix_ticket(self.conf, request.user, token)
                 ConferenceMember.objects.update_or_create(conference=self.conf, user=request.user, defaults={'has_ticket': True})
                 return HttpResponseRedirect(reverse('plainui:index'))
@@ -1151,39 +1191,50 @@ class ModifyFavoritesView(ConferenceRequiredMixin, View):
                         EventLikeCount.objects.filter(event1=fav_id, event2__in=user.favorite_events.exclude(pk=fav_id)).update(likes=F('likes') - 1)
                         EventLikeCount.objects.filter(event2=fav_id, event1__in=user.favorite_events.all()).update(likes=F('likes') - 1)
                         request.user.favorite_events.remove(fav_id)
-            else:
-                if request.POST['type'] == 'assembly':
-                    assembly = Assembly.objects.conference_accessible(conference=self.conf).get(pk=fav_id)
-                    if not request.user.favorite_assemblies.filter(pk=fav_id).exists():
-                        request.user.favorite_assemblies.add(assembly)
-                        with connection.cursor() as cursor:
-                            cursor.execute("""
+            elif request.POST['type'] == 'assembly':
+                assembly = Assembly.objects.conference_accessible(conference=self.conf).get(pk=fav_id)
+                if not request.user.favorite_assemblies.filter(pk=fav_id).exists():
+                    request.user.favorite_assemblies.add(assembly)
+                    with connection.cursor() as cursor:
+                        cursor.execute(
+                            """
+                        INSERT INTO core_assemblylikecount AS lc (assembly1_id, assembly2_id, likes, like_ratio)
+                            SELECT %s, e.assembly_id, 1, 0 FROM core_assembly_favorited_by AS e WHERE e.assembly_id <> %s AND e.platformuser_id = %s
+                            ON CONFLICT (assembly1_id, assembly2_id) DO UPDATE SET likes = lc.likes + 1
+                        """,
+                            [fav_id, fav_id, user.pk],
+                        )
+
+                        cursor.execute(
+                            """
                             INSERT INTO core_assemblylikecount AS lc (assembly1_id, assembly2_id, likes, like_ratio)
-                                SELECT %s, e.assembly_id, 1, 0 FROM core_assembly_favorited_by AS e WHERE e.assembly_id <> %s AND e.platformuser_id = %s
+                                SELECT e.assembly_id, %s, 1, 0 FROM core_assembly_favorited_by AS e WHERE e.platformuser_id = %s
                                 ON CONFLICT (assembly1_id, assembly2_id) DO UPDATE SET likes = lc.likes + 1
-                            """, [fav_id, fav_id, user.pk])
-
-                            cursor.execute("""
-                                INSERT INTO core_assemblylikecount AS lc (assembly1_id, assembly2_id, likes, like_ratio)
-                                    SELECT e.assembly_id, %s, 1, 0 FROM core_assembly_favorited_by AS e WHERE e.platformuser_id = %s
-                                    ON CONFLICT (assembly1_id, assembly2_id) DO UPDATE SET likes = lc.likes + 1
-                            """, [fav_id, user.pk])
-                elif request.POST['type'] == 'event':
-                    event = Event.objects.conference_accessible(conference=self.conf).get(pk=fav_id)
-                    if not request.user.favorite_events.filter(pk=fav_id).exists():
-                        request.user.favorite_events.add(event)
-                        with connection.cursor() as cursor:
-                            cursor.execute("""
+                        """,
+                            [fav_id, user.pk],
+                        )
+            elif request.POST['type'] == 'event':
+                event = Event.objects.conference_accessible(conference=self.conf).get(pk=fav_id)
+                if not request.user.favorite_events.filter(pk=fav_id).exists():
+                    request.user.favorite_events.add(event)
+                    with connection.cursor() as cursor:
+                        cursor.execute(
+                            """
+                        INSERT INTO core_eventlikecount AS lc (event1_id, event2_id, likes, like_ratio)
+                            SELECT %s, e.event_id, 1, 0 FROM core_event_favorited_by AS e WHERE e.event_id <> %s AND e.platformuser_id = %s
+                            ON CONFLICT (event1_id, event2_id) DO UPDATE SET likes = lc.likes + 1
+                        """,
+                            [fav_id, fav_id, user.pk],
+                        )
+
+                        cursor.execute(
+                            """
                             INSERT INTO core_eventlikecount AS lc (event1_id, event2_id, likes, like_ratio)
-                                SELECT %s, e.event_id, 1, 0 FROM core_event_favorited_by AS e WHERE e.event_id <> %s AND e.platformuser_id = %s
+                                SELECT e.event_id, %s, 1, 0 FROM core_event_favorited_by AS e WHERE e.platformuser_id = %s
                                 ON CONFLICT (event1_id, event2_id) DO UPDATE SET likes = lc.likes + 1
-                            """, [fav_id, fav_id, user.pk])
-
-                            cursor.execute("""
-                                INSERT INTO core_eventlikecount AS lc (event1_id, event2_id, likes, like_ratio)
-                                    SELECT e.event_id, %s, 1, 0 FROM core_event_favorited_by AS e WHERE e.platformuser_id = %s
-                                    ON CONFLICT (event1_id, event2_id) DO UPDATE SET likes = lc.likes + 1
-                            """, [fav_id, user.pk])
+                        """,
+                            [fav_id, user.pk],
+                        )
 
         if request.POST['type'] == 'assembly':
             _session_refresh_favorite_assemblies(self.request.session, self.request.user)
@@ -1207,6 +1258,7 @@ class ModifyLanguageView(ConferenceRequiredMixin, View):
 
     def post(self, request):
         from core.templatetags.hub_absolute import hub_absolute
+
         redirect_to = request.POST.get('next') or hub_absolute('plainui:index')
 
         url_is_safe = url_has_allowed_host_and_scheme(
@@ -1278,7 +1330,7 @@ class PersonalMessageListView(ConferenceRequiredMixin, TemplateView):
 
         context['start'] = start
         context['total'] = dms.count()
-        context['msgs'] = dms[start:start + PAGE_SIZE]
+        context['msgs'] = dms[start : start + PAGE_SIZE]
         return context
 
 
@@ -1290,10 +1342,7 @@ class PersonalMessageSendView(ConferenceRequiredMixin, FormView):
     form_class = NewDirectMessageForm
 
     def get_context_data(self, **kwargs):
-        return super().get_context_data(
-            conf=self.conf,
-            **kwargs
-        )
+        return super().get_context_data(conf=self.conf, **kwargs)
 
     def get_initial(self):
         initial = super().get_initial()
@@ -1328,7 +1377,7 @@ class PersonalMessageSendView(ConferenceRequiredMixin, FormView):
         if in_reply_to:
             DirectMessage.objects.filter(id=in_reply_to, recipient=self.request.user).update(has_responded=True)
 
-        messages.success(self.request, gettext("Message sent."))
+        messages.success(self.request, gettext('Message sent.'))
         return super().form_valid(form)
 
 
@@ -1347,10 +1396,7 @@ class PersonalMessageShowView(ConferenceRequiredMixin, TemplateView):
             DirectMessage.objects.filter(pk=message.pk).update(was_read=True)
         context['msg_body'] = render_markdown(self.conf, message.body)
 
-        context['report_info'] = {
-            'kind': 'pn',
-            'url': message.id
-        }
+        context['report_info'] = {'kind': 'pn', 'url': message.id}
 
         return context
 
@@ -1366,7 +1412,7 @@ class PersonalMessageDeleteView(ConferenceRequiredMixin, View):
         DirectMessage.objects.filter(recipient=request.user, pk=request.POST['id']).update(deleted_by_recipient=True)
         DirectMessage.objects.filter(pk=request.POST['id'], deleted_by_sender=True, deleted_by_recipient=True).delete()
 
-        messages.success(self.request, gettext("Message deleted."))
+        messages.success(self.request, gettext('Message deleted.'))
         return redirect(reverse('plainui:personal_message'))
 
 
@@ -1381,7 +1427,7 @@ class IndexView(ConferenceRequiredMixin, TemplateView):
                 default_title = 'Page Missing'
 
             if not default_body:
-                url = reverse("plainui:static_page_edit", kwargs={"page_slug": slug})
+                url = reverse('plainui:static_page_edit', kwargs={'page_slug': slug})
                 default_body = 'Please configure the wiki page "{slug}" (or change slug). <a href="{url}">(edit)</a>'
 
             static_page = StaticPage(conference=self.conf, title=default_title, body_html=default_body.format(slug=slug, url=url))
@@ -1391,9 +1437,7 @@ class IndexView(ConferenceRequiredMixin, TemplateView):
         context = super().get_context_data(**kwargs)
         context['conf'] = self.conf
 
-        context['report_info'] = {
-            'url': reverse('plainui:index')
-        }
+        context['report_info'] = {'url': reverse('plainui:index')}
         context['share_url'] = reverse('plainui:index')
 
         context['start'] = self._fetch_page('start')
@@ -1461,13 +1505,10 @@ class PasswordChangeView(ConferenceRequiredMixin, auth_views.PasswordChangeView)
         return super().dispatch(*args, **kwargs)
 
     def get_context_data(self, **kwargs):
-        return super().get_context_data(
-            conf=self.conf,
-            **kwargs
-        )
+        return super().get_context_data(conf=self.conf, **kwargs)
 
     def get_success_url(self):
-        messages.success(self.request, gettext("Password changed successfully."))
+        messages.success(self.request, gettext('Password changed successfully.'))
         return reverse('plainui:userprofile')
 
 
@@ -1476,9 +1517,7 @@ class PasswordResetView(ConferenceRequiredMixin, BasePasswordResetView):
     require_login = False
     success_url = reverse_lazy('plainui:password_reset_done')
     template_name = 'plainui/registration/password_reset_form.html'
-    extra_email_context = {
-        "reset_url": 'plainui:password_reset_confirm'
-    }
+    extra_email_context = {'reset_url': 'plainui:password_reset_confirm'}
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -1544,12 +1583,12 @@ class TokenPasswordResetView(ConferenceRequiredMixin, FormView):
 
     def form_valid(self, form):
         form.save()
-        messages.success(self.request, gettext("Passwort successfully reset!"))
+        messages.success(self.request, gettext('Passwort successfully reset!'))
         return HttpResponseRedirect(reverse('plainui:login'))
 
 
 class LogoutView(auth_views.LogoutView):
-    next_page = "plainui:login"
+    next_page = 'plainui:login'
 
 
 class BoardView(ConferenceRequiredMixin, TemplateView):
@@ -1564,22 +1603,17 @@ class BoardView(ConferenceRequiredMixin, TemplateView):
             start = int(self.request.GET['start'])
         except (ValueError, KeyError):
             start = 0
-        entries = BulletinBoardEntry.objects \
-            .select_related('owner') \
-            .filter(conference=self.conf, is_public=True) \
-            .order_by('-timestamp')
+        entries = BulletinBoardEntry.objects.select_related('owner').filter(conference=self.conf, is_public=True).order_by('-timestamp')
         if self.request.user.is_authenticated and self.request.user.shadow_banned:
             entries = entries.filter(Q(owner=self.request.user) | Q(hidden=False))
         else:
             entries = entries.filter(hidden=False)
-        context['board'] = entries[start:start + PAGE_SIZE]
+        context['board'] = entries[start : start + PAGE_SIZE]
         context['current_user'] = self.request.user.id if self.request.user.is_authenticated else 0
         context['next_url'] = reverse('plainui:board') + '?start=' + str(start + PAGE_SIZE)
         context['prev_url'] = reverse('plainui:board') + '?start=' + str(start - PAGE_SIZE) if start != 0 else None
 
-        context['report_info'] = {
-            'url': reverse('plainui:board')
-        }
+        context['report_info'] = {'url': reverse('plainui:board')}
         context['share_url'] = reverse('plainui:board') if start == 0 else reverse('plainui:board') + '?start=' + str(start)
 
         return context
@@ -1593,9 +1627,7 @@ class BoardPrivateView(ConferenceRequiredMixin, TemplateView):
         context = super().get_context_data(**kwargs)
         context['conf'] = self.conf
 
-        context['board'] = BulletinBoardEntry.objects \
-            .filter(conference=self.conf, owner=self.request.user) \
-            .order_by('-timestamp')
+        context['board'] = BulletinBoardEntry.objects.filter(conference=self.conf, owner=self.request.user).order_by('-timestamp')
         return context
 
 
@@ -1609,10 +1641,7 @@ class BoardEntryView(ConferenceRequiredMixin, TemplateView):
 
         context['entry'] = get_object_or_404(BulletinBoardEntry, conference=self.conf, pk=self.kwargs['id'], is_public=True)
 
-        context['report_info'] = {
-            'kind': 'board',
-            'url': self.kwargs['id']
-        }
+        context['report_info'] = {'kind': 'board', 'url': self.kwargs['id']}
         context['share_url'] = reverse('plainui:board_entry', kwargs={'id': self.kwargs['id']})
 
         return context
@@ -1626,11 +1655,7 @@ class BoardEntryEditView(ConferenceRequiredMixin, FormView):
     form_class = BulletinBoardEntryForm
 
     def get_context_data(self, **kwargs):
-        return super().get_context_data(
-            conf=self.conf,
-            edit_mode=self.kwargs.get('id', 0) != 0,
-            **kwargs
-        )
+        return super().get_context_data(conf=self.conf, edit_mode=self.kwargs.get('id', 0) != 0, **kwargs)
 
     def get_form_kwargs(self):
         kwargs = super().get_form_kwargs()
@@ -1652,9 +1677,9 @@ class BoardEntryEditView(ConferenceRequiredMixin, FormView):
         self.board_entry.save()
 
         if 'id' in self.kwargs:
-            messages.success(self.request, gettext("Bulletin Board Entry updated."))
+            messages.success(self.request, gettext('Bulletin Board Entry updated.'))
         else:
-            messages.success(self.request, gettext("Bulletin Board Entry created."))
+            messages.success(self.request, gettext('Bulletin Board Entry created.'))
         return super().form_valid(form)
 
 
@@ -1667,7 +1692,7 @@ class BoardEntryDeleteView(ConferenceRequiredMixin, View):
     def post(self, request, **kwargs):
         BulletinBoardEntry.objects.filter(conference_id=self.conf, owner=self.request.user, id=request.POST['id']).delete()
 
-        messages.success(self.request, gettext("Bulletin Board Entry deleted."))
+        messages.success(self.request, gettext('Bulletin Board Entry deleted.'))
         return redirect(reverse('plainui:board_private'))
 
 
@@ -1696,12 +1721,12 @@ def _organize_events_for_calendar(conf, events):
     if not events_by_room:
         return None
 
-    rooms = Room.objects.conference_accessible(conf) \
-        .filter(pk__in=events_by_room.keys()) \
+    rooms = (
+        Room.objects.conference_accessible(conf)
+        .filter(pk__in=events_by_room.keys())
         .order_by('official_room_order', F('capacity').desc(nulls_last=True), 'name')
-    rooms_with_events = [
-        (room, events_by_room[room.id]) for room in rooms
-    ]
+    )
+    rooms_with_events = [(room, events_by_room[room.id]) for room in rooms]
 
     # calendar starts at first scheduled event rounded down to the full hour
     calendar_start = min(
@@ -1711,7 +1736,7 @@ def _organize_events_for_calendar(conf, events):
     calendar_start = localtime(calendar_start.replace(minute=0, second=0, microsecond=0))
 
     # calendar ends at the end of the last scheduled event rounded up to the full hour
-    calendar_end = max((room_events[-1]['event'].schedule_end for (_, room_events) in rooms_with_events))
+    calendar_end = max(room_events[-1]['event'].schedule_end for (_, room_events) in rooms_with_events)
     calendar_end = (calendar_end + timedelta(0, 3599)).replace(minute=0, second=0, microsecond=0)
     calendar_end = localtime(calendar_end)
 
@@ -1736,8 +1761,18 @@ UPCOMING_WINDOW = timedelta(minutes=30)
 
 
 def _event_filter(
-        user: PlatformUser, conf, day=None, kinds=None, assembly=None, track=None,
-        room=None, user_schedule_only=False, upcoming=False, calendar_mode=True, public_fahrplan=None):
+    user: PlatformUser,
+    conf,
+    day=None,
+    kinds=None,
+    assembly=None,
+    track=None,
+    room=None,
+    user_schedule_only=False,
+    upcoming=False,
+    calendar_mode=True,
+    public_fahrplan=None,
+):
     min_date, max_date = conf.start, conf.end
     if min_date is None or max_date is None:
         return Event.objects.none()
@@ -1766,20 +1801,11 @@ def _event_filter(
         events = events.filter(Q(schedule_start__lt=now, schedule_end__gte=now) | Q(schedule_start__gte=now, schedule_start__lt=now + UPCOMING_WINDOW))
     if calendar_mode:
         events = events.filter(room__isnull=False)
-    res = events.filter(
-            schedule_duration__isnull=False,
-            **filters
-        ).order_by('schedule_start', 'schedule_end')
+    res = events.filter(schedule_duration__isnull=False, **filters).order_by('schedule_start', 'schedule_end')
     res = res.annotate(track_name=F('track__name'))
     speakers = EventParticipant.objects.filter(is_public=True, role=EventParticipant.Role.SPEAKER).order_by('participant__username')
     speakers = speakers.annotate(speaker_name=F('participant__username'))
-    return res.prefetch_related(
-        Prefetch(
-            'participants',
-            queryset=speakers,
-            to_attr='speakers'
-        )
-    )
+    return res.prefetch_related(Prefetch('participants', queryset=speakers, to_attr='speakers'))
 
 
 class FahrplanView(ConferenceRequiredMixin, TemplateView):
@@ -1804,7 +1830,7 @@ class FahrplanView(ConferenceRequiredMixin, TemplateView):
         min_date = self.conf.start
         max_date = self.conf.end
         if min_date is None or max_date is None:
-            raise Http404()
+            raise Http404
         n_days = (max_date - min_date).days
         if (max_date - min_date) != timedelta(n_days):
             n_days += 1
@@ -1907,7 +1933,8 @@ class FahrplanView(ConferenceRequiredMixin, TemplateView):
             track=track,
             calendar_mode=mode == 'calendar',
             public_fahrplan=public_fahrplan,
-            **self.filter_opts)
+            **self.filter_opts,
+        )
 
         if mode == 'calendar':
             context['mode'] = 'calendar'
@@ -1939,20 +1966,19 @@ class PublicFahrplanView(ConferenceRequiredMixin, TemplateView):
                 context['conference_timezone'] = self.conf.timezone
 
         events = _event_filter(self.request.user, self.conf, public_fahrplan=True)
-        # events = _event_filter(self.request.user, self.conf)
         context['events'] = _organize_events_for_calendar(self.conf, events)
         return context
 
 
 class AssemblyJoinBBB(ConferenceRequiredMixin, View):
-    """ extra view to join BBB rooms from the Workadventure """
+    """extra view to join BBB rooms from the Workadventure"""
 
     require_user = True
 
     def get(self, request, assembly_slug, room_slug):
         room = get_object_or_404(Room.objects.conference_accessible(self.conf), assembly__slug=assembly_slug, slug=room_slug)
         if not room.room_type == Room.RoomType.BIGBLUEBUTTON:
-            raise Http404()
+            raise Http404
 
         try:
             return HttpResponseRedirect(integrations.BigBlueButton.join_room(room, self.request.user, not self.request.user.show_name))
@@ -1973,14 +1999,14 @@ class RoomView(ConferenceRequiredMixin, TemplateView):
                 if self.request.user.is_authenticated:
                     return HttpResponseRedirect(integrations.BigBlueButton.join_room(self.room, self.request.user, not self.request.user.show_name))
 
-                messages.error(request, gettext("RoomView--loginrequired"))
+                messages.error(request, gettext('RoomView--loginrequired'))
             elif self.room.room_type == Room.RoomType.WORKADVENTURE:
                 if self.request.user.is_authenticated:
                     wa_session = WorkadventureSession.create_for_conference_user(self.conf, self.request.user, self.room)
                     token_url = wa_session.get_token_url()
                     return redirect(token_url)
 
-                messages.error(request, gettext("RoomView--loginrequired"))
+                messages.error(request, gettext('RoomView--loginrequired'))
         except integrations.IntegrationError as e:
             messages.error(request, str(e))
 
@@ -2005,9 +2031,7 @@ class RoomView(ConferenceRequiredMixin, TemplateView):
         context['my_favorite_events'] = _session_get_favorite_events(self.request.session, self.request.user)
         context['my_scheduled_events'] = _session_get_scheduled_events(self.request.session, self.request.user)
 
-        context['report_info'] = {
-            'url': reverse('plainui:room', kwargs={'room_slug': self.room.slug})
-        }
+        context['report_info'] = {'url': reverse('plainui:room', kwargs={'room_slug': self.room.slug})}
         context['share_url'] = reverse('plainui:room', kwargs={'room_slug': self.room.slug})
 
         return context
@@ -2021,9 +2045,7 @@ class RoomsView(ConferenceRequiredMixin, TemplateView):
         context['conf'] = self.conf
         context['rooms'] = Room.objects.conference_accessible(self.conf)
 
-        context['report_info'] = {
-            'url': reverse('plainui:rooms')
-        }
+        context['report_info'] = {'url': reverse('plainui:rooms')}
         context['share_url'] = reverse('plainui:rooms')
 
         return context
@@ -2040,7 +2062,7 @@ class UpcomingView(ConferenceRequiredMixin, TemplateView):
             events=events,
             my_favorite_events=_session_get_favorite_events(self.request.session, self.request.user),
             my_scheduled_events=_session_get_scheduled_events(self.request.session, self.request.user),
-            **kwargs
+            **kwargs,
         )
 
 
@@ -2077,33 +2099,29 @@ class ChannelEventsView(ConferenceRequiredMixin, TemplateView):
             rooms.append(room)
             room_ids.append(room.pk)
 
-        current_events_qs = Event.objects.conference_accessible(self.conf)\
-            .filter(room_id__in=room_ids, schedule_start__lte=now, schedule_end__gte=now).order_by('room_id', 'schedule_start').distinct('room_id')
+        current_events_qs = (
+            Event.objects.conference_accessible(self.conf)
+            .filter(room_id__in=room_ids, schedule_start__lte=now, schedule_end__gte=now)
+            .order_by('room_id', 'schedule_start')
+            .distinct('room_id')
+        )
         speakers = EventParticipant.objects.filter(is_public=True, role=EventParticipant.Role.SPEAKER).order_by('participant__username')
         speakers = speakers.annotate(speaker_name=F('participant__username'))
-        current_events_qs = current_events_qs.prefetch_related(
-            Prefetch(
-                'participants',
-                queryset=speakers,
-                to_attr='speakers'
-            )
-        )
+        current_events_qs = current_events_qs.prefetch_related(Prefetch('participants', queryset=speakers, to_attr='speakers'))
 
         current_events = {}  # type: Dict[UUID, Event]
         for event in current_events_qs:
             current_events[event.room_id] = event
 
-        next_events_qs = Event.objects.conference_accessible(self.conf)\
-            .filter(room_id__in=room_ids, schedule_start__gt=now, schedule_end__isnull=False).order_by('room_id', 'schedule_start').distinct('room_id')
+        next_events_qs = (
+            Event.objects.conference_accessible(self.conf)
+            .filter(room_id__in=room_ids, schedule_start__gt=now, schedule_end__isnull=False)
+            .order_by('room_id', 'schedule_start')
+            .distinct('room_id')
+        )
         speakers = EventParticipant.objects.filter(is_public=True, role=EventParticipant.Role.SPEAKER).order_by('participant__username')
         speakers = speakers.annotate(speaker_name=F('participant__username'))
-        next_events_qs = next_events_qs.prefetch_related(
-            Prefetch(
-                'participants',
-                queryset=speakers,
-                to_attr='speakers'
-            )
-        )
+        next_events_qs = next_events_qs.prefetch_related(Prefetch('participants', queryset=speakers, to_attr='speakers'))
 
         next_events = {}  # type: Dict[UUID, Event]
         for event in next_events_qs:
@@ -2131,12 +2149,9 @@ class ChannelEventsView(ConferenceRequiredMixin, TemplateView):
         context['my_scheduled_events'] = _session_get_scheduled_events(self.request.session, self.request.user)
 
         events = _event_filter(self.request.user, self.conf, public_fahrplan=True)
-        # events = _event_filter(self.request.user, self.conf)
         context['events'] = _organize_events_for_calendar(self.conf, events)
 
-        context['report_info'] = {
-            'url': reverse('plainui:channel_events')
-        }
+        context['report_info'] = {'url': reverse('plainui:channel_events')}
         context['share_url'] = reverse('plainui:channel_events')
 
         return context
@@ -2181,7 +2196,7 @@ class WorkadventureEnter(ConferenceRequiredMixin, View):
 class BaseDereferrerView(ConferenceRequiredMixin, View):
     def gen_salt(self):
         if self.request.user.is_authenticated:
-            return '%sdereferrer' % (self.request.user.pk,)
+            return f'{self.request.user.pk}dereferrer'
         else:
             return '000000dereferrer'
 
@@ -2189,7 +2204,7 @@ class BaseDereferrerView(ConferenceRequiredMixin, View):
         try:
             return signing.loads(signed_payload, salt=self.gen_salt())
         except signing.BadSignature:
-            raise PermissionDenied()
+            raise PermissionDenied
 
     def sign_payload(self, payload: str) -> str:
         return signing.dumps(payload, salt=self.gen_salt())
@@ -2201,11 +2216,12 @@ class BaseDereferrerView(ConferenceRequiredMixin, View):
         if url_in_allowlist(scheme_and_netloc, settings.DEREFERRER_COUNT_ACCESS):
             with connection.cursor() as cursor:
                 cursor.execute(
-                    "INSERT INTO " + DereferrerStats._meta.db_table + " AS ds (domain, hits) VALUES (%s, 1) \
-                        ON CONFLICT (domain) DO UPDATE SET hits = ds.hits + 1",
-                    [scheme_and_netloc]
+                    'INSERT INTO '
+                    + DereferrerStats._meta.db_table
+                    + ' AS ds (domain, hits) VALUES (%s, 1) \
+                        ON CONFLICT (domain) DO UPDATE SET hits = ds.hits + 1',
+                    [scheme_and_netloc],
                 )
-            # DereferrerStats.objects.filter(domain=scheme_and_netloc).update(hits=F('hits') + 1)
 
         return HttpResponseRedirect(url.geturl())
 
@@ -2247,7 +2263,7 @@ class DereferrerView(BaseDereferrerView):
         url = urlparse(destination)
 
         # local urls are always allowed
-        allowlist_match = (not url.scheme and not url.netloc)
+        allowlist_match = not url.scheme and not url.netloc
         scheme = url.scheme if url.scheme else request.scheme
         scheme_and_netloc = scheme + '://' + url.netloc
 
@@ -2271,13 +2287,17 @@ class DereferrerView(BaseDereferrerView):
         else:
             # if the url is not allowed, show the dereferrer page
             signed_url = self.sign_payload(url.geturl())
-            return TemplateResponse(request, self.template_name, context={
-                'conf': self.conf,
-                'plain_url': url.geturl(),
-                'domain': scheme_and_netloc,
-                'signed_url': signed_url,
-                'can_allow': request.user.is_authenticated,
-            })
+            return TemplateResponse(
+                request,
+                self.template_name,
+                context={
+                    'conf': self.conf,
+                    'plain_url': url.geturl(),
+                    'domain': scheme_and_netloc,
+                    'signed_url': signed_url,
+                    'can_allow': request.user.is_authenticated,
+                },
+            )
 
 
 class WorkadventureDereferrerView(DereferrerView):
@@ -2295,7 +2315,7 @@ class DereferrerRemoveFromAllowlistView(View):
             return redirect(reverse('plainui:userprofile'))
 
         entry.delete()
-        messages.success(request, gettext("UserDereferrerAllowlist--deleted"))
+        messages.success(request, gettext('UserDereferrerAllowlist--deleted'))
         return redirect(reverse('plainui:userprofile'))
 
 
@@ -2314,11 +2334,7 @@ class ReportContentView(ConferenceRequiredMixin, FormView):
                 conference=self.conf, title='Page Missing', body_html='Please configure the static page "report_content" (or change slug... ).'
             )
 
-        return super().get_context_data(
-            conf=self.conf,
-            page=static_page,
-            **kwargs
-        )
+        return super().get_context_data(conf=self.conf, page=static_page, **kwargs)
 
     def get_form_kwargs(self):
         kwargs = super().get_form_kwargs()
@@ -2338,10 +2354,10 @@ class ReportContentView(ConferenceRequiredMixin, FormView):
         messages.success(
             self.request,
             gettext(
-                "Thank you for your help to make this plattform safer and better! "
-                "Please give us some time to find a solution and keep an eye on your Messages, we may contact you."
-            )
-         )
+                'Thank you for your help to make this plattform safer and better! '
+                'Please give us some time to find a solution and keep an eye on your Messages, we may contact you.'
+            ),
+        )
 
         redirect_to = form.cleaned_data['next']
         url_is_safe = url_has_allowed_host_and_scheme(
@@ -2375,20 +2391,26 @@ class UserView(ConferenceRequiredMixin, TemplateView):
         contact = user.is_authenticated and UserContact.objects.filter(user=display_user, contact=user).first()
         if contact and not contact.pending:
             # Show Friend Badges
-            context['badges'] = UserBadge.objects.filter(user=display_user, accepted_by_user=True) \
-                                .filter(Q(visibility__in=[UserBadge.Visibility.PUBLIC, UserBadge.Visibility.FRIENDS, UserBadge.Visibility.CLUBFRIENDS]) |
-                                        Q(badge__in=user_badges, visibility__in=[UserBadge.Visibility.CLUB, UserBadge.Visibility.CLUBFRIENDS])) \
-                                .select_related('badge')
+            context['badges'] = (
+                UserBadge.objects.filter(user=display_user, accepted_by_user=True)
+                .filter(
+                    Q(visibility__in=[UserBadge.Visibility.PUBLIC, UserBadge.Visibility.FRIENDS, UserBadge.Visibility.CLUBFRIENDS])
+                    | Q(badge__in=user_badges, visibility__in=[UserBadge.Visibility.CLUB, UserBadge.Visibility.CLUBFRIENDS])
+                )
+                .select_related('badge')
+            )
         else:
             # Show only public badges
-            context['badges'] = UserBadge.objects.filter(user=display_user, accepted_by_user=True) \
-                                .filter(Q(visibility__in=[UserBadge.Visibility.PUBLIC]) |
-                                        Q(badge__in=user_badges, visibility__in=[UserBadge.Visibility.CLUB, UserBadge.Visibility.CLUBFRIENDS])) \
-                                .select_related('badge')
+            context['badges'] = (
+                UserBadge.objects.filter(user=display_user, accepted_by_user=True)
+                .filter(
+                    Q(visibility__in=[UserBadge.Visibility.PUBLIC])
+                    | Q(badge__in=user_badges, visibility__in=[UserBadge.Visibility.CLUB, UserBadge.Visibility.CLUBFRIENDS])
+                )
+                .select_related('badge')
+            )
 
-        context['report_info'] = {
-            'url': reverse('plainui:user', kwargs={'user_slug': display_user.slug})
-        }
+        context['report_info'] = {'url': reverse('plainui:user', kwargs={'user_slug': display_user.slug})}
 
         return context
 
@@ -2411,44 +2433,34 @@ class ComponentGalleryView(TemplateView):
             conf={
                 'slug': 'sample',
                 'name': 'Example',
-                "id": "confID",
-                "get_navigation_tree": lambda: [{
-                    'label': 'Conference',
-                    'title': 'Conference',
-                    'icon': 'hammer',
-                    'url': '/',
-                }],
-                "map_config": {"frontend": {
-                    "start": {
-                        "latitude": 53.56172,
-                        "longitude": 9.98593,
-                        "zoom": 16
-                    },
-                    "style": {
-                        "version": 8,
-                        "sources": {
-                            "osm-raster": {
-                                "type": "raster",
-                                "tiles": [
-                                    "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
-                                ],
-                                "tileSize": 256,
-                                "attribution": "&copy; OpenStreetMap contributors / test use only!!!"
-                            }
+                'id': 'confID',
+                'get_navigation_tree': lambda: [
+                    {
+                        'label': 'Conference',
+                        'title': 'Conference',
+                        'icon': 'hammer',
+                        'url': '/',
+                    }
+                ],
+                'map_config': {
+                    'frontend': {
+                        'start': {'latitude': 53.56172, 'longitude': 9.98593, 'zoom': 16},
+                        'style': {
+                            'version': 8,
+                            'sources': {
+                                'osm-raster': {
+                                    'type': 'raster',
+                                    'tiles': ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
+                                    'tileSize': 256,
+                                    'attribution': '&copy; OpenStreetMap contributors / test use only!!!',
+                                }
+                            },
+                            'layers': [{'id': 'simple-tiles', 'type': 'raster', 'source': 'osm-raster', 'minzoom': 0, 'maxzoom': 19}],
                         },
-                        "layers": [
-                            {
-                                "id": "simple-tiles",
-                                "type": "raster",
-                                "source": "osm-raster",
-                                "minzoom": 0,
-                                "maxzoom": 19
-                            }
-                        ]
                     }
-                }}
+                },
             },
-            demo_map_startpos={"longitude": 9.98641, "latitude": 53.56148},
+            demo_map_startpos={'longitude': 9.98641, 'latitude': 53.56148},
             csrf_input='xss-safe',
             hide_header=True,
             time1=timezone.now(),
diff --git a/tests/utils.py b/tests/utils.py
index eec7db6e6..988e88631 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -4,7 +4,6 @@ from urllib.parse import urljoin
 
 import requests
 
-
 _DEFAULT_BASE_URL = 'http://hubapp'
 _DEFAULT_ADMIN_USERNAME = 'admin'
 _DEFAULT_ADMIN_PASSWORD = 'password'
-- 
GitLab