diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b5decde4519f7d4ca45a84e8f70aa23dd745d677..be605d93e176d6515afef3269bfa9fc19aca7c70 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -259,6 +259,7 @@ django-tests:
     - pdm sync -d --no-editable -G dev
     - pdm run test
     - pdm run coverage xml -i
+    - pdm run coverage report
   coverage: '/(?i)total(?:\s+\d+){4}\s+(\d+)%/'
   artifacts:
     reports:
diff --git a/.pylintrc.toml b/.pylintrc.toml
index 92b9f079fb5afc1610d22c1258eaab29e1988b71..a71b646c8e7171307309b5af0ab825da8491a5e0 100644
--- a/.pylintrc.toml
+++ b/.pylintrc.toml
@@ -19,11 +19,11 @@ ignore-patterns = ["^\\.#"]
 # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
 # number of processors available to use, and will cap the count on Windows to
 # avoid hangs.
-jobs = 0
+#jobs = 0
 
 # List of plugins (as comma separated values of python module names) to load,
 # usually to register additional checkers.
-load-plugins = ["pylint_django", "pylint_per_file_ignores"]
+load-plugins = ["pylint_django"]
 
 # Pickle collected data for later comparisons.
 persistent = true
@@ -156,14 +156,10 @@ disable = [
   "too-many-return-statements",
   "too-many-statements",
 
+  "unused-argument",
+  "protected-access",
 ]
 
-# TODO: replace with TOML List after https://github.com/christopherpickering/pylint-per-file-ignores/issues/160 is fixec
-per-file-ignores = """
-  "src/hub/settings/*:unused-wildcard-import,wildcard-import,unused-import",
-  ".*/tests/.*:invalid-name",
-"""
-
 
 [tool.pylint.miscellaneous]
 # List of note tags to take in consideration, separated by a comma.
diff --git a/pdm.lock b/pdm.lock
index 80fa92196c5cf368447c3c03aec97b0f53e7aa64..e40d73c468e64fb34e85aa0c6c2a75aca70ddd24 100644
--- a/pdm.lock
+++ b/pdm.lock
@@ -5,7 +5,7 @@
 groups = ["default", "dev", "lint", "local", "static-analysis", "typing", "watchfiles"]
 strategy = ["inherit_metadata"]
 lock_version = "4.5.0"
-content_hash = "sha256:fc86b394f3b33fdd867e661094a00a1b8cbb93ae926d2d1e8b1590fa54771159"
+content_hash = "sha256:af6518bad6a153127d484181441f930ee00c15c1d0f704ea906e0fc5b4d15282"
 
 [[metadata.targets]]
 requires_python = "==3.13.*"
@@ -138,23 +138,23 @@ files = [
 
 [[package]]
 name = "boto3"
-version = "1.35.57"
+version = "1.35.68"
 requires_python = ">=3.8"
 summary = "The AWS SDK for Python"
 groups = ["default"]
 dependencies = [
-    "botocore<1.36.0,>=1.35.57",
+    "botocore<1.36.0,>=1.35.68",
     "jmespath<2.0.0,>=0.7.1",
     "s3transfer<0.11.0,>=0.10.0",
 ]
 files = [
-    {file = "boto3-1.35.57-py3-none-any.whl", hash = "sha256:9edf49640c79a05b0a72f4c2d1e24dfc164344b680535a645f455ac624dc3680"},
-    {file = "boto3-1.35.57.tar.gz", hash = "sha256:db58348849a5af061f0f5ec9c3b699da5221ca83354059fdccb798e3ddb6b62a"},
+    {file = "boto3-1.35.68-py3-none-any.whl", hash = "sha256:9b26fa31901da7793c1dcd65eee9bab7e897d8aa1ffed0b5e1c3bce93d2aefe4"},
+    {file = "boto3-1.35.68.tar.gz", hash = "sha256:091d6bed1422370987a839bff3f8755df7404fc15e9fac2a48e8505356f07433"},
 ]
 
 [[package]]
 name = "botocore"
-version = "1.35.57"
+version = "1.35.68"
 requires_python = ">=3.8"
 summary = "Low-level, data-driven core of boto 3."
 groups = ["default"]
@@ -165,8 +165,8 @@ dependencies = [
     "urllib3<1.27,>=1.25.4; python_version < \"3.10\"",
 ]
 files = [
-    {file = "botocore-1.35.57-py3-none-any.whl", hash = "sha256:92ddd02469213766872cb2399269dd20948f90348b42bf08379881d5e946cc34"},
-    {file = "botocore-1.35.57.tar.gz", hash = "sha256:d96306558085baf0bcb3b022d7a8c39c93494f031edb376694d2b2dcd0e81327"},
+    {file = "botocore-1.35.68-py3-none-any.whl", hash = "sha256:599139d5564291f5be873800711f9e4e14a823395ae9ce7b142be775e9849b94"},
+    {file = "botocore-1.35.68.tar.gz", hash = "sha256:42c3700583a82f2b5316281a073d644a521d6358837e2b446dc458ba5d990fb4"},
 ]
 
 [[package]]
@@ -292,32 +292,32 @@ files = [
 
 [[package]]
 name = "coverage"
-version = "7.6.4"
+version = "7.6.8"
 requires_python = ">=3.9"
 summary = "Code coverage measurement for Python"
 groups = ["dev"]
 files = [
-    {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"},
-    {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"},
-    {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"},
-    {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"},
-    {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"},
-    {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"},
-    {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"},
-    {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"},
-    {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"},
-    {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"},
-    {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"},
-    {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"},
-    {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"},
-    {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"},
-    {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"},
-    {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"},
-    {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"},
-    {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"},
-    {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"},
-    {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"},
-    {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"},
+    {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"},
+    {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"},
+    {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"},
+    {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"},
+    {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"},
+    {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"},
+    {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"},
+    {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"},
+    {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"},
+    {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"},
+    {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"},
+    {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"},
+    {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"},
+    {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"},
+    {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"},
+    {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"},
+    {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"},
+    {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"},
+    {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"},
+    {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"},
+    {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"},
 ]
 
 [[package]]
@@ -367,17 +367,17 @@ files = [
 
 [[package]]
 name = "debugpy"
-version = "1.8.8"
+version = "1.8.9"
 requires_python = ">=3.8"
 summary = "An implementation of the Debug Adapter Protocol for Python"
 groups = ["local"]
 files = [
-    {file = "debugpy-1.8.8-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:705cd123a773d184860ed8dae99becd879dfec361098edbefb5fc0d3683eb804"},
-    {file = "debugpy-1.8.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:890fd16803f50aa9cb1a9b9b25b5ec321656dd6b78157c74283de241993d086f"},
-    {file = "debugpy-1.8.8-cp313-cp313-win32.whl", hash = "sha256:90244598214bbe704aa47556ec591d2f9869ff9e042e301a2859c57106649add"},
-    {file = "debugpy-1.8.8-cp313-cp313-win_amd64.whl", hash = "sha256:4b93e4832fd4a759a0c465c967214ed0c8a6e8914bced63a28ddb0dd8c5f078b"},
-    {file = "debugpy-1.8.8-py2.py3-none-any.whl", hash = "sha256:ec684553aba5b4066d4de510859922419febc710df7bba04fe9e7ef3de15d34f"},
-    {file = "debugpy-1.8.8.zip", hash = "sha256:e6355385db85cbd666be703a96ab7351bc9e6c61d694893206f8001e22aee091"},
+    {file = "debugpy-1.8.9-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:957ecffff80d47cafa9b6545de9e016ae8c9547c98a538ee96ab5947115fb3dd"},
+    {file = "debugpy-1.8.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1efbb3ff61487e2c16b3e033bc8595aea578222c08aaf3c4bf0f93fadbd662ee"},
+    {file = "debugpy-1.8.9-cp313-cp313-win32.whl", hash = "sha256:7c4d65d03bee875bcb211c76c1d8f10f600c305dbd734beaed4077e902606fee"},
+    {file = "debugpy-1.8.9-cp313-cp313-win_amd64.whl", hash = "sha256:e46b420dc1bea64e5bbedd678148be512442bc589b0111bd799367cde051e71a"},
+    {file = "debugpy-1.8.9-py2.py3-none-any.whl", hash = "sha256:cc37a6c9987ad743d9c3a14fa1b1a14b7e4e6041f9dd0c8abf8895fe7a97b899"},
+    {file = "debugpy-1.8.9.zip", hash = "sha256:1339e14c7d980407248f09824d1b25ff5c5616651689f1e0f0e51bdead3ea13e"},
 ]
 
 [[package]]
@@ -843,7 +843,7 @@ files = [
 
 [[package]]
 name = "httpcore"
-version = "1.0.6"
+version = "1.0.7"
 requires_python = ">=3.8"
 summary = "A minimal low-level HTTP client."
 groups = ["default"]
@@ -852,8 +852,8 @@ dependencies = [
     "h11<0.15,>=0.13",
 ]
 files = [
-    {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"},
-    {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"},
+    {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
+    {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
 ]
 
 [[package]]
@@ -990,13 +990,13 @@ files = [
 
 [[package]]
 name = "json5"
-version = "0.9.25"
-requires_python = ">=3.8"
+version = "0.9.28"
+requires_python = ">=3.8.0"
 summary = "A Python implementation of the JSON5 data format."
 groups = ["lint"]
 files = [
-    {file = "json5-0.9.25-py3-none-any.whl", hash = "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f"},
-    {file = "json5-0.9.25.tar.gz", hash = "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae"},
+    {file = "json5-0.9.28-py3-none-any.whl", hash = "sha256:29c56f1accdd8bc2e037321237662034a7e07921e2b7223281a5ce2c46f0c4df"},
+    {file = "json5-0.9.28.tar.gz", hash = "sha256:1f82f36e615bc5b42f1bbd49dbc94b12563c56408c6ffa06414ea310890e9a6e"},
 ]
 
 [[package]]
@@ -1453,7 +1453,7 @@ files = [
 
 [[package]]
 name = "psycopg-pool"
-version = "3.2.3"
+version = "3.2.4"
 requires_python = ">=3.8"
 summary = "Connection Pool for Psycopg"
 groups = ["default"]
@@ -1461,8 +1461,8 @@ dependencies = [
     "typing-extensions>=4.6",
 ]
 files = [
-    {file = "psycopg_pool-3.2.3-py3-none-any.whl", hash = "sha256:53bd8e640625e01b2927b2ad96df8ed8e8f91caea4597d45e7673fc7bbb85eb1"},
-    {file = "psycopg_pool-3.2.3.tar.gz", hash = "sha256:bb942f123bef4b7fbe4d55421bd3fb01829903c95c0f33fd42b7e94e5ac9b52a"},
+    {file = "psycopg_pool-3.2.4-py3-none-any.whl", hash = "sha256:f6a22cff0f21f06d72fb2f5cb48c618946777c49385358e0c88d062c59cbd224"},
+    {file = "psycopg_pool-3.2.4.tar.gz", hash = "sha256:61774b5bbf23e8d22bedc7504707135aaf744679f8ef9b3fe29942920746a6ed"},
 ]
 
 [[package]]
@@ -1496,24 +1496,23 @@ files = [
 
 [[package]]
 name = "pydantic"
-version = "2.9.2"
+version = "2.10.1"
 requires_python = ">=3.8"
 summary = "Data validation using Python type hints"
 groups = ["default"]
 dependencies = [
     "annotated-types>=0.6.0",
-    "pydantic-core==2.23.4",
-    "typing-extensions>=4.12.2; python_version >= \"3.13\"",
-    "typing-extensions>=4.6.1; python_version < \"3.13\"",
+    "pydantic-core==2.27.1",
+    "typing-extensions>=4.12.2",
 ]
 files = [
-    {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
-    {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
+    {file = "pydantic-2.10.1-py3-none-any.whl", hash = "sha256:a8d20db84de64cf4a7d59e899c2caf0fe9d660c7cfc482528e7020d7dd189a7e"},
+    {file = "pydantic-2.10.1.tar.gz", hash = "sha256:a4daca2dc0aa429555e0656d6bf94873a7dc5f54ee42b1f5873d666fb3f35560"},
 ]
 
 [[package]]
 name = "pydantic-core"
-version = "2.23.4"
+version = "2.27.1"
 requires_python = ">=3.8"
 summary = "Core functionality for Pydantic validation and serialization"
 groups = ["default"]
@@ -1521,19 +1520,21 @@ dependencies = [
     "typing-extensions!=4.7.0,>=4.6.0",
 ]
 files = [
-    {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
-    {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
-    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
-    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
-    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
-    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
-    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
-    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
-    {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
-    {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
-    {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
-    {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
-    {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"},
+    {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"},
+    {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"},
+    {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"},
+    {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"},
+    {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
 ]
 
 [[package]]
@@ -1597,20 +1598,6 @@ files = [
     {file = "pylint_django-2.6.1-py3-none-any.whl", hash = "sha256:359f68fe8c810ee6bc8e1ab4c83c19b15a43b234a24b08978f47a23462b5ce28"},
 ]
 
-[[package]]
-name = "pylint-per-file-ignores"
-version = "1.3.2"
-requires_python = ">=3.8.1,<4.0.0"
-summary = "A pylint plugin to ignore error codes per file."
-groups = ["static-analysis"]
-dependencies = [
-    "tomli<3.0.0,>=2.0.1; python_version < \"3.11\"",
-]
-files = [
-    {file = "pylint_per_file_ignores-1.3.2-py3-none-any.whl", hash = "sha256:4a2a2d7b88484ef1d1b1170029e542954f70efbab13ac3b977606ea5617d04c1"},
-    {file = "pylint_per_file_ignores-1.3.2.tar.gz", hash = "sha256:3c641f69c316770749a8a353556504dae7469541cdaef38e195fe2228841451e"},
-]
-
 [[package]]
 name = "pylint-plugin-utils"
 version = "0.8.2"
@@ -1804,29 +1791,29 @@ files = [
 
 [[package]]
 name = "ruff"
-version = "0.7.3"
+version = "0.8.0"
 requires_python = ">=3.7"
 summary = "An extremely fast Python linter and code formatter, written in Rust."
 groups = ["lint"]
 files = [
-    {file = "ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344"},
-    {file = "ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0"},
-    {file = "ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9"},
-    {file = "ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5"},
-    {file = "ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299"},
-    {file = "ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e"},
-    {file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29"},
-    {file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5"},
-    {file = "ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67"},
-    {file = "ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2"},
-    {file = "ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d"},
-    {file = "ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2"},
-    {file = "ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2"},
-    {file = "ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16"},
-    {file = "ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc"},
-    {file = "ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088"},
-    {file = "ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c"},
-    {file = "ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313"},
+    {file = "ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea"},
+    {file = "ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b"},
+    {file = "ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a"},
+    {file = "ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99"},
+    {file = "ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c"},
+    {file = "ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9"},
+    {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362"},
+    {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df"},
+    {file = "ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3"},
+    {file = "ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c"},
+    {file = "ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2"},
+    {file = "ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70"},
+    {file = "ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd"},
+    {file = "ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426"},
+    {file = "ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468"},
+    {file = "ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f"},
+    {file = "ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6"},
+    {file = "ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44"},
 ]
 
 [[package]]
@@ -1841,7 +1828,7 @@ files = [
 
 [[package]]
 name = "s3transfer"
-version = "0.10.3"
+version = "0.10.4"
 requires_python = ">=3.8"
 summary = "An Amazon S3 Transfer Manager"
 groups = ["default"]
@@ -1849,8 +1836,8 @@ dependencies = [
     "botocore<2.0a.0,>=1.33.2",
 ]
 files = [
-    {file = "s3transfer-0.10.3-py3-none-any.whl", hash = "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d"},
-    {file = "s3transfer-0.10.3.tar.gz", hash = "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c"},
+    {file = "s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e"},
+    {file = "s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7"},
 ]
 
 [[package]]
@@ -1939,13 +1926,13 @@ files = [
 
 [[package]]
 name = "sqlparse"
-version = "0.5.1"
+version = "0.5.2"
 requires_python = ">=3.8"
 summary = "A non-validating SQL parser."
 groups = ["default", "typing", "watchfiles"]
 files = [
-    {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"},
-    {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"},
+    {file = "sqlparse-0.5.2-py3-none-any.whl", hash = "sha256:e99bc85c78160918c3e1d9230834ab8d80fc06c59d03f8db2618f65f65dda55e"},
+    {file = "sqlparse-0.5.2.tar.gz", hash = "sha256:9e37b35e16d1cc652a2545f0997c1deb23ea28fa1f3eefe609eee3063c3b105f"},
 ]
 
 [[package]]
@@ -2000,7 +1987,7 @@ files = [
 
 [[package]]
 name = "tqdm"
-version = "4.67.0"
+version = "4.67.1"
 requires_python = ">=3.7"
 summary = "Fast, Extensible Progress Meter"
 groups = ["lint"]
@@ -2008,8 +1995,8 @@ dependencies = [
     "colorama; platform_system == \"Windows\"",
 ]
 files = [
-    {file = "tqdm-4.67.0-py3-none-any.whl", hash = "sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be"},
-    {file = "tqdm-4.67.0.tar.gz", hash = "sha256:fe5a6f95e6fe0b9755e9469b77b9c3cf850048224ecaa8293d7d2d31f97d869a"},
+    {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"},
+    {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"},
 ]
 
 [[package]]
diff --git a/pyproject.toml b/pyproject.toml
index f0cafc3d131a8b81c419630b31cf5b7949e5c1f5..9e4c869dfb62b52555721b72e47dabcd21e1f01c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -83,7 +83,6 @@ typing = [
 static-analysis = [
     "pylint>=3.3.1",
     "pylint-django>=2.6.1",
-    "pylint-per-file-ignores>=1.3.2",
 ]
 
 [tool.pdm.scripts]
diff --git a/requirements.dev.txt b/requirements.dev.txt
index 5a8bea86bc285d6212f7f943c2c0d07ac9349bac..7da485862cf49823ec2ef5f4c4e06a8dde9d9218 100644
--- a/requirements.dev.txt
+++ b/requirements.dev.txt
@@ -10,8 +10,8 @@ babel==2.16.0
 beautifulsoup4==4.12.3
 bleach==6.2.0
 blinker==1.9.0
-boto3==1.35.57
-botocore==1.35.57
+boto3==1.35.68
+botocore==1.35.68
 cachetools==5.5.0
 certifi==2024.8.30
 cffi==1.17.1; platform_python_implementation != "PyPy"
@@ -20,10 +20,10 @@ chardet==5.2.0
 charset-normalizer==3.4.0
 click==8.1.7
 colorama==0.4.6
-coverage==7.6.4
+coverage==7.6.8
 cryptography==43.0.3
 cssbeautifier==1.15.1
-debugpy==1.8.8
+debugpy==1.8.9
 defusedxml==0.7.1
 dep-logic==0.4.9
 dill==0.3.9; python_version >= "3.11"
@@ -55,7 +55,7 @@ freezegun==1.5.1
 gunicorn==23.0.0
 h11==0.14.0
 hishel==0.0.33
-httpcore==1.0.6
+httpcore==1.0.7
 httpx[socks]==0.27.2
 icecream==2.1.3
 identify==2.6.2
@@ -65,7 +65,7 @@ isort==5.13.2
 jinja2==3.1.4
 jmespath==1.0.1
 jsbeautifier==1.15.1
-json5==0.9.25
+json5==0.9.28
 jwcrypto==1.5.6
 lxml==5.3.0
 markdown-it-py==3.0.0
@@ -91,16 +91,15 @@ platformdirs==4.3.6
 pluggy==1.5.0
 pre-commit==4.0.1
 psycopg-binary==3.2.3; implementation_name != "pypy"
-psycopg-pool==3.2.3
+psycopg-pool==3.2.4
 psycopg[binary,pool]==3.2.3
 pycparser==2.22; platform_python_implementation != "PyPy"
-pydantic==2.9.2
-pydantic-core==2.23.4
+pydantic==2.10.1
+pydantic-core==2.27.1
 pygments==2.18.0
 pyjwt==2.9.0
 pylint==3.3.1
 pylint-django==2.6.1
-pylint-per-file-ignores==1.3.2
 pylint-plugin-utils==0.8.2
 pyproject-api==1.8.0
 pyproject-hooks==1.2.0
@@ -114,9 +113,9 @@ requests==2.31.0
 requests-file==1.5.1
 resolvelib==1.1.0
 rich==13.9.4
-ruff==0.7.3
+ruff==0.8.0
 rules==3.5
-s3transfer==0.10.3
+s3transfer==0.10.4
 segno==1.6.1
 sentry-sdk==2.17.0
 shellingham==1.5.4
@@ -124,11 +123,11 @@ six==1.16.0
 sniffio==1.3.1
 socksio==1.0.0
 soupsieve==2.6
-sqlparse==0.5.1
+sqlparse==0.5.2
 tomlkit==0.13.2
 tox==4.23.2
 tox-pdm==0.7.2
-tqdm==4.67.0
+tqdm==4.67.1
 truststore==0.10.0; python_version >= "3.10"
 types-pyyaml==6.0.12.20240917
 typing-extensions==4.12.2
diff --git a/requirements.txt b/requirements.txt
index 4d5e2a4460a0bb6a355cf8f4bf9975b1234ede8a..1467aba9ded099d2bcff8dfce66cd482ac459f7b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,8 +8,8 @@ babel==2.16.0
 beautifulsoup4==4.12.3
 bleach==6.2.0
 blinker==1.9.0
-boto3==1.35.57
-botocore==1.35.57
+boto3==1.35.68
+botocore==1.35.68
 certifi==2024.8.30
 cffi==1.17.1; platform_python_implementation != "PyPy"
 charset-normalizer==3.4.0
@@ -38,7 +38,7 @@ freezegun==1.5.1
 gunicorn==23.0.0
 h11==0.14.0
 hishel==0.0.33
-httpcore==1.0.6
+httpcore==1.0.7
 httpx[socks]==0.27.2
 idna==3.10
 installer==0.7.0
@@ -64,11 +64,11 @@ pdm==2.20.1
 pillow==11.0.0
 platformdirs==4.3.6
 psycopg-binary==3.2.3; implementation_name != "pypy"
-psycopg-pool==3.2.3
+psycopg-pool==3.2.4
 psycopg[binary,pool]==3.2.3
 pycparser==2.22; platform_python_implementation != "PyPy"
-pydantic==2.9.2
-pydantic-core==2.23.4
+pydantic==2.10.1
+pydantic-core==2.27.1
 pygments==2.18.0
 pyjwt==2.9.0
 pyproject-hooks==1.2.0
@@ -81,7 +81,7 @@ requests-file==1.5.1
 resolvelib==1.1.0
 rich==13.9.4
 rules==3.5
-s3transfer==0.10.3
+s3transfer==0.10.4
 segno==1.6.1
 sentry-sdk==2.17.0
 shellingham==1.5.4
@@ -89,7 +89,7 @@ six==1.16.0
 sniffio==1.3.1
 socksio==1.0.0
 soupsieve==2.6
-sqlparse==0.5.1
+sqlparse==0.5.2
 tomlkit==0.13.2
 truststore==0.10.0; python_version >= "3.10"
 typing-extensions==4.12.2
diff --git a/src/api/schedule.py b/src/api/schedule.py
index cf26a3c40645dca72214468a74a38440c28ee055..d0f10416a626a71f442165014d1f5297b82c0a3c 100644
--- a/src/api/schedule.py
+++ b/src/api/schedule.py
@@ -4,7 +4,7 @@ import logging
 import re
 from collections import OrderedDict
 from datetime import datetime, timedelta
-from typing import Optional, TYPE_CHECKING
+from typing import TYPE_CHECKING
 from uuid import UUID
 
 from lxml import etree as ET
@@ -91,7 +91,7 @@ class RoomDay:
 class ScheduleEncoder(json.JSONEncoder):
     tz = None
 
-    def encode_duration(self, duration: Optional[timedelta]) -> Optional[str]:
+    def encode_duration(self, duration: timedelta | None) -> str | None:
         """converts a python `timedelta` to the schedule xml timedelta string that represents this timedelta. ([d:]HH:mm)"""
 
         if duration is None:
diff --git a/src/api/serializers.py b/src/api/serializers.py
index f6a5ef970628ac7ce536c1bfb077dd660f40aad3..aa56d026e3d20cc78fe684addf6512f0281110e9 100644
--- a/src/api/serializers.py
+++ b/src/api/serializers.py
@@ -33,7 +33,7 @@ class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField):
         self.lookup_fields = kwargs.pop('lookup_fields', self.lookup_fields)
         super().__init__(*args, **kwargs)
 
-    def get_url(self, obj, view_name, request, format):
+    def get_url(self, obj, view_name, request, format):  # pylint: disable=redefined-builtin
         """
         Given an object, return the URL that hyperlinks to the object.
 
diff --git a/src/api/tests/badges/create_redeem_token.py b/src/api/tests/badges/create_redeem_token.py
index aa74f060b0375cb5f4022d56cff1e1f1d4ec41dc..28931971e31803126209a4c87296cb27e393da16 100644
--- a/src/api/tests/badges/create_redeem_token.py
+++ b/src/api/tests/badges/create_redeem_token.py
@@ -1,9 +1,9 @@
 import uuid
 from datetime import datetime
 from http import HTTPStatus
+from zoneinfo import ZoneInfo
 
 from rest_framework.authtoken.models import Token
-from zoneinfo import ZoneInfo
 
 from django.test import TestCase, override_settings
 from django.urls import reverse
diff --git a/src/api/tests/map.py b/src/api/tests/map.py
index 7261e596f05cd215e4e4ca9870cdf9a1c91ab706..9f6375eecc07b16dbecbf38cb9888c6da18bcb4f 100644
--- a/src/api/tests/map.py
+++ b/src/api/tests/map.py
@@ -1,7 +1,6 @@
 import json
 import uuid
 from datetime import datetime
-
 from zoneinfo import ZoneInfo
 
 from django.contrib.gis.geos import Point
diff --git a/src/api/tests/permissions.py b/src/api/tests/permissions.py
index 92fef7d754435b8bacbd57c7be073c0c93eb7e5b..87c9010ebc969fb1c338200eb04669074048f4e0 100644
--- a/src/api/tests/permissions.py
+++ b/src/api/tests/permissions.py
@@ -135,7 +135,7 @@ class ConferencePermissionTestCase(PermissionTestCase):
         self.conference = self.view.conference = Conference.objects.create(slug='conf', name='TestConf', is_public=True)
 
 
-class ConferenceDetectionTestCase(PermissionTestCase):
+class ConferenceDetectionTestCase(ConferencePermissionTestCase):
     def test_get_conference_view(self):
         self.assertEqual(ConferencePermission().get_conference(view=self.view), self.conference)
 
diff --git a/src/api/tests/schedule.py b/src/api/tests/schedule.py
index 3fa2c6d7e32b77ac512618798f806b370f897b64..5d3e5b045a5aab46e5e54c9515c0aae29f0724a8 100644
--- a/src/api/tests/schedule.py
+++ b/src/api/tests/schedule.py
@@ -2,9 +2,9 @@ import json
 import uuid
 import xml.etree.ElementTree as ET
 from datetime import datetime, timedelta
+from zoneinfo import ZoneInfo
 
 from rest_framework.authtoken.models import Token
-from zoneinfo import ZoneInfo
 
 from django.test import TestCase, override_settings
 from django.urls import reverse
diff --git a/src/api/views/__init__.py b/src/api/views/__init__.py
index 5758e553087046b1e6856d8ccf2cf742cca7f0c1..2e840c7ac53e1c48e7dd0ba2b1313f73c11e4a52 100644
--- a/src/api/views/__init__.py
+++ b/src/api/views/__init__.py
@@ -8,7 +8,7 @@ __all__ = [
 
 
 @api_view(['GET'])
-def api_root(request, format=None):
+def api_root(request, format=None):  # pylint: disable=redefined-builtin
     links = {
         'conference': {
             'info': reverse('api:conference-detail', request=request, format=format),
diff --git a/src/api/views/badges.py b/src/api/views/badges.py
index 066adb4786bbdfd7cf5d5aae67737a6cbc75aa57..41abd07579164cb59504dc3a6987da0cc723a88f 100644
--- a/src/api/views/badges.py
+++ b/src/api/views/badges.py
@@ -100,7 +100,7 @@ class RedeemBadgeMapTokenView(ConferenceSlugMixin, APIView):
 
         return super().initial(request, *args, **kwargs)
 
-    def post(self, request, *get, format=None, **kwargs):
+    def post(self, request, *get, format=None, **kwargs):  # pylint: disable=redefined-builtin
         if self.target_user is None or not self.target_user.is_in_conference(self.conference):
             return HttpResponse(status=404)
 
diff --git a/src/api/views/conferencemember.py b/src/api/views/conferencemember.py
index 795ea8136a6b837b0af8a299a3df92f47be6f6d1..29d3cc9d274899f6710f2c6ef9ebab8ea557e660 100644
--- a/src/api/views/conferencemember.py
+++ b/src/api/views/conferencemember.py
@@ -28,14 +28,14 @@ class AngelView(ConferenceSlugMixin, APIView):
 
         return super().initial(request, *args, **kwargs)
 
-    def get(self, request, *args, format=None, **kwargs):
+    def get(self, request, *args, format=None, **kwargs):  # pylint: disable=redefined-builtin
         data = {
             'active_angel': self.target.active_angel,
             'angel_types': self.target.roles,
         }
         return Response(data)
 
-    def post(self, request, *args, format=None, **kwargs):
+    def post(self, request, *args, format=None, **kwargs):  # pylint: disable=redefined-builtin
         data = request.data
 
         if 'active_angel' in data:
@@ -60,7 +60,7 @@ class WorkadventureView(ConferenceSlugMixin, APIView):
 
         return super().initial(request, *args, **kwargs)
 
-    def get(self, request, *args, format=None, **kwargs):
+    def get(self, request, *args, format=None, **kwargs):  # pylint: disable=redefined-builtin
         if self.target_user is None:
             return Response({'active': False}, status=status.HTTP_400_BAD_REQUEST)
 
@@ -74,7 +74,7 @@ class WorkadventureView(ConferenceSlugMixin, APIView):
         )
         return Response(result)
 
-    def post(self, request, *get, format=None, **kwargs):
+    def post(self, request, *get, format=None, **kwargs):  # pylint: disable=redefined-builtin
         if (u := self.target_user) is None:
             return Response({'error': 'No target user.'}, status=status.HTTP_400_BAD_REQUEST)
 
diff --git a/src/api/views/maps.py b/src/api/views/maps.py
index 41c83afe237adf4eea82e585cd31f9a987d96e5a..eba2387d775bb38af9f35b69c43c511dc70a6c03 100644
--- a/src/api/views/maps.py
+++ b/src/api/views/maps.py
@@ -59,7 +59,7 @@ class PoiExportView(ConferenceSlugMixin, APIView):
         return ConferenceExportCache.handle_http_request(
             request=request,
             conference=self.conference,
-            type=ConferenceExportCache.Type.MAP,
+            entry_type=ConferenceExportCache.Type.MAP,
             ident=cache_id,
             content_type='application/geo+json',
             result_func=lambda: json.dumps(self.get_geojson()),
@@ -113,7 +113,7 @@ class AssembliesExportView(ConferenceSlugMixin, APIView, metaclass=abc.ABCMeta):
         return ConferenceExportCache.handle_http_request(
             request=request,
             conference=self.conference,
-            type=ConferenceExportCache.Type.MAP,
+            entry_type=ConferenceExportCache.Type.MAP,
             ident=cache_id,
             content_type='application/geo+json',
             result_func=lambda: json.dumps(self.get_geojson()),
diff --git a/src/api/views/schedule.py b/src/api/views/schedule.py
index b728ff43977d803200de257ab8ea958ee3642aa4..31bc747186d3bb0be5aa07d57834a2241e6eb334 100644
--- a/src/api/views/schedule.py
+++ b/src/api/views/schedule.py
@@ -60,7 +60,7 @@ class BaseScheduleView(ConferenceSlugMixin, View):
         return ConferenceExportCache.handle_http_request(
             request=self.request,
             conference=self.conference,
-            type=ConferenceExportCache.Type.SCHEDULE,
+            entry_type=ConferenceExportCache.Type.SCHEDULE,
             ident=cache_id,
             content_type=lambda: 'application/json' if req_format == 'json' else 'text/xml',
             result_func=gen_data,
@@ -77,7 +77,7 @@ class ConferenceSchedule(BaseScheduleView):
     def get_cache_id(self, **kwargs):
         return ''
 
-    def get_events(self, filter=None, **kwargs):
+    def get_events(self, filter=None, **kwargs):  # pylint: disable=redefined-builtin
         queryset = (
             Event.objects.conference_accessible(conference=self.conference)
             .exclude(schedule_duration=None)
@@ -95,7 +95,7 @@ class AssemblySchedule(BaseScheduleView):
         assembly_id = self.request.resolver_match.kwargs.get('assembly')
         return f'assembly_{assembly_id}'
 
-    def get_events(self, filter=None):
+    def get_events(self, filter=None):  # pylint: disable=redefined-builtin
         assembly_id = self.request.resolver_match.kwargs.get('assembly')
         queryset = (
             Event.objects.conference_accessible(conference=self.conference)
@@ -113,7 +113,7 @@ class RoomSchedule(BaseScheduleView):
         room_id = self.request.resolver_match.kwargs.get('pk')
         return f'room-{room_id}'
 
-    def get_events(self, filter=None):
+    def get_events(self, filter=None):  # pylint: disable=redefined-builtin
         room_id = self.request.resolver_match.kwargs.get('pk')
         queryset = Event.objects.conference_accessible(conference=self.conference).filter(room_id=room_id).order_by('schedule_start')
         if filter:
@@ -126,11 +126,11 @@ class EventSchedule(ConferenceSlugMixin, APIView):
     authentication_classes = [authentication.TokenAuthentication]
     permission_classes = [IsApiUserOrReadOnly]
 
-    def get(self, request, pk, format=None, **kwargs):
+    def get(self, request, pk, format=None, **kwargs):  # pylint: disable=redefined-builtin
         event = Event.objects.associated_with_user(conference=self.conference, user=self.request.user, show_public=True).get(pk=pk)
         return Response(ScheduleEncoder().encode_event(event, self.conference.timezone))
 
-    def post(self, request, pk, format=None, **kwargs):
+    def post(self, request, pk, format=None, **kwargs):  # pylint: disable=redefined-builtin
         event = request.data
         if len(event) == 0:
             return Response({'error': 'No data.'}, status=400)
diff --git a/src/api/views/users.py b/src/api/views/users.py
index 371269fc96912613a7b18e92e41b891c5a40ac89..b210f908e88bb8961264432640153ea5c08b087d 100644
--- a/src/api/views/users.py
+++ b/src/api/views/users.py
@@ -18,7 +18,7 @@ from api.serializers import UserTimelineEntrySerializer
 
 
 @api_view(['GET'])
-def profile(request, format=None):
+def profile(request, format=None):  # pylint: disable=redefined-builtin
     u = request.user
 
     if not u.is_authenticated:
@@ -34,7 +34,7 @@ def profile(request, format=None):
 
 
 @api_view(['GET'])
-def friends(request, format=None):
+def friends(request, format=None):  # pylint: disable=redefined-builtin
     u = request.user
     if not u.is_authenticated:
         return Response([])
@@ -55,7 +55,7 @@ def friends(request, format=None):
 
 
 @api_view(['GET'])
-def badges(request, format=None):
+def badges(request, format=None):  # pylint: disable=redefined-builtin
     u = request.user
     if not u.is_authenticated:
         return Response([])
diff --git a/src/api/views/workadventure.py b/src/api/views/workadventure.py
index 4cc556758e114dd6822c9550e632c5115431ddd5..3b47e5cb6e43a7e00ab41dd449e546e6ef1a18c0 100644
--- a/src/api/views/workadventure.py
+++ b/src/api/views/workadventure.py
@@ -72,7 +72,7 @@ class MapServiceView(ConferenceSlugMixin, APIView):
     permission_classes = [IsConferenceService | IsSuperUser]
     required_service_classes = ['wa_mapservice']
 
-    def get(self, request, format=None):
+    def get(self, request, format=None):  # pylint: disable=redefined-builtin
         wa_rooms = []
         for room in Room.objects.filter(conference=self.conference, room_type=Room.RoomType.WORKADVENTURE).select_related('assembly'):
             if room.assembly is not None and room.assembly.state_assembly not in Assembly.PUBLIC_STATES:
@@ -82,7 +82,7 @@ class MapServiceView(ConferenceSlugMixin, APIView):
 
         return Response(wa_rooms)
 
-    def put(self, request, format=None):
+    def put(self, request, format=None):  # pylint: disable=redefined-builtin
         data = request.data
 
         status_code, response = self.handle_mapservice_push_request(self.conference, data)
@@ -272,7 +272,7 @@ class MapDetailView(ConferenceSlugMixin, APIView):
     permission_classes = [IsConferenceService | IsSuperUser]
     required_service_classes = ['wa_mapservice', 'wa_backend']
 
-    def get(self, request, conference, assembly, world, room=None, format=None):
+    def get(self, request, conference, assembly, world, room=None, format=None):  # pylint: disable=redefined-builtin
         a = self.conference.assemblies.get(slug=assembly)
 
         wa_room = a.rooms.get(room_type=Room.RoomType.WORKADVENTURE)  # TODO:, slug=world)
@@ -305,7 +305,7 @@ class UserInfoView(ConferenceSlugMixin, APIView):
     permission_classes = [IsConferenceService | IsSuperUser]
     required_service_classes = ['wa_backend']
 
-    def get(self, request, conference, uid, format=None):
+    def get(self, request, conference, uid, format=None):  # pylint: disable=redefined-builtin
         try:
             wa_session = WorkadventureSession.objects.get(conference=self.conference, pk=uid)
         except WorkadventureSession.DoesNotExist:
@@ -313,7 +313,7 @@ class UserInfoView(ConferenceSlugMixin, APIView):
 
         return Response(wa_session.export_userdata())
 
-    def post(self, request, conference, uid, format=None):
+    def post(self, request, conference, uid, format=None):  # pylint: disable=redefined-builtin
         try:
             wa_session = WorkadventureSession.objects.get(conference=self.conference, pk=uid)
         except WorkadventureSession.DoesNotExist:
@@ -335,7 +335,7 @@ class RegisterView(ConferenceSlugMixin, APIView):
     permission_classes = [IsConferenceService | IsSuperUser]
     required_service_classes = ['wa_backend']
 
-    def get(self, request, conference, token, format=None):
+    def get(self, request, conference, token, format=None):  # pylint: disable=redefined-builtin
         try:
             wa_session = WorkadventureSession.objects.get(conference=self.conference, token=token)
             if wa_session.token_expiry < timezone.now():
diff --git a/src/backoffice/forms/events.py b/src/backoffice/forms/events.py
index 1248e7e07815d518efb7351abd41272df097c05b..819ca7fd2f641e63dced69cd11987e383572380a 100644
--- a/src/backoffice/forms/events.py
+++ b/src/backoffice/forms/events.py
@@ -1,5 +1,5 @@
 import logging
-from typing import Any, Optional
+from typing import Any
 
 from django import forms
 from django.utils.translation import gettext_lazy as _
@@ -45,8 +45,8 @@ class EventForm(TranslatedFieldsForm):
         self,
         *args,
         conference,
-        assembly: Optional[Assembly] = None,
-        owner: Optional[PlatformUser] = None,
+        assembly: Assembly | None = None,
+        owner: PlatformUser | None = None,
         create: bool = False,
         publish: bool = False,
         **kwargs,
diff --git a/src/backoffice/forms/rooms.py b/src/backoffice/forms/rooms.py
index 5b94ea23c73702b5a53197f839667748e096acda..8e22909df8476d7079960b652bf4ae5c16b430b1 100644
--- a/src/backoffice/forms/rooms.py
+++ b/src/backoffice/forms/rooms.py
@@ -167,7 +167,7 @@ class AssemblyRoomEditForm(TranslatedFieldsForm):
         model = Room
         fields = ['name', 'slug', 'description', 'is_public_fahrplan', 'is_official', 'official_room_order', 'capacity']
 
-    def __init__(self, with_capacity=False, channel_staff=False, *args, **kwargs):
+    def __init__(self, *args, with_capacity=False, channel_staff=False, **kwargs):
         super().__init__(*args, **kwargs)
         self.fields['slug'].disabled = True
         if self.instance.room_type in Room.BACKEND_ROOMTYPES:
@@ -238,7 +238,7 @@ class AssemblyRoomEditWorkAdventureForm(TranslatedFieldsForm):
         model = Room
         fields = ['backend_status', 'backend_link', 'backend_link_branch']
 
-    def __init__(self, with_capacity=False, channel_staff=False, *args, **kwargs):
+    def __init__(self, *args, with_capacity=False, channel_staff=False, **kwargs):
         super().__init__(*args, **kwargs)
         self.fields['backend_status'].disabled = True
         self.fields['backend_link'].label = _('Room-workadventure_backend_link')
@@ -254,7 +254,7 @@ class AssemblyRoomEditHangarForm(TranslatedFieldsForm):
         model = Room
         fields = ['backend_status', 'backend_link']
 
-    def __init__(self, with_capacity=False, channel_staff=False, *args, **kwargs):
+    def __init__(self, *args, with_capacity=False, channel_staff=False, **kwargs):
         super().__init__(*args, **kwargs)
         self.fields['backend_status'].disabled = True
         self.fields['backend_link'].label = _('Room-hangar_backend_link')
diff --git a/src/backoffice/tests/auth.py b/src/backoffice/tests/auth.py
index 7881dd10700257c4713ca7d36e3c2347fe711618..f70e9381fa1110bed026b6ebbb7c531d29db1e4f 100644
--- a/src/backoffice/tests/auth.py
+++ b/src/backoffice/tests/auth.py
@@ -1,10 +1,14 @@
 import re
+from datetime import timedelta
 
+from django.contrib.auth.tokens import default_token_generator
 from django.contrib.auth.views import INTERNAL_RESET_SESSION_TOKEN
 from django.core import mail
 from django.http import SimpleCookie
 from django.test import override_settings
 from django.urls import reverse
+from django.utils.encoding import force_bytes
+from django.utils.http import urlsafe_base64_encode
 from django.utils.translation import override as override_locale
 
 from core.models import (
@@ -65,12 +69,6 @@ class PasswordResetTest(BackOfficeTestCase):
 
     @override_settings(LANGUAGE_CODE='en', AUTH_PASSWORD_VALIDATORS=[])
     def test_invalid_password_reset_link(self):
-        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
@@ -85,11 +83,6 @@ class PasswordResetTest(BackOfficeTestCase):
 
     @override_settings(LANGUAGE_CODE='en', AUTH_PASSWORD_VALIDATORS=[])
     def test_PasswordResetConfirmView(self):
-        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')
diff --git a/src/backoffice/tests/invitations/habitat.py b/src/backoffice/tests/invitations/habitat.py
index 021900da3f7cd9ba03acb06042464904a072d963..f911540364a14ca2cfae3545e8b43afca671fcc6 100644
--- a/src/backoffice/tests/invitations/habitat.py
+++ b/src/backoffice/tests/invitations/habitat.py
@@ -244,9 +244,10 @@ class InvitationHabitatSendViewTestCase(InvitationHabitatTestCase):
             self.assertEqual(Invitation.objects.count(), 1)
 
         # After the rejection has timed out, can send invitation again
-        with patch.object(timezone, 'now', return_value=(datetime(2042, 12, 27, 12, 34, 0, 0, tzinfo=UTC)) + timedelta(hours=4)), patch(
-            'core.models.invitation.datetime'
-        ) as mock_datetime:
+        with (
+            patch.object(timezone, 'now', return_value=(datetime(2042, 12, 27, 12, 34, 0, 0, tzinfo=UTC)) + timedelta(hours=4)),
+            patch('core.models.invitation.datetime') as mock_datetime,
+        ):
             mock_datetime.now.return_value = datetime(2042, 12, 27, 12, 34, 0, 0, tzinfo=UTC) + timedelta(hours=4)
             response = self.client.post(
                 reverse(
diff --git a/src/backoffice/views/assemblies/__init__.py b/src/backoffice/views/assemblies/__init__.py
index b5411010f26dfb9b0683f5c286da67dbbd391de7..3cc45e5da6afb2dbc608425b9abb8200a6172e1c 100644
--- a/src/backoffice/views/assemblies/__init__.py
+++ b/src/backoffice/views/assemblies/__init__.py
@@ -32,20 +32,20 @@ __all__ = [
     'AssemblyCreateView',
     'AssemblyDetailView',
     'AssemblyLinksUpdateView',
-    'AssemblyUpdateView',
     'AssemblyListView',
     'AssemblyParentLeaveView',
+    'AssemblyRoomCreateView',
+    'AssemblyRoomDeleteView',
+    'AssemblyRoomUpdateView',
+    'AssemblyUpdateView',
     'AuthAppView',
     'AuthGetTokenView',
     'AuthView',
     'MemberCreateView',
     'MemberListView',
     'MembersUpdateView',
-    'VouchersView',
-    'AssemblyRoomUpdateView',
-    'AssemblyRoomCreateView',
     'RoomLinkCreateView',
-    'RoomNotAvailableError',
     'RoomLinkDeleteView',
-    'AssemblyRoomDeleteView',
+    'RoomNotAvailableError',
+    'VouchersView',
 ]
diff --git a/src/backoffice/views/assemblies/assemblies.py b/src/backoffice/views/assemblies/assemblies.py
index 9bffb30f54cbe3cea190b8086b8c34f945e759e1..06bbe853723f05ace7d2f6c7e6bebb492cd8b090 100644
--- a/src/backoffice/views/assemblies/assemblies.py
+++ b/src/backoffice/views/assemblies/assemblies.py
@@ -402,7 +402,7 @@ class AssemblyLinksUpdateView(AssemblyMixin, View):
                 if link_created:
                     messages.success(request, gettext('assemblyedit_addedlink').format(linked_name=linkee.name))
                     logger.info(
-                        'Assembly "%(assembly_name)s" (%(assembly_pk)s): added link to "%(linkee)s" (%(linkee_pk)s), requested by {%(user)s.'
+                        'Assembly "%(assembly_name)s" (%(assembly_pk)s): added link to "%(linkee)s" (%(linkee_pk)s), requested by {%(user)s}.'
                         'Not from this assembly',
                         {'assembly_name': assembly.name, 'assembly_pk': assembly.pk, 'linkee': linkee, 'linkee_pk': linkee.pk, 'user': request.user.username},
                     )
diff --git a/src/backoffice/views/events/__init__.py b/src/backoffice/views/events/__init__.py
index 485c41059cb2ac663bd4defae33c9c4e0ae30bf9..e7159fba6d7ede9bbf294b128c9c1a49fc9fd7aa 100644
--- a/src/backoffice/views/events/__init__.py
+++ b/src/backoffice/views/events/__init__.py
@@ -12,12 +12,12 @@ from backoffice.views.events.self_organized_sessions import (
 )
 
 __all__ = [
-    'AssemblyEventListView',
     'AssemblyEventCreateView',
-    'AssemblyEventUpdateView',
     'AssemblyEventDeleteView',
+    'AssemblyEventListView',
+    'AssemblyEventUpdateView',
     'SelfOrganizedContentListView',
     'SoSCreateView',
-    'SoSUpdateView',
     'SoSDeleteView',
+    'SoSUpdateView',
 ]
diff --git a/src/backoffice/views/invitations/__init__.py b/src/backoffice/views/invitations/__init__.py
index 2cb8e3459de1a4f3ed9c0e6a2bbdb67af9ee1fb7..696ed9a8bc5185dbced1abe5769830dc7ae22f99 100644
--- a/src/backoffice/views/invitations/__init__.py
+++ b/src/backoffice/views/invitations/__init__.py
@@ -44,7 +44,7 @@ class InvitationSendView(CreateView):
 
 
 __all__ = [
-    'InvitationUpdateView',
     'InvitationListView',
     'InvitationSendView',
+    'InvitationUpdateView',
 ]
diff --git a/src/backoffice/views/mixins.py b/src/backoffice/views/mixins.py
index cb17f642f7b802b86ea08196addaf85d24524844..e6b187cf4b1b1b588744da4750112c060d419e70 100644
--- a/src/backoffice/views/mixins.py
+++ b/src/backoffice/views/mixins.py
@@ -73,10 +73,10 @@ class ConferenceRequiredMixinBase:
             return redirect('backoffice:conferences')
         return super().dispatch(request, *args, **kwargs)
 
-    def get_context_data(self, *args, **kwargs):
+    def get_context_data(self, **kwargs):
         # super() does not have to have get_context_data(), e.g. if it's a plain View
         if hasattr(super(), 'get_context_data'):
-            context = super().get_context_data(*args, **kwargs)
+            context = super().get_context_data(**kwargs)
         else:
             context = {}
 
@@ -243,8 +243,8 @@ class AssemblyMixinBase:
     def staff_mode(self):
         return self._staff_mode
 
-    def get_context_data(self, *args, **kwargs):
-        context = super().get_context_data(*args, **kwargs)
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
         context['assembly'] = assembly = self.assembly
 
         context['can_manage'] = can_manage = self.can_manage
diff --git a/src/backoffice/views/moderation/__init__.py b/src/backoffice/views/moderation/__init__.py
index efc4978be7f1e8d788e01837206a491c62cf9255..ec51583a5e27c7c0979abec38f0f13f2a98108ee 100644
--- a/src/backoffice/views/moderation/__init__.py
+++ b/src/backoffice/views/moderation/__init__.py
@@ -22,7 +22,7 @@ RE_UUID = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{
 class IndexView(ModerationAdminMixin, TemplateView):
     template_name = 'backoffice/moderation_index.html'
 
-    def get(self, request):
+    def get(self, request, *args, **kwargs):
         ctx = self.get_context_data()
         ctx['results'] = results = {}
 
@@ -34,21 +34,21 @@ class IndexView(ModerationAdminMixin, TemplateView):
 
 
 __all__ = [
-    'ModerationAdminMixin',
     'IndexView',
-    'ModerationAssemblyListView',
+    'ModerationAdminMixin',
     'ModerationAssemblyDetailView',
-    'ModerationEventListView',
-    'ModerationEventDetailView',
-    'ModerationBadgeListView',
+    'ModerationAssemblyListView',
     'ModerationBadgeDetailView',
-    'ModerationBoardListView',
+    'ModerationBadgeListView',
     'ModerationBoardDetailView',
-    'ModerationProjectListView',
+    'ModerationBoardListView',
+    'ModerationEventDetailView',
+    'ModerationEventListView',
     'ModerationProjectDetailView',
-    'ModerationUserListView',
+    'ModerationProjectListView',
     'ModerationUserDetailView',
+    'ModerationUserListView',
     'ModerationUserRenameView',
-    'ModerationWikiListView',
     'ModerationWikiDetailView',
+    'ModerationWikiListView',
 ]
diff --git a/src/backoffice/views/moderation/board_entry.py b/src/backoffice/views/moderation/board_entry.py
index 77b4030383712907c7bb869190993b3fa793417e..9113d34560812b362d4ed249acbbbf414fe78a8a 100644
--- a/src/backoffice/views/moderation/board_entry.py
+++ b/src/backoffice/views/moderation/board_entry.py
@@ -25,26 +25,26 @@ class ModerationBoardDetailView(ModerationAdminMixin, DetailView):
         return qs
 
     def post(self, request, *args, **kwargs):
-        object = self.get_object()
+        current_object = self.get_object()
         action = request.POST.get('action')
 
         # TODO: add log entries
         if action == 'hide':
-            object.hidden = True
-            object.save(update_fields=['hidden'])
-            messages.success(request, f'board entry forced hidden: #{object.id}')
+            current_object.hidden = True
+            current_object.save(update_fields=['hidden'])
+            messages.success(request, f'board entry forced hidden: #{current_object.id}')
 
         elif action == 'show':
-            object.hidden = False
-            object.save(update_fields=['hidden'])
-            messages.success(request, f'board entry is not forced hidden any more: #{object.id}')
+            current_object.hidden = False
+            current_object.save(update_fields=['hidden'])
+            messages.success(request, f'board entry is not forced hidden any more: #{current_object.id}')
 
         elif action == 'delete':
-            object.delete()
-            messages.success(request, f'board entry deleted: #{object.id}')
+            current_object.delete()
+            messages.success(request, f'board entry deleted: #{current_object.id}')
             return redirect('backoffice:moderation-board-list')
 
         else:
             messages.error(request, f'UNKNOWN ACTION: {action}')
 
-        return redirect('backoffice:moderation-board-detail', pk=object.pk)
+        return redirect('backoffice:moderation-board-detail', pk=current_object.pk)
diff --git a/src/backoffice/views/moderation/projects.py b/src/backoffice/views/moderation/projects.py
index ff6d0b69878dd3ceb7b413f31965ecc680f108e6..a358416ebe2c8fe30a339e68d27c30c28c66a0a7 100644
--- a/src/backoffice/views/moderation/projects.py
+++ b/src/backoffice/views/moderation/projects.py
@@ -26,31 +26,31 @@ class ModerationProjectDetailView(ModerationAdminMixin, DetailView):
         return qs
 
     def post(self, request, *args, **kwargs):
-        object = self.get_object()
+        current_object = self.get_object()
         action = request.POST.get('action')
 
         # TODO: add log entries
         if action == 'hide':
-            object.is_public = False
-            object.save(update_fields=['is_public'])
-            messages.success(request, f'project hidden: #{object.id}')
+            current_object.is_public = False
+            current_object.save(update_fields=['is_public'])
+            messages.success(request, f'project hidden: #{current_object.id}')
 
         if action == 'block':
-            object.blocked = True
-            object.save(update_fields=['blocked'])
-            messages.success(request, f'project blocked: #{object.id}')
+            current_object.blocked = True
+            current_object.save(update_fields=['blocked'])
+            messages.success(request, f'project blocked: #{current_object.id}')
 
         elif action == 'unblock':
-            object.blocked = False
-            object.save(update_fields=['blocked'])
-            messages.success(request, f'project unblocked: #{object.id}')
+            current_object.blocked = False
+            current_object.save(update_fields=['blocked'])
+            messages.success(request, f'project unblocked: #{current_object.id}')
 
         elif action == 'delete':
-            object.delete()
-            messages.success(request, f'project deleted: #{object.id}')
+            current_object.delete()
+            messages.success(request, f'project deleted: #{current_object.id}')
             return redirect('backoffice:moderation-project-list')
 
         else:
             messages.error(request, f'UNKNOWN ACTION: {action}')
 
-        return redirect('backoffice:moderation-project-detail', pk=object.pk)
+        return redirect('backoffice:moderation-project-detail', pk=current_object.pk)
diff --git a/src/backoffice/views/projects/__init__.py b/src/backoffice/views/projects/__init__.py
index de750f8627149942d192df799a8ab7d8530405dc..e658c6ebac5b1a329abef12ee7f91b9c4a2445f3 100644
--- a/src/backoffice/views/projects/__init__.py
+++ b/src/backoffice/views/projects/__init__.py
@@ -68,7 +68,7 @@ __all__ = [
     'AssemblyProjectListView',
     'AssemblyProjectUpdateView',
     'ProjectCreateView',
+    'ProjectDeleteView',
     'SelfOrganizedProjectListView',
     'SelfOrganizedProjectUpdateView',
-    'ProjectDeleteView',
 ]
diff --git a/src/core/admin.py b/src/core/admin.py
index d25f7458f212b8b2892cd0cae7bcf44f1f51c931..1494e08a694ed197a187173fc6569e4a7692f65a 100644
--- a/src/core/admin.py
+++ b/src/core/admin.py
@@ -226,10 +226,10 @@ class ConferenceNavigationItemInline(admin.TabularInline):
     def has_add_permission(self, request, obj):
         return False
 
-    def has_change_permission(self, request, obj):
+    def has_change_permission(self, request, obj=None):
         return False
 
-    def has_delete_permission(self, request, obj):
+    def has_delete_permission(self, request, obj=None):
         return False
 
 
diff --git a/src/core/forms/__init__.py b/src/core/forms/__init__.py
index de1590280aeb4452df61677b1e084c37a06369d8..f7d33eb1b1124c803a3855000949e2b10ce1f342 100644
--- a/src/core/forms/__init__.py
+++ b/src/core/forms/__init__.py
@@ -5,13 +5,13 @@ from core.forms.links import LinkForm, LinkFormSet
 from core.forms.projects import ProjectForm
 
 __all__ = [
-    'LoginForm',
-    'PasswordResetForm',
-    'RegistrationForm',
+    'ConferencePublicationForm',
+    'ConferenceRegistrationForm',
     'InvitationHabitatForm',
     'LinkForm',
     'LinkFormSet',
-    'ConferencePublicationForm',
-    'ConferenceRegistrationForm',
+    'LoginForm',
+    'PasswordResetForm',
     'ProjectForm',
+    'RegistrationForm',
 ]
diff --git a/src/core/integrations/bigbluebutton.py b/src/core/integrations/bigbluebutton.py
index 8c1326a098da9b9fd267b79cc602670afac4907d..374ab65f9838e137a0ee6e06d6f3bf6235bfefab 100644
--- a/src/core/integrations/bigbluebutton.py
+++ b/src/core/integrations/bigbluebutton.py
@@ -2,7 +2,6 @@ import logging
 import string
 from hashlib import sha1
 from random import SystemRandom
-from typing import Dict, Union
 from urllib.parse import quote, urlencode, urljoin
 from uuid import uuid4
 from xml.etree import ElementTree as ET
@@ -20,7 +19,7 @@ logger = logging.getLogger(__name__)
 PASSWORD_CHARS = string.ascii_letters + string.digits
 
 
-def _params_to_str(data: Dict[str, Union[str, int, bool]]):
+def _params_to_str(data: dict[str, str | int | bool]):
     res = {}
     for k, v in data.items():
         if isinstance(v, bool):
@@ -48,7 +47,8 @@ class BigBlueButtonIntegration:
         self._initial_presentation_url = initial_presentation_url
         self._session = requests.session()
 
-    def _send_request(self, resource: str, params: Dict[str, str] = {}, raw=False, post_body=None):
+    def _send_request(self, resource: str, params: dict[str, str] | None = None, raw=False, post_body=None):
+        params = params or {}
         encoded_params = urlencode(_params_to_str(params))
         hash_input = resource + encoded_params + self._api_token
         hash_input = hash_input.encode('utf-8')
@@ -89,7 +89,7 @@ class BigBlueButtonIntegration:
         return True
 
     def create_room(self, room: BackendMixin):
-        assert room is not None and (isinstance(room, Room) and room.room_type == Room.RoomType.BIGBLUEBUTTON or isinstance(room, Event))
+        assert room is not None and ((isinstance(room, Room) and room.room_type == Room.RoomType.BIGBLUEBUTTON) or isinstance(room, Event))
 
         if room.backend_status in {Room.BackendStatus.ACTIVE, Room.BackendStatus.FULL}:
             # room was already created, don't need to create it twice
@@ -175,7 +175,7 @@ class BigBlueButtonIntegration:
         return result
 
     def remove_room(self, room: BackendMixin):
-        assert room is not None and (isinstance(room, Room) and room.room_type == Room.RoomType.BIGBLUEBUTTON) or isinstance(room, Event)
+        assert (room is not None and (isinstance(room, Room) and room.room_type == Room.RoomType.BIGBLUEBUTTON)) or isinstance(room, Event)
 
         if room.backend_status not in {Room.BackendStatus.ACTIVE, Room.BackendStatus.FULL, Room.BackendStatus.INACTIVE}:
             return
@@ -202,7 +202,7 @@ class BigBlueButtonIntegration:
         return result
 
     def _room_status(self, room: BackendMixin, commit=True):
-        assert room is not None and (isinstance(room, Room) and room.room_type == Room.RoomType.BIGBLUEBUTTON) or isinstance(room, Event)
+        assert (room is not None and (isinstance(room, Room) and room.room_type == Room.RoomType.BIGBLUEBUTTON)) or isinstance(room, Event)
 
         if room.backend_status not in {Room.BackendStatus.ACTIVE, Room.BackendStatus.FULL, Room.BackendStatus.INACTIVE}:
             return False
@@ -229,7 +229,7 @@ class BigBlueButtonIntegration:
         return self._room_status(room, True)
 
     def _join_room(self, room: BackendMixin, user: PlatformUser, anonymous: bool = False, retrying: bool = False):
-        assert room is not None and (isinstance(room, Room) and room.room_type == Room.RoomType.BIGBLUEBUTTON) or isinstance(room, Event)
+        assert (room is not None and (isinstance(room, Room) and room.room_type == Room.RoomType.BIGBLUEBUTTON)) or isinstance(room, Event)
         assert user is not None
 
         create_room = False
diff --git a/src/core/integrations/workadventure.py b/src/core/integrations/workadventure.py
index 094bed765356d0f6fae4d68f81e1cc5d9ea671c9..461339ba1cb49c33ffc3e49b3b3913ec1920be6b 100644
--- a/src/core/integrations/workadventure.py
+++ b/src/core/integrations/workadventure.py
@@ -264,6 +264,7 @@ class WorkAdventureIntegration:
         linter_errors = None
         linter_missingassets = None
         linter_missingentrypoints = None
+        linter_exitgraph = None
         publish_timestamp = None
         publish_commit = None
         has_mapinfo = False
diff --git a/src/core/management/commands/create_conference.py b/src/core/management/commands/create_conference.py
index 7023a6969c9f5a953e665434e46b43e52b389616..c2c28b3116c75792c81332832a7555db3aa7acce 100644
--- a/src/core/management/commands/create_conference.py
+++ b/src/core/management/commands/create_conference.py
@@ -1,8 +1,8 @@
 from argparse import ArgumentTypeError, BooleanOptionalAction
 from datetime import datetime
+from zoneinfo import ZoneInfo
 
 from rich.console import Console
-from zoneinfo import ZoneInfo
 
 from django.contrib.auth.models import Group
 from django.core.management import call_command
@@ -336,7 +336,7 @@ def seed_conference(conf: Conference, console: Console, year: int = 0):
         )
 
     # create badge categories
-    badge_category_general, created = BadgeCategory.objects.get_or_create(
+    badge_category_general, _created = BadgeCategory.objects.get_or_create(
         slug='general',
         defaults={
             'name_de': 'Allgemein',
@@ -346,7 +346,7 @@ def seed_conference(conf: Conference, console: Console, year: int = 0):
             'description_en': 'Category for general badges',
         },
     )
-    badge_category_explore, created = BadgeCategory.objects.get_or_create(
+    badge_category_explore, _created = BadgeCategory.objects.get_or_create(
         slug='explore',
         defaults={
             'name_de': 'Erkunden',
@@ -356,7 +356,7 @@ def seed_conference(conf: Conference, console: Console, year: int = 0):
             'description_en': 'Category for badges related to discovering new things',
         },
     )
-    badge_category_help, created = BadgeCategory.objects.get_or_create(
+    badge_category_help, _created = BadgeCategory.objects.get_or_create(
         slug='help',
         defaults={
             'name_de': 'Hilfe',
diff --git a/src/core/management/commands/sanitize_database.py b/src/core/management/commands/sanitize_database.py
index 0b56489e4ad3ec4ccf8b78426d1ab96aab279d57..c9b268407ff695a13739b9ab62fd0229d5e35c5f 100644
--- a/src/core/management/commands/sanitize_database.py
+++ b/src/core/management/commands/sanitize_database.py
@@ -4,7 +4,8 @@ from django.conf import settings
 from django.core.management.base import BaseCommand
 from django.db.models import Max
 
-from core.models.assemblies import Assembly, AssemblyLogEntry, AssemblyMember
+from core.models.activitylog import ActivityLogEntry
+from core.models.assemblies import Assembly, AssemblyMember
 from core.models.badges import UserBadge
 from core.models.conference import (
     ConferenceMember,
@@ -60,8 +61,8 @@ class Command(BaseCommand):
         print('Assembly(technical_user:=None): ', end='', flush=True)
         print(Assembly.objects.exclude(technical_user=None).update(technical_user=None))
 
-        print('AssemblyLogEntry: ', end='', flush=True)
-        print_delete_stat(AssemblyLogEntry.objects.all().delete())
+        print('ActivityLogEntry: ', end='', flush=True)
+        print_delete_stat(ActivityLogEntry.objects.all().delete())
 
         print('ConferenceMembers: ', end='', flush=True)
         print_delete_stat(ConferenceMember.objects.all().delete())
diff --git a/src/core/markdown.py b/src/core/markdown.py
index f5af1efc229edcb2a804b0477b2e567547437240..06e3365b4e0f1f3949aed2a5aad7faad5eaba16d 100644
--- a/src/core/markdown.py
+++ b/src/core/markdown.py
@@ -1,6 +1,5 @@
 import html
 import re
-from typing import Optional, Tuple
 from urllib.parse import quote, urlparse
 
 import bleach
@@ -110,7 +109,7 @@ class MyHtmlRenderer(HTMLRenderer):
         result += '</div>\n'
         return result
 
-    def __init__(self, conf: 'conference.Conference', result: 'RenderResult', derefer_allowlist: bool = True, *extras, **kwargs):
+    def __init__(self, conf: 'conference.Conference', result: 'RenderResult', *extras, derefer_allowlist: bool = True, **kwargs):
         self.conf = conf
         self.result = result
         self.derefer_allowlist = derefer_allowlist
@@ -131,7 +130,7 @@ class MyHtmlRenderer(HTMLRenderer):
 
         return redirect_via_dereferer(url) if do_derefer else url
 
-    def handle_link(self, url: str) -> Tuple[str, str]:
+    def handle_link(self, url: str) -> tuple[str, str]:
         from .utils import resolve_internal_url
 
         # attempt resolving an internal URL
@@ -396,7 +395,7 @@ def refresh_linking_markdown(conf: 'conference.Conference', link_target):
 
 
 def compile_translated_markdown_fields(
-    obj: Model, conf: 'conference.Conference', field_name: str, dst_obj: Optional[Model] = None, dst_field_name: Optional[str] = None
+    obj: Model, conf: 'conference.Conference', field_name: str, dst_obj: Model | None = None, dst_field_name: str | None = None
 ) -> RenderResult:
     if dst_obj is None:
         dst_obj = obj
diff --git a/src/core/models/__init__.py b/src/core/models/__init__.py
index 15e500bcf527404b98c07484d50b58eca623da5d..7b37267d429446682446dada2212d09872d8edc1 100644
--- a/src/core/models/__init__.py
+++ b/src/core/models/__init__.py
@@ -27,8 +27,8 @@ from .voucher import Voucher, VoucherEntry
 from .workadventure import WorkadventureSession, WorkadventureTexture
 
 __all__ = [
-    'ActivityLogEntry',
     'ActivityLogChange',
+    'ActivityLogEntry',
     'Application',
     'Assembly',
     'AssemblyLink',
@@ -57,6 +57,7 @@ __all__ = [
     'Lock',
     'MapFloor',
     'MapPOI',
+    'MarkdownMeta',
     'MetaNavItem',
     'PlatformUser',
     'Project',
@@ -67,13 +68,12 @@ __all__ = [
     'ScheduleSourceImport',
     'ScheduleSourceMapping',
     'StaticPage',
-    'StaticPageRevision',
     'StaticPageNamespace',
-    'MarkdownMeta',
+    'StaticPageRevision',
     'TagItem',
-    'UserContact',
-    'UserCommunicationChannel',
     'UserBadge',
+    'UserCommunicationChannel',
+    'UserContact',
     'UserDereferrerAllowlist',
     'Voucher',
     'VoucherEntry',
diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py
index 2db742f2799441021ccd0f91e707636ecd9e95ff..c0d6f7b60a83b4d5ba72da3c64f6613276f09405 100644
--- a/src/core/models/assemblies.py
+++ b/src/core/models/assemblies.py
@@ -2,7 +2,7 @@ import json
 import logging
 import re
 from pathlib import Path
-from typing import Dict, Optional, TypeIs
+from typing import TypeIs
 from uuid import uuid4
 
 import rules
@@ -295,7 +295,7 @@ class Assembly(TaggedItemMixin, ActivityLogMixin, RulesModel):
 
         return hub_absolute('plainui:assembly', assembly_slug=self.slug)
 
-    def ensure_single_type_conference_match(self) -> Dict:
+    def ensure_single_type_conference_match(self) -> dict[str, tuple[bool, bool]]:
         """
         Checks if the conference only supports a single type of assembly
         and sets that field if it is not set.
@@ -474,7 +474,7 @@ class Assembly(TaggedItemMixin, ActivityLogMixin, RulesModel):
 
         return super().save(*args, update_fields=update_fields, **kwargs)
 
-    def prepare_mail_to_managers(self, subject, message) -> tuple[str, Optional[str], str]:
+    def prepare_mail_to_managers(self, subject, message) -> tuple[str, str | None, str]:
         from core.templatetags.hub_absolute import hub_absolute  # pylint: disable=import-outside-toplevel
 
         ctx = {
@@ -514,7 +514,7 @@ class Assembly(TaggedItemMixin, ActivityLogMixin, RulesModel):
             result.append(recipient.member.username)
         return result
 
-    def get_voucher_count(self, with_always_public: bool = True) -> Optional[int]:
+    def get_voucher_count(self, with_always_public: bool = True) -> int | None:
         from .voucher import Voucher
 
         entries = Voucher.objects.for_assembly(self, include_public_ones=False).count()
@@ -570,7 +570,7 @@ class AssemblyLinkManager(ConferenceManagerMixin['AssemblyLink']):
     staff_permissions = ['core.assembly_team']
     conference_filter = 'a__conference'
 
-    def apply_public_filter(self, queryset: 'QuerySet[AssemblyLink]', member: ConferenceMember) -> 'QuerySet[AssemblyLink]':
+    def apply_public_filter(self, queryset: 'QuerySet[AssemblyLink]', member: ConferenceMember | None = None) -> 'QuerySet[AssemblyLink]':
         return queryset.filter(Type__in=self.model.PUBLIC_TYPES)
 
 
diff --git a/src/core/models/badges.py b/src/core/models/badges.py
index fcee09d49089064bf53110fd6062524835558607..2bfb38e2e545b493b1ddbffcc3eb058f6ff8faaf 100644
--- a/src/core/models/badges.py
+++ b/src/core/models/badges.py
@@ -184,11 +184,10 @@ class Badge(models.Model):
     def is_public(self):
         return self.state == self.State.PUBLIC
 
-    def save(self, update_fields=None, *args, **kwargs):
-        if self.conference_id is None and self.issuing_assembly_id is not None:
-            self.conference_id = self.issuing_assembly.conference_id
-
-        elif self.conference_id != self.issuing_assembly.conference_id:
+    def save(self, *args, update_fields=None, **kwargs):
+        if not hasattr(self, 'conference') and hasattr(self, 'issuing_assembly'):
+            self.conference = self.issuing_assembly.conference
+        elif hasattr(self, 'conference') and self.conference != self.issuing_assembly.conference:
             raise ValidationError(_('Badge__issuing_assembly__wrong_conference'))
         if update_fields is None or 'description' in update_fields:
             render_results = compile_translated_markdown_fields(self, self.conference, 'description')
diff --git a/src/core/models/conference.py b/src/core/models/conference.py
index 1b4be0f04b2df1a2552ee6d51655018627d6439e..9bf0caa5f9f7921a9358f0a5da5a7ee93a1cb578 100644
--- a/src/core/models/conference.py
+++ b/src/core/models/conference.py
@@ -767,10 +767,10 @@ class ConferenceExportCache(models.Model):
         return super().save(*args, **kwargs)
 
     @classmethod
-    def _get_or_create_entry(cls, conference: Conference, type: Type, ident: str, result_func: typing.Callable[..., bytes]):
+    def _get_or_create_entry(cls, conference: Conference, entry_type: Type, ident: str, result_func: typing.Callable[..., bytes]):
         cache_entry, _ = cls.objects.get_or_create(
             conference=conference,
-            type=type,
+            type=entry_type,
             ident=ident,
             defaults={'needs_regeneration': True},
         )
@@ -782,14 +782,14 @@ class ConferenceExportCache(models.Model):
         return cache_entry
 
     @classmethod
-    def fetch_entry(cls, conference: Conference, type: Type, ident: str, result_func, as_text: bool = False):
-        cache_entry = cls._get_or_create_entry(conference, type, ident, result_func)
+    def fetch_entry(cls, conference: Conference, entry_type: Type, ident: str, result_func, as_text: bool = False):
+        cache_entry = cls._get_or_create_entry(conference, entry_type, ident, result_func)
         content = cache_entry.data
         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):
-        cache_entry = cls._get_or_create_entry(conference, type, ident, result_func)
+    def handle_http_request(cls, request: HttpRequest, conference: Conference, entry_type: Type, ident: str, content_type, result_func):
+        cache_entry = cls._get_or_create_entry(conference, entry_type, ident, result_func)
 
         headers = {
             'Content-Type': content_type if isinstance(content_type, str) else content_type(),
diff --git a/src/core/models/messages.py b/src/core/models/messages.py
index edcb87f3e5aa5710bcf96db1c2a42a334ae71da7..28e424ce9bac8c3be832245d62b3b2f2ed72875c 100644
--- a/src/core/models/messages.py
+++ b/src/core/models/messages.py
@@ -87,7 +87,7 @@ class DirectMessage(models.Model):
         ctx = {
             'conference': self.conference,
             'subject': self.subject,
-            'message': self.message,
+            'message': self.body,
             'timestamp': self.timestamp,
             'sender': str(self.sender) if self.sender is not None else None,
             'recipient': str(self.recipient) if self.recipient is not None else None,
@@ -96,7 +96,7 @@ class DirectMessage(models.Model):
         body_text = render_to_string('core/directmessage.txt', ctx)
 
         if settings.SUPPORT_HTML_MAILS:
-            ctx['message_html'] = render_markdown(self.body)
+            ctx['message_html'] = render_markdown(self.conference, self.body)
             body_html = render_to_string('core/directmessage.html', ctx)
         else:
             body_html = None
diff --git a/src/core/models/pages.py b/src/core/models/pages.py
index 1d5c480f93fddee344d22b24b9978c0cecbd8b1f..257ca1333fcafc5d67617ae4ff9a17e657390425 100644
--- a/src/core/models/pages.py
+++ b/src/core/models/pages.py
@@ -1,7 +1,6 @@
 import logging
 import re
 import uuid
-from typing import Dict, Optional, Tuple
 
 from urllib3.util import Url, parse_url
 
@@ -65,7 +64,7 @@ class StaticPageNamespace(models.Model):
         return self.prefix
 
     @staticmethod
-    def _extract_doc_metadata(markup) -> Tuple[Dict[str, str], str]:
+    def _extract_doc_metadata(markup) -> tuple[dict[str, str], str]:
         if markup.startswith('---\n'):
             metadata_raw, content = markup[4:].split('---\n', maxsplit=1)
             metadata = {}
@@ -174,7 +173,7 @@ class StaticPageNamespace(models.Model):
 
 
 class StaticPageManager(models.Manager):
-    def accessible_by_user(self, user: PlatformUser, conference: Conference, language: Optional[str] = ''):
+    def accessible_by_user(self, user: PlatformUser, conference: Conference, language: str | None = ''):
         if language is not None:
             qs = self.get_queryset().filter(language=language, conference=conference)
         else:
@@ -195,7 +194,7 @@ class StaticPageManager(models.Manager):
 
         return qs.filter(public_revision__gt=0).filter(pages_filters)
 
-    def get_editable_page(self, user: PlatformUser, conference: Conference, language: str, slug: str, check=False) -> Tuple[Optional['StaticPage'], bool]:
+    def get_editable_page(self, user: PlatformUser, conference: Conference, language: str, slug: str, check=False) -> tuple['StaticPage | None', bool]:
         """
         Request to edit the Static Page with slug `slug` in the context of `user` and `conference` and in the language `langguage`.
 
@@ -327,7 +326,7 @@ class StaticPage(models.Model):
     def newest_revision(self):
         try:
             return self.revisions.order_by('-revision').first()
-        except models.DoesNotExist:
+        except StaticPageRevision.DoesNotExist:
             return None
 
     def clean(self, *args, **kwargs):
diff --git a/src/core/models/rooms.py b/src/core/models/rooms.py
index 8990529c366aa65353ffc2291e48ec2b67f9cd3d..e471f98450e89cca2f7339e1f38b5dc28a0d6e06 100644
--- a/src/core/models/rooms.py
+++ b/src/core/models/rooms.py
@@ -1,7 +1,7 @@
 import uuid
 from datetime import datetime
 from pathlib import Path
-from typing import Optional, TypeIs
+from typing import TypeIs
 from uuid import uuid4
 
 from django.conf import settings
@@ -48,7 +48,7 @@ class RoomManager(ConferenceManagerMixin['Room']):
             Q(assembly__state_assembly__in=Assembly.PUBLIC_STATES) | Q(assembly__state_channel__in=Assembly.PUBLIC_STATES)
         )
 
-    def assignable_in_timeframe(self, conference: Conference, start: datetime, end: datetime, assembly: Optional[Assembly] = None, is_sos: bool = False):
+    def assignable_in_timeframe(self, conference: Conference, start: datetime, end: datetime, assembly: Assembly | None = None, is_sos: bool = False):
         """
         Fetch all rooms which are bookable for events in the given assembly (if any), in the given timeframe.
         Optionally, include rooms which are free for SOS usage.
@@ -450,6 +450,7 @@ class RoomShare(models.Model):
 
 
 class RoomLinkManager(ConferenceManagerMixin['RoomLink']):
+    # TODO: Add public filter when switching to querysets
     staff_permissions = ['core.assembly_team']
     conference_filter = 'room__assembly__conference'
     assembly_filter = 'room__assembly'
diff --git a/src/core/models/schedules.py b/src/core/models/schedules.py
index e712f8b4294e6b221e8457371633c804f55e562c..3e394cb9cfd7e64251ac33efe66eb9f6cef601fa 100644
--- a/src/core/models/schedules.py
+++ b/src/core/models/schedules.py
@@ -1,7 +1,7 @@
 import logging
 from datetime import timedelta
 from hashlib import sha1
-from typing import TYPE_CHECKING, Dict, List, Optional
+from typing import TYPE_CHECKING
 from uuid import UUID, uuid4
 
 from django.core.exceptions import ObjectDoesNotExist
@@ -92,7 +92,7 @@ class ScheduleSource(models.Model):
         """
 
         # a frequency of 0 means: don't import automatically
-        if self.import_frequency.total_seconds == 0:
+        if self.import_frequency.total_seconds() == 0:
             return False
 
         # no imports yet? we're due!
@@ -104,7 +104,7 @@ class ScheduleSource(models.Model):
         return timespan_since_last_import >= self.import_frequency
 
     @property
-    def latest_import(self) -> Optional['ScheduleSourceImport']:
+    def latest_import(self) -> 'ScheduleSourceImport | None':
         result = self.imports.order_by('-start').first()
         return result
 
@@ -125,8 +125,8 @@ class ScheduleSource(models.Model):
     def _get_or_create_speaker(
         self,
         name: str,
-        mail_guid: Optional[str | UUID] = None,
-        addresses: Optional[List[str]] = None,
+        mail_guid: str | UUID | None = None,
+        addresses: list[str] | None = None,
     ):
         if not name:
             raise ValueError('You need to provide a name for the speaker.')
@@ -448,7 +448,7 @@ class ScheduleSource(models.Model):
             ).values_list('local_id', flat=True)
         )
 
-        def speaker_lookup(speaker_info: Dict[str, str]):
+        def speaker_lookup(speaker_info: dict[str, str]):
             """
             Try to match the given speaker dict to a PlatformUser, if necessary creating a virtual one in the process.
             Returns None if the speaker shall be skipped (explicitly, using ScheduleSourceMapping.skip=True).
@@ -562,7 +562,7 @@ class ScheduleSource(models.Model):
                     from_dict_args={
                         'allow_kind': self.assembly.is_official if self.assembly else False,  # TODO: lookup assembly's room if not given
                         'allow_track': allow_track,  # TODO
-                        'room_lookup': lambda r_source_id: rooms.get(r_source_id),
+                        'room_lookup': rooms.get,
                         'speaker_lookup': speaker_lookup,
                     },
                 )
diff --git a/src/core/models/users.py b/src/core/models/users.py
index 495500edd4384f407c78a27d4d3c6e7b7db3b7f6..65d89795a317dd8919f34ef60688b26a7b162e09 100644
--- a/src/core/models/users.py
+++ b/src/core/models/users.py
@@ -285,19 +285,19 @@ class PlatformUser(AbstractUser):
         """Counterpart to get_avatar_json() to update the settings."""
 
         MIN_AVATAR_INDEX = -1
-        """The lowest allowed avatar index for WorkAdventure, probably -1 (custom avatar)."""
+        # The lowest allowed avatar index for WorkAdventure, probably -1 (custom avatar).
 
         MAX_AVATAR_INDEX = 42
-        """The largest allowed avatar index for WorkAdventure."""
+        # The largest allowed avatar index for WorkAdventure.
 
         CUSTOM_LENGTH = 6
-        """The length of the custom avatar array, these correspond to WorkAdventure's custom avatar selection window's row count."""
+        # The length of the custom avatar array, these correspond to WorkAdventure's custom avatar selection window's row count.
 
         MIN_CUSTOM_INDEX = 0
-        """The minimum value for each element in the custom avatar spec's array."""
+        # The minimum value for each element in the custom avatar spec's array.
 
         MAX_CUSTOM_INDEX = 100
-        """The maximum value for each element in the custom avatar spec's array."""
+        # The maximum value for each element in the custom avatar spec's array.
 
         info = self.avatar_config or {}
         _UNSET = object()
diff --git a/src/core/models/voucher.py b/src/core/models/voucher.py
index b6f437a5091627b4d4738aff6e01d9d45bd846a3..6f5d007cb65b58cfd81bfadb817eb527400fdea9 100644
--- a/src/core/models/voucher.py
+++ b/src/core/models/voucher.py
@@ -1,5 +1,4 @@
 import logging
-from typing import List
 from uuid import uuid4
 
 from django.core.exceptions import ValidationError
@@ -134,7 +133,7 @@ class Voucher(models.Model):
         return html
 
     @classmethod
-    def do_auto_assignments(cls, conference: Conference = None, assemblies: List[Assembly] | None = None) -> int:
+    def do_auto_assignments(cls, conference: Conference = None, assemblies: list[Assembly] | None = None) -> int:
         qs = cls.objects.filter(conference=conference) if conference is not None else cls.objects.all()
         qs = qs.filter(enabled=True)
         qs = qs.filter(target__in=Voucher.ASSEMBLY_TARGETS)
@@ -149,7 +148,7 @@ class Voucher(models.Model):
 
         return total
 
-    def do_auto_assignment(self, assemblies: List[Assembly]) -> int:
+    def do_auto_assignment(self, assemblies: list[Assembly]) -> int:
         if self.target not in self.ASSEMBLY_TARGETS:
             raise NotImplementedError('Auto-Assignment of non assemblies/channels not implemented yet.')
 
diff --git a/src/core/paginators.py b/src/core/paginators.py
index 4c60dd23e0186fbbcfa898860da13b14a9992092..addde934f9c8c8ea2abeb5e3c5c864844f5b501f 100644
--- a/src/core/paginators.py
+++ b/src/core/paginators.py
@@ -12,7 +12,7 @@ class AlphabetPaginator(Paginator):
 
     def __init__(self, *args, on: str | None = None, **kwargs) -> None:
         if on is None:
-            ImproperlyConfigured('on parameter required for NamePaginator')
+            raise ImproperlyConfigured('on parameter required for NamePaginator')
         self.on = on
         super().__init__(*args, **kwargs)
         self.item_dict = {}
diff --git a/src/core/schedules/base.py b/src/core/schedules/base.py
index 7d3d5cc01b4c03fcefdc7aec83a40c79ad6c4359..7735ba89ee0c2d2bfccceb47de4f512bc436042f 100644
--- a/src/core/schedules/base.py
+++ b/src/core/schedules/base.py
@@ -7,7 +7,8 @@ from django.utils import timezone
 from django.utils.module_loading import import_string
 
 
-def filter_additional_data(data: dict, computed_fields={}) -> dict:
+def filter_additional_data(data: dict, computed_fields: dict | None = None) -> dict:
+    computed_fields = computed_fields or {}
     return {
         k: v for k, v in data.items() if (v and k not in ['guid', 'room', 'start', 'date', 'duration', 'title', 'abstract', 'description', 'language'])
     } | computed_fields
diff --git a/src/core/search.py b/src/core/search.py
index baf83f2c1a72da3b955b7d3b618e94f8819563cb..23f5f663ffe6780cafee76782c3af25ae893c776 100644
--- a/src/core/search.py
+++ b/src/core/search.py
@@ -1,5 +1,5 @@
 import shlex
-from typing import Iterator, Union
+from collections.abc import Iterator
 
 from django.contrib.postgres.search import SearchQuery
 
@@ -12,7 +12,7 @@ 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]]:
+) -> Iterator[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.
diff --git a/src/core/sso.py b/src/core/sso.py
index 1a70b5a9e87424e3c692f25f0e693ca126c02a30..e723ab7326160595a2853f5ac6bf4efb1b5ca477 100644
--- a/src/core/sso.py
+++ b/src/core/sso.py
@@ -82,7 +82,7 @@ class OAuth2Scopes(BaseScopes):
             logger.warning('OAuth2Scopes.get_available_scopes() called for unexpected application user_type: %s', application.id)
         return result
 
-    def get_available_scopes(self, application=None, request=None, *args, **kwargs):
+    def get_available_scopes(self, *args, application=None, request=None, **kwargs):
         """
         Return a list of scopes available for the current application/request.
 
@@ -103,7 +103,7 @@ class OAuth2Scopes(BaseScopes):
 
         return []
 
-    def get_default_scopes(self, application=None, request=None, *args, **kwargs):
+    def get_default_scopes(self, *args, application=None, request=None, **kwargs):
         """
         Return a list of the default scopes for the current application/request.
         This MUST be a subset of the scopes returned by `get_available_scopes`.
diff --git a/src/core/templatetags/hub_absolute.py b/src/core/templatetags/hub_absolute.py
index f28467ad3afdf46afb004adffd7914b3f5380652..301aee5e863ed8fe3f28a6350360e1fa7f356f4d 100644
--- a/src/core/templatetags/hub_absolute.py
+++ b/src/core/templatetags/hub_absolute.py
@@ -1,5 +1,3 @@
-from typing import Optional
-
 from django.conf import settings
 from django.conf.urls.i18n import i18n_patterns
 from django.contrib import admin
@@ -56,7 +54,7 @@ def hub_absolute(url: str, *args, i18n: bool = True, lang: str | None = None, qu
         - *args:
           Positional arguments to pass to the URL resolver.
         - i18n (bool, optional): Whether to use internationalization. Defaults to True.
-        - lang (Optional[str], optional): Language code to switch to. Defaults to None.
+        - lang (str | None, optional): Language code to switch to. Defaults to None.
         - query_string (str, optional): Query string to append to the URL. Defaults to ''.
         - **kwargs: Keyword arguments to pass to the URL resolver.
 
@@ -86,14 +84,14 @@ def hub_absolute(url: str, *args, i18n: bool = True, lang: str | None = None, qu
 
 
 @register.simple_tag
-def hub_absolute_self(request: HttpRequest, i18n: bool = True, lang: Optional[str] = None):
+def hub_absolute_self(request: HttpRequest, i18n: bool = True, lang: str | None = None):
     """
     Generates an absolute URL for the current requests url.
 
     ### Args:
         - request (HttpRequest): The HTTP request object.
         - i18n (bool, optional): Flag to include internationalization in the URL. Defaults to True.
-        - lang (Optional[str], optional): Language code to include in the URL. Defaults to None.
+        - lang (str | None, optional): Language code to include in the URL. Defaults to None.
 
     ### Returns:
         - str: The absolute URL for the current requests url.
diff --git a/src/core/tests/exportcache.py b/src/core/tests/exportcache.py
index 72e57d7e46465b636be9dc6a0995b81b8985be14..a62ffc4c80d1f54d0caeb04b89469faf69061009 100644
--- a/src/core/tests/exportcache.py
+++ b/src/core/tests/exportcache.py
@@ -1,7 +1,6 @@
-from datetime import datetime, timedelta
+from datetime import UTC, datetime, timedelta
 
 from django.test import TestCase, override_settings
-from django.utils import timezone
 from django.utils.timezone import now
 
 from core.models.assemblies import Assembly
@@ -13,8 +12,8 @@ class ScheduleTests(TestCase):
         self.conference = Conference(
             slug='foo',
             name='Foo Conference',
-            start=datetime(2021, 12, 1, 9, 0, 0, tzinfo=timezone.utc),
-            end=datetime(2021, 12, 31, 15, 00, 00, tzinfo=timezone.utc),
+            start=datetime(2021, 12, 1, 9, 0, 0, tzinfo=UTC),
+            end=datetime(2021, 12, 31, 15, 00, 00, tzinfo=UTC),
         )
         self.conference.save()
 
diff --git a/src/core/utils.py b/src/core/utils.py
index 4df1eea55b26faf5eb433e044d4f64ae1dcc5d54..a39e515cbdd1f0560c81d6e65c6943f7c8adecf4 100644
--- a/src/core/utils.py
+++ b/src/core/utils.py
@@ -9,7 +9,6 @@ from datetime import UTC, datetime, timedelta
 from io import BytesIO
 from pathlib import Path
 from string import ascii_letters, digits
-from typing import Dict, List, Optional, Tuple, Union
 from urllib.parse import parse_qs, urlparse, urlunparse
 
 import requests
@@ -21,7 +20,7 @@ from django.utils.html import strip_tags
 logger = logging.getLogger(__name__)
 
 
-def scheme_and_netloc_from_url(url: str) -> Optional[str]:
+def scheme_and_netloc_from_url(url: str) -> str | None:
     parsed = urlparse(url)
     if not parsed.netloc:
         return None
@@ -30,7 +29,7 @@ def scheme_and_netloc_from_url(url: str) -> Optional[str]:
     return scheme + '://' + parsed.netloc
 
 
-def url_in_allowlist(scheme_and_netloc: str, patterns: List[Union[re.Pattern, str]]) -> bool:
+def url_in_allowlist(scheme_and_netloc: str, patterns: list[re.Pattern | str]) -> bool:
     """
     Checks whether the given scheme_and_netloc is present in the given list of patterns.
     This is used in dereferrer handling and (optionally) markdown generation.
@@ -153,7 +152,7 @@ def mask_url(url):
     return urlunparse(masked)
 
 
-def resolve_internal_url(url: str, fallback_as_is: bool = True) -> Optional[str]:
+def resolve_internal_url(url: str, fallback_as_is: bool = True) -> str | None:
     """
     Resolves special URLs like
      - conference://url_resolver_name
@@ -195,9 +194,9 @@ def resolve_internal_url(url: str, fallback_as_is: bool = True) -> Optional[str]
     return url if fallback_as_is else None
 
 
-def download_from_url(url: str) -> Tuple[str, bytes]:
+def download_from_url(url: str) -> tuple[str, bytes]:
     # let requests library fetch the URL
-    r = requests.get(url)
+    r = requests.get(url, timeout=30)
 
     # bail out if response is not 200
     r.raise_for_status()
@@ -323,7 +322,7 @@ class GitRepo:
 
         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
diff --git a/src/core/validators.py b/src/core/validators.py
index d7750f148b048c619d768f61796a184d2f1c8628..d288ef9c886111569de90f3bb1a3970dfe1b33e8 100644
--- a/src/core/validators.py
+++ b/src/core/validators.py
@@ -102,7 +102,7 @@ class ImageDimensionValidator:
         if errors:
             errors.append(
                 ValidationError(
-                    _('Validation__image_dimensions ' '%(min_width)s %(min_height)s ' '%(max_width)s %(max_height)s'),
+                    _('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],
diff --git a/src/core/views/__init__.py b/src/core/views/__init__.py
index 627b74ea625b0fe66500626617a9700d6b30829d..76c258a3b05ed6086ca4df9b67949398dc280a50 100644
--- a/src/core/views/__init__.py
+++ b/src/core/views/__init__.py
@@ -1,9 +1,15 @@
-from core.views.auth import BaseLoginView, BasePasswordResetConfirmView, BasePasswordResetView, BaseRegistrationActivationView, BaseRegistrationView
-
-all = (
+from core.views.auth import (
     BaseLoginView,
+    BasePasswordResetConfirmView,
+    BasePasswordResetView,
     BaseRegistrationActivationView,
     BaseRegistrationView,
-    BasePasswordResetView,
-    BasePasswordResetConfirmView,
+)
+
+__all__ = (
+    'BaseLoginView',
+    'BasePasswordResetConfirmView',
+    'BasePasswordResetView',
+    'BaseRegistrationActivationView',
+    'BaseRegistrationView',
 )
diff --git a/src/hub/logging_utils.py b/src/hub/logging_utils.py
index f2040793b54e6cbe6dd25beed0e3ca8b9355e6ff..6e311c51192b467188b7dfaf1cb69087255385d7 100644
--- a/src/hub/logging_utils.py
+++ b/src/hub/logging_utils.py
@@ -6,7 +6,6 @@ import logging
 from datetime import datetime
 from logging import LogRecord
 from pathlib import Path
-from typing import Optional
 
 from dateutil import tz
 from rich.console import ConsoleRenderable
@@ -81,14 +80,14 @@ class HubLoggingHandler(RichHandler):
         self,
         *,
         record: LogRecord,
-        traceback: Optional[Traceback],
+        traceback: Traceback | None,
         message_renderable: 'ConsoleRenderable',
     ) -> 'ConsoleRenderable':
         """Render log for display.
 
         Args:
             record (LogRecord): logging Record.
-            traceback (Optional[Traceback]): Traceback instance or None for no Traceback.
+            traceback (Traceback | None): Traceback instance or None for no Traceback.
             message_renderable (ConsoleRenderable): Renderable (typically Text) containing log message contents.
 
         Returns:
diff --git a/src/hub/settings/base.py b/src/hub/settings/base.py
index 192e7da89b6ad074f6031883538dfb4a778864b1..1c4d6482e7febf1f186368b549712a29e4884c34 100644
--- a/src/hub/settings/base.py
+++ b/src/hub/settings/base.py
@@ -173,7 +173,7 @@ DEBUG = env('DJANGO_DEBUG') == 'I_KNOW_WHAT_I_AM_DOING'
 ALLOWED_HOSTS = env('ALLOWED_HOSTS')
 
 # read application version from marker file
-with BASE_DIR.joinpath('version.json').open() as version_file:
+with BASE_DIR.joinpath('version.json').open(encoding='UTF-8') as version_file:
     APP_VERSION_INFO = json.load(version_file)
 
 # Application definition
diff --git a/src/hub/settings/build.py b/src/hub/settings/build.py
index bda3e5fa7ebeb64d1d43241c888b3d226e3cbb8a..6c4b906cef6c81389576fca9eb742b2ecd5c0e66 100644
--- a/src/hub/settings/build.py
+++ b/src/hub/settings/build.py
@@ -1,4 +1,4 @@
-from hub.settings.base import *  # noqa: F403
+from hub.settings.base import *  # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import
 
 SECRET_KEY = 'BUILD_KEY'
 
diff --git a/src/hub/settings/default.py b/src/hub/settings/default.py
index 61f83b91bc2d4a2e15e11ce418f1f5724c549449..f7c1ee2ba33cd603c778d9cf26a0ce3034a4f5ed 100644
--- a/src/hub/settings/default.py
+++ b/src/hub/settings/default.py
@@ -2,7 +2,7 @@ import environ
 
 from django.urls import reverse_lazy
 
-from hub.settings.base import *  # noqa: F403
+from hub.settings.base import *  # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import
 
 default_env = environ.FileAwareEnv(
     # set casting, default value
@@ -61,10 +61,10 @@ assert SECRET_KEY is None or not SECRET_KEY.startswith('/'), (
 
 # Local Settings
 try:
-    from hub.local_settings import *  # noqa: F403,F401
+    from hub.local_settings import *  # noqa: F403,F401 # pylint: disable=wildcard-import,unused-wildcard-import
 except ImportError:
     try:
-        from hub.settings.local_settings import *  # noqa: F403,F401
+        from hub.settings.local_settings import *  # noqa: F403,F401 # pylint: disable=wildcard-import,unused-wildcard-import
     except ImportError:
         print('Unable to load (optional) local_settings.py.')
 
@@ -72,12 +72,12 @@ except ImportError:
 if DEBUG and SECRET_KEY is None:  # noqa: F405
     SECRET_FILE = BASE_DIR.joinpath('hub', '.settings.secret')  # noqa: F405
     try:
-        with SECRET_FILE.open() as secret_file:
+        with SECRET_FILE.open(encoding='UTF-8') as secret_file:
             SECRET_KEY = secret_file.read().strip()
     except OSError:
         try:
             SECRET_KEY = gen_secret_key()  # noqa: F405
-            with SECRET_FILE.open('w') as secret:
+            with SECRET_FILE.open('w', encoding='UTF-8') as secret:
                 secret.write(SECRET_KEY)
                 print('*' * 72)
                 print('*', 'Written SECRET_KEY file:', SECRET_FILE)
diff --git a/src/hub/settings/dev.py b/src/hub/settings/dev.py
index 4537146421af619835b976c267a77fadd37a41a6..640f276423aa28cd87cf5f42d2ea7223dd2d3e98 100644
--- a/src/hub/settings/dev.py
+++ b/src/hub/settings/dev.py
@@ -57,7 +57,7 @@ os.environ.setdefault('ABUSE_DEFAULT_RECIPIENT', 'report@hub.example.net')
 os.environ.setdefault('ABUSE_RECIPIENTS', 'abuse=abuse@hub.example.net,tech=hub@hub.example.net,foo=')
 
 # load all default settings
-from .default import *  # noqa: F401, E402, F403
+from .default import *  # noqa: F401, E402, F403 # pylint: disable=wildcard-import,unused-wildcard-import
 
 # force DEBUG mode
 DEBUG = True
diff --git a/src/hub/settings/test.py b/src/hub/settings/test.py
index 207a156a37f651f102bd35c7c9d83b0458afba94..b2db9cf16393674bde51b52fb720a21d815c38c9 100644
--- a/src/hub/settings/test.py
+++ b/src/hub/settings/test.py
@@ -2,7 +2,7 @@ import os
 
 os.environ.setdefault('DJANGO_SECRET_KEY', 'Testing101')
 
-from .default import *  # noqa: F401, E402, F403
+from .default import *  # noqa: F401, E402, F403 # pylint: disable=wildcard-import,unused-wildcard-import
 
 INTEGRATIONS_WORKADVENTURE = True
 INTEGRATIONS_BBB = True
diff --git a/src/hub/urls.py b/src/hub/urls.py
index a6be1c306a42cf1e4db15d745d81d55ca548d45c..be661c7d18b311216c63516f41bc4c0d8e1dea00 100644
--- a/src/hub/urls.py
+++ b/src/hub/urls.py
@@ -38,13 +38,13 @@ def is_only(service: str) -> bool:
     return not any(v for k, v in services.items() if k != service)
 
 
-def add_service(service: str, prefix: str, urlpatterns: Any, path_name: str = '{prefix}', always_prefix: bool = False):
+def add_service(service: str, prefix: str, service_patterns: Any, path_name: str = '{prefix}', always_prefix: bool = False):
     if not services[service]:
         return []
     return [
         path(
             prefix if always_prefix else path_name.format(prefix='' if is_only(service) else prefix),
-            urlpatterns,
+            service_patterns,
         )
     ]
 
@@ -74,9 +74,9 @@ services.pop('admin')
 urlpatterns += add_service(
     'api',
     'api/',
-    urlpatterns=include('api.urls'),
+    service_patterns=include('api.urls'),
 )
-urlpatterns += add_service('api', 'api/', path_name='{prefix}auth/', urlpatterns=include('rest_framework.urls', namespace='rest_framework'))
+urlpatterns += add_service('api', 'api/', path_name='{prefix}auth/', service_patterns=include('rest_framework.urls', namespace='rest_framework'))
 urlpatterns += add_service('api', 'metrics/', include('api.urls_metrics', namespace='metrics'), always_prefix=True)
 services.pop('api')
 
diff --git a/src/plainui/forms.py b/src/plainui/forms.py
index 518904fda04b1c2ef317164d91e36af6d1ef5ae3..9444cef763028b621049dfb2c33561f1c68a679d 100644
--- a/src/plainui/forms.py
+++ b/src/plainui/forms.py
@@ -244,7 +244,7 @@ class TokenPasswortResetForm(auth_forms.SetPasswordForm):
         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!'))
 
-        pretix_ident, ticket_data = ConferenceMemberTicket.validate_pretix_ticket(self.conf, self.user, self.cleaned_data['jwt'], validate_only=True)
+        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():
             raise ValidationError(gettext("User didn't use this Ticket!"))
 
diff --git a/src/plainui/jinja2.py b/src/plainui/jinja2.py
index a3be9bc73fbdf87c8a048d17029686b85602a215..2677ce6ce025e927519f7457abe8663890209993 100644
--- a/src/plainui/jinja2.py
+++ b/src/plainui/jinja2.py
@@ -108,7 +108,7 @@ def custom_timedelta_short(tdelta: timedelta):
     s -= h * 3600
     m = s // 60
     s -= m * 60
-    return '%02d:%02d:%02d' % (h, m, s)
+    return f'{h:02d}:{m:02d}:{s:02d}'
 
 
 def custom_strftime(date):
diff --git a/src/plainui/tests/__init__.py b/src/plainui/tests/__init__.py
index fc9875885ae34b0b8d9452769eda0e20fb9c8232..b84588ab6f34e4a5f49a96db5a85c6f69ff43726 100644
--- a/src/plainui/tests/__init__.py
+++ b/src/plainui/tests/__init__.py
@@ -1,9 +1,9 @@
 from plainui.tests.test_auth import AuthViewTest, LoginViewTests, RegistrationTests
 from plainui.tests.test_views import ViewsTest
 
-all = (
-    AuthViewTest,
-    RegistrationTests,
-    LoginViewTests,
-    ViewsTest,
-)
+__all__ = [
+    'AuthViewTest',
+    'LoginViewTests',
+    'RegistrationTests',
+    'ViewsTest',
+]
diff --git a/src/plainui/tests/test_views.py b/src/plainui/tests/test_views.py
index a7ca15956bbcf25f442c22e407313fc1cd7735bc..e9002f0c704db719229956d1659aa6d71ed7b151 100644
--- a/src/plainui/tests/test_views.py
+++ b/src/plainui/tests/test_views.py
@@ -131,8 +131,8 @@ class ViewsTestBase(TestCase):
 
         This is the test pendant to `plainui.views.ConferenceRequiredMixin`.
         """
+        old_cookies = self.client.cookies
         try:
-            old_cookies = self.client.cookies
             self.client.cookies = SimpleCookie()
             ConferenceRequiredMixin._test_cork = True
             _request = self.client.post if post else self.client.get
@@ -192,8 +192,8 @@ class ViewsTestBase(TestCase):
             ConferenceRequiredMixin._conf = None
 
     def assertNeedsNoLogin(self, url, data=None, post=False):
+        old_cookies = self.client.cookies
         try:
-            old_cookies = self.client.cookies
             self.client.cookies = SimpleCookie()
             ConferenceRequiredMixin._test_cork = True
             _request = self.client.post if post else self.client.get
diff --git a/src/plainui/utils.py b/src/plainui/utils.py
index 608b35b83db6e9d4d52b0dc11a3c3dd49d851e01..08c243bd7634f39bf271c97af306ff950dadb61a 100644
--- a/src/plainui/utils.py
+++ b/src/plainui/utils.py
@@ -1,7 +1,6 @@
 import logging
 import re
 from difflib import HtmlDiff
-from typing import Optional, Tuple
 
 from django.conf import settings
 from django.urls import reverse
@@ -17,8 +16,8 @@ logger = logging.getLogger(__name__)
 
 
 def fetch_wiki_page(
-    conference: Conference, slug: str, language: Optional[str] = None, default_title: Optional[str] = 'Page 404', default_body: Optional[str] = None
-) -> Tuple[str, str]:
+    conference: Conference, slug: str, language: str | None = None, default_title: str | None = 'Page 404', default_body: str | None = None
+) -> tuple[str, str]:
     """
     Retrieves the wiki page with the given slug in the given language (defaults to current request's one).
     :param conference: the Conference in which to search
@@ -63,6 +62,7 @@ def check_message_content(conf, request, text, kind, kind_data):
 
         report_form = ReportForm(
             conf=conf,
+            request=request,
             data={
                 'kind': kind,
                 'kind_data': str(kind_data),
@@ -83,11 +83,11 @@ class StaticPageDiff(HtmlDiff):
     # function from source code - removed `nowrap="nowrap"`
     def _format_line(self, side, flag, linenum, text):
         try:
-            linenum = '%d' % linenum
-            id = f' id="{self._prefix[side]}{linenum}"'
+            linenum = f'{linenum:d}'
+            change_id = f' id="{self._prefix[side]}{linenum}"'
         except TypeError:
             # handle blank lines where linenum is '>' or ''
-            id = ''
+            change_id = ''
 
         # replace those things that would get confused with HTML symbols
         text = text.replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;')
@@ -96,4 +96,4 @@ class StaticPageDiff(HtmlDiff):
         text = text.replace(' ', '&nbsp;').rstrip()
 
         # vvv ---- removed `nowrap="nowrap"` --- vvv
-        return f'<td class="diff_header"{id}>{linenum}</td><td class="diff_content">{text}</td>'
+        return f'<td class="diff_header"{change_id}>{linenum}</td><td class="diff_content">{text}</td>'
diff --git a/src/plainui/views/assemblies.py b/src/plainui/views/assemblies.py
index 5ccfdeed76dd2fa42d230ec064fa723f0eede0dc..67037006d0666742ceb68e1b7900b5db0fb9b8cc 100644
--- a/src/plainui/views/assemblies.py
+++ b/src/plainui/views/assemblies.py
@@ -1,7 +1,7 @@
 __all__ = (
-    'AssemblyView',
-    'AssembliesView',
     'AssembliesAllView',
+    'AssembliesView',
+    'AssemblyView',
 )
 
 import logging
@@ -114,10 +114,10 @@ class AssembliesAllView(ConferenceRequiredMixin, PrefetchTagsMixin, Alphabetical
     }
     ordering = ['name']
 
-    def get_queryset(self) -> QuerySet[Assembly]:
+    def get_queryset(self, queryset: QuerySet[Assembly] | None = None) -> QuerySet[Assembly]:
         public_events = Event.objects.conference_accessible(self.conf)
         public_projects = Project.objects.conference_accessible(self.conf)
-        queryset: QuerySet[Assembly] = Assembly.objects.conference_accessible(self.conf)
+        queryset = queryset if queryset else Assembly.objects.conference_accessible(self.conf)
         queryset = queryset.annotate(
             public_events_count=Count('events', filter=Q(events__in=public_events), distinct=True),
             public_projects_count=Count('projects', Q(projects__in=public_projects), distinct=True),
diff --git a/src/plainui/views/auth.py b/src/plainui/views/auth.py
index 00c5dbca0593a2c7b93f69f786ca33372682cd01..a39f45b35d2e21feb2ca90772c6d293dab1e9ab6 100644
--- a/src/plainui/views/auth.py
+++ b/src/plainui/views/auth.py
@@ -1,16 +1,16 @@
 __all__ = (
     'LoginView',
-    'RegistrationView',
-    'RegistrationDoneView',
-    'RegistrationActivationView',
+    'LogoutView',
     'PasswordChangeView',
-    'PasswordResetView',
-    'PasswordResetDoneView',
-    'PasswordResetConfirmView',
     'PasswordResetCompleteView',
-    'TokenPasswordResetView',
-    'LogoutView',
+    'PasswordResetConfirmView',
+    'PasswordResetDoneView',
+    'PasswordResetView',
+    'RegistrationActivationView',
+    'RegistrationDoneView',
+    'RegistrationView',
     'ShibboleetView',
+    'TokenPasswordResetView',
 )
 
 import logging
diff --git a/src/plainui/views/badges.py b/src/plainui/views/badges.py
index 047ab6ecf3c6032b1a4d2e19cc42238731971634..0aafbdd27b9f600c38ec06fa7fbd714cf723bc96 100644
--- a/src/plainui/views/badges.py
+++ b/src/plainui/views/badges.py
@@ -1,11 +1,11 @@
 __all__ = (
+    'AwardBadgeView',
     'BadgeDetailView',
     'BadgeListView',
-    'ManageBadgesView',
+    'BadgeSettingsView',
     'ManageBadgeView',
-    'AwardBadgeView',
+    'ManageBadgesView',
     'RedeemTokenDetailView',
-    'BadgeSettingsView',
 )
 
 from typing import Any
@@ -235,7 +235,7 @@ class ManageBadgesView(ConferenceRequiredMixin, TemplateView):
         return context
 
     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)
 
@@ -256,7 +256,7 @@ class ManageBadgeView(ConferenceRequiredMixin, UpdateView):
         return queryset.get(badge__id=self.kwargs.get('pk'))
 
     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)
 
diff --git a/src/plainui/views/board.py b/src/plainui/views/board.py
index 55b27ec54045ee602c4cfa1d1f62b60cef10155f..25cf021c942e643f148ccdb5074a35c5ef62388f 100644
--- a/src/plainui/views/board.py
+++ b/src/plainui/views/board.py
@@ -1,10 +1,10 @@
 __all__ = (
-    'BoardView',
-    'BoardPrivateView',
-    'BoardEntryView',
     'BoardEntryCreateView',
-    'BoardEntryEditView',
     'BoardEntryDeleteView',
+    'BoardEntryEditView',
+    'BoardEntryView',
+    'BoardPrivateView',
+    'BoardView',
 )
 
 
diff --git a/src/plainui/views/dereferrer.py b/src/plainui/views/dereferrer.py
index 503267e418f45544bdab954189667e75e8f881e7..4e874977b17594ecd2988b82c3100172d2aac958 100644
--- a/src/plainui/views/dereferrer.py
+++ b/src/plainui/views/dereferrer.py
@@ -1,9 +1,9 @@
 __all__ = (
-    'DereferrerView',
-    'DereferrerRedirectView',
-    'WorkadventureDereferrerView',
     'DereferrerAddToAllowlistView',
+    'DereferrerRedirectView',
     'DereferrerRemoveFromAllowlistView',
+    'DereferrerView',
+    'WorkadventureDereferrerView',
 )
 
 from urllib.parse import ParseResult, unquote, urlparse
diff --git a/src/plainui/views/events.py b/src/plainui/views/events.py
index e483b54693880a1a42c9a7267e618fe03253c710..f6e7fc3c7377f4bc2b3eb84dd8f7ed78d0273e8d 100644
--- a/src/plainui/views/events.py
+++ b/src/plainui/views/events.py
@@ -1,10 +1,10 @@
 __all__ = (
     'AssembliesEventsView',
-    'EventView',
-    'UpcomingView',
     'ChannelEventsView',
-    'SosList',
+    'EventView',
     'SosJoin',
+    'SosList',
+    'UpcomingView',
 )
 
 from django.contrib import messages
diff --git a/src/plainui/views/general.py b/src/plainui/views/general.py
index 41a9b819acf0edf46b93c9b44fae86288aa96b87..7d935cb942d4d8f32a020ae60b36dc0276ecc642 100644
--- a/src/plainui/views/general.py
+++ b/src/plainui/views/general.py
@@ -1,10 +1,10 @@
 __all__ = (
-    'LandingView',
+    'ComponentGalleryView',
     'IndexView',
+    'LandingView',
     'MetaNavView',
-    'TagView',
     'SearchView',
-    'ComponentGalleryView',
+    'TagView',
     'UnderConstructionView',
 )
 
@@ -146,7 +146,7 @@ class TagView(ConferenceRequiredMixin, TemplateView):
 class SearchView(ConferenceRequiredMixin, TemplateView):
     template_name = 'plainui/search.html.j2'
 
-    def get(self, request, **kwargs):
+    def get(self, request, *args, **kwargs):
         return redirect(reverse('plainui:index'))
 
     def post(self, request, **kwargs):
@@ -169,10 +169,11 @@ class SearchView(ConferenceRequiredMixin, TemplateView):
 class ComponentGalleryView(TemplateView):
     template_name = 'plainui/component_gallery.html.j2'
 
-    def get_context_data(self):
+    def get_context_data(self, **kwargs):
         form_invalid = ExampleForm({'text': '', 'textarea': ''})
         form_invalid.full_clean()
         return super().get_context_data(
+            **kwargs,
             form_valid=ExampleForm({'text': 'lorem ipsum', 'password': 'lorem ipsum', 'checkbox': True, 'textarea': 'blork'}),
             form_invalid=form_invalid,
             conf={
diff --git a/src/plainui/views/personal_messages.py b/src/plainui/views/personal_messages.py
index bfc0b681ee2f4fb742335aacea4790fefdc9404b..195d38beb6c25e802ddb1cef79bb5f3ed0554c20 100644
--- a/src/plainui/views/personal_messages.py
+++ b/src/plainui/views/personal_messages.py
@@ -1,8 +1,8 @@
 __all__ = (
+    'PersonalMessageDeleteView',
     'PersonalMessageListView',
     'PersonalMessageSendView',
     'PersonalMessageShowView',
-    'PersonalMessageDeleteView',
 )
 
 from django_ratelimit.decorators import ratelimit
diff --git a/src/plainui/views/static_pages.py b/src/plainui/views/static_pages.py
index 962ffa607a7750b3612e21f583885b575cf3118c..8a435aa25d3ddcbafb0dc8bb401f573ab292fbbd 100644
--- a/src/plainui/views/static_pages.py
+++ b/src/plainui/views/static_pages.py
@@ -1,15 +1,15 @@
 __all__ = (
-    'StaticPageView',
-    'StaticPageEditView',
-    'StaticPageHistoryView',
     'StaticPageDiffView',
+    'StaticPageEditView',
     'StaticPageGlobalHistoryView',
+    'StaticPageHistoryView',
     'StaticPageLockKeepalive',
     'StaticPageRedirectToStart',
+    'StaticPageView',
 )
 
 from datetime import timedelta
-from typing import Any, Dict
+from typing import Any
 
 from django_ratelimit.decorators import ratelimit
 
@@ -45,7 +45,7 @@ class StaticPageView(ConferenceRequiredMixin, TemplateView):
     require_conference_member = False
 
     @transaction.atomic
-    def get(self, request, page_slug, **kwargs):
+    def get(self, request, *args, page_slug, **kwargs):
         self.static_page = StaticPage.objects.conference_accessible(conference=self.conf, language=get_language()).filter(slug=page_slug).first()
 
         if 'release' in request.GET:
@@ -63,7 +63,7 @@ class StaticPageView(ConferenceRequiredMixin, TemplateView):
                     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)
+        return super().get(request, *args, page_slug=page_slug, **kwargs)
 
     def get_context_data(self, page_slug, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -275,7 +275,7 @@ class StaticPageEditView(ConferenceRequiredMixin, TemplateView):
         lock = None
         if page_exists and not preview:
             lock = Lock.objects.filter(pk=lock_id, content_type=ContentType.objects.get_for_model(StaticPage), object_id=static_page.pk).first()
-            if not lock or lock.timeout >= self.now and lock.lock_holder != request.user:
+            if not lock or (lock.timeout >= self.now and lock.lock_holder != request.user):
                 messages.error(request, gettext('Lock error, could not save page!'))
                 lock_error = True
 
@@ -336,7 +336,7 @@ class StaticPageHistoryView(ConferenceRequiredMixin, TemplateView):
     template_name = 'plainui/static_page_history.html.j2'
     require_user = True
 
-    def get(self, request, page_slug, **kwargs):
+    def get(self, request, *args, page_slug, **kwargs):
         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}))
@@ -348,9 +348,9 @@ class StaticPageHistoryView(ConferenceRequiredMixin, TemplateView):
             if not self.request.user.is_authenticated or not self.request.user.has_conference_staff_permission(self.conf, 'core.static_pages'):
                 return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
-        return super().get(request, page_slug=page_slug, **kwargs)
+        return super().get(request, *args, page_slug=page_slug, **kwargs)
 
-    def get_context_data(self, page_slug, **kwargs) -> Dict[str, Any]:
+    def get_context_data(self, page_slug, **kwargs) -> dict[str, Any]:
         context = super().get_context_data(**kwargs)
         context['conf'] = self.conf
         context['page_slug'] = page_slug
@@ -364,7 +364,7 @@ class StaticPageDiffView(ConferenceRequiredMixin, TemplateView):
     template_name = 'plainui/static_page_diff.html.j2'
     require_user = True
 
-    def get(self, request, page_slug, **kwargs):
+    def get(self, request, *args, page_slug, **kwargs):
         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}))
@@ -376,9 +376,9 @@ class StaticPageDiffView(ConferenceRequiredMixin, TemplateView):
             if not self.request.user.is_authenticated or not self.request.user.has_conference_staff_permission(self.conf, 'core.static_pages'):
                 return redirect(reverse('plainui:static_page', kwargs={'page_slug': page_slug}))
 
-        return super().get(request, page_slug=page_slug, **kwargs)
+        return super().get(request, *args, page_slug=page_slug, **kwargs)
 
-    def get_context_data(self, page_slug, **kwargs) -> Dict[str, Any]:
+    def get_context_data(self, page_slug, **kwargs) -> dict[str, Any]:
         context = super().get_context_data(**kwargs)
         context['conf'] = self.conf
         context['page_slug'] = page_slug
@@ -407,7 +407,7 @@ class StaticPageGlobalHistoryView(ConferenceRequiredMixin, TemplateView):
     template_name = 'plainui/static_page_global_history.html.j2'
     require_user = True
 
-    def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
+    def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
         PAGE_SIZE = 30
         try:
             page = int(self.request.GET.get('page', '0'))
diff --git a/src/plainui/views/ticket_tokens.py b/src/plainui/views/ticket_tokens.py
index a5bf8a13ad4c92e513319700bca26186781d4b88..648c02d5b52589a9f6ec4636f242b8674d8b4a5c 100644
--- a/src/plainui/views/ticket_tokens.py
+++ b/src/plainui/views/ticket_tokens.py
@@ -1,8 +1,8 @@
 __all__ = (
-    'RedeemTokenView',
     'RedeemTokenAddToUserView',
-    'RedeemTokenUserCreateView',
     'RedeemTokenLoggedIn',
+    'RedeemTokenUserCreateView',
+    'RedeemTokenView',
 )
 
 from django_ratelimit.decorators import ratelimit
diff --git a/src/plainui/views/user_profile.py b/src/plainui/views/user_profile.py
index 0cfd977fa9d04838f1295f16ba2a78a2e429578e..04e5de43300cc2073c0fd92672d2a6b65e9fd373 100644
--- a/src/plainui/views/user_profile.py
+++ b/src/plainui/views/user_profile.py
@@ -1,7 +1,7 @@
 __all__ = (
-    'ProfileView',
-    'ModifyThemeView',
     'ModifyFavoritesView',
+    'ModifyThemeView',
+    'ProfileView',
 )
 
 import logging
diff --git a/src/plainui/views/users.py b/src/plainui/views/users.py
index d6729bf3b8d94fc7dac5e962c84fd38e75b5afc3..785e052416e2cc1e1ba9bac9141ec256702e2f74 100644
--- a/src/plainui/views/users.py
+++ b/src/plainui/views/users.py
@@ -1,6 +1,6 @@
 __all__ = (
-    'UserView',
     'UserByUuidView',
+    'UserView',
 )
 
 from django.db.models import Q
diff --git a/src/plainui/views/utils.py b/src/plainui/views/utils.py
index 4c150bd4a8886006d827b909573c799eb5633394..b38eaaf0f3a6892288c812ad9fe1b279fac6263f 100644
--- a/src/plainui/views/utils.py
+++ b/src/plainui/views/utils.py
@@ -1,6 +1,6 @@
 import threading
 from datetime import UTC, datetime, timedelta
-from typing import ClassVar, Optional
+from typing import ClassVar
 
 from django.conf import settings
 from django.contrib import messages
@@ -37,7 +37,7 @@ class ConferenceRequiredMixin(auth_mixins.AccessMixin):
     """ This view should only be shown when the conference is published."""
 
     # cache for the conference used in this plainui instance
-    _conf: ClassVar[Optional[Conference]] = None
+    _conf: ClassVar[Conference | None] = None
     _conf_timeout: ClassVar[datetime] = datetime.min.replace(tzinfo=UTC)
     _conf_lock: ClassVar[threading.Lock] = threading.Lock()
 
@@ -97,7 +97,7 @@ class ConferenceRequiredMixin(auth_mixins.AccessMixin):
             return redirect(reverse('plainui:under_construction'))
 
         if (
-            self.require_login and conf.require_login or conf.require_ticket and self.require_conference_member or self.require_user
+            (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()
 
@@ -190,7 +190,7 @@ def event_filter(
     upcoming=False,
     calendar_mode=True,
     public_fahrplan=None,
-    is_recorded: Optional[bool] = None,
+    is_recorded: bool | None = None,
 ):
     min_date, max_date = conf.start, conf.end
     if min_date is None or max_date is None:
@@ -275,22 +275,22 @@ _SESSION_TYPE_MAP = {
 }
 
 
-def _session_refresh_favorite(session, user, type: str) -> list[str]:
+def _session_refresh_favorite(session, user, object_type: str) -> list[str]:
     if not user.is_authenticated:
         return []
 
-    favorites = [str(id_) for id_ in _SESSION_TYPE_MAP[type].objects.filter(favorite_of=user).values_list('id', flat=True)]
-    session[type] = favorites
+    favorites = [str(id_) for id_ in _SESSION_TYPE_MAP[object_type].objects.filter(favorite_of=user).values_list('id', flat=True)]
+    session[object_type] = favorites
     return favorites
 
 
-def session_get_favorite(session, user, type: str) -> list[str]:
+def session_get_favorite(session, user, object_type: str) -> list[str]:
     if not user.is_authenticated:
         return []
 
-    favorites = session.get(type, None)
+    favorites = session.get(object_type, None)
     if favorites is None:
-        return _session_refresh_favorite(session, user, type)
+        return _session_refresh_favorite(session, user, object_type)
     return favorites
 
 
@@ -303,9 +303,6 @@ def _session_refresh_favorite_assemblies(session, user) -> list[str]:
 
 
 def session_get_favorite_events(session, user) -> list[str]:
-    # TODO: remove this after 37c3, when no more session are active, this is to migrate the personal calendar data
-    if session.pop('sch_e', None):
-        session.pop('fav_e')
     return session_get_favorite(session, user, 'fav_e')
 
 
diff --git a/src/plainui/views/work_adventure.py b/src/plainui/views/work_adventure.py
index 49cb99e00c8cc9f85bcc83951444e691e8021816..dd522e3228e1583496ca9f6d67793ff4285c0d8a 100644
--- a/src/plainui/views/work_adventure.py
+++ b/src/plainui/views/work_adventure.py
@@ -1,9 +1,9 @@
 __all__ = (
-    'WorldView',
-    'WorkadventureEnterView',
+    'AssemblyJoinBBBView',
     'WorkadventureContactPageView',
+    'WorkadventureEnterView',
     'WorkadventureVCardView',
-    'AssemblyJoinBBBView',
+    'WorldView',
 )
 
 from django.conf import settings