diff --git a/aleksis/core/frontend/app/vuetify.js b/aleksis/core/frontend/app/vuetify.js
index 3e32230a6637f2748797bbe91afe2a16ad4add64..4cccdd2978ed417ea1dd62f1da9ee3770013b733 100644
--- a/aleksis/core/frontend/app/vuetify.js
+++ b/aleksis/core/frontend/app/vuetify.js
@@ -32,6 +32,7 @@ const vuetifyOpts = {
       home: "mdi-home-outline",
       groupType: "mdi-shape-outline",
       print: "mdi-printer-outline",
+      schoolTerm: "mdi-calendar-range-outline",
     },
   },
 };
diff --git a/aleksis/core/frontend/components/app/App.vue b/aleksis/core/frontend/components/app/App.vue
index d4496e09f1e3593974de598ca5f8bed712fefd07..d522d33fc65106b853a07e4e8285b632858fc84c 100644
--- a/aleksis/core/frontend/components/app/App.vue
+++ b/aleksis/core/frontend/components/app/App.vue
@@ -56,6 +56,7 @@
           v-if="whoAmI && whoAmI.isAuthenticated && whoAmI.person"
           class="d-flex"
         >
+          <active-school-term-select v-model="$root.activeSchoolTerm" />
           <notification-list v-if="!whoAmI.person.isDummy" />
           <account-menu
             :account-menu="accountMenu"
@@ -65,6 +66,9 @@
         </div>
       </v-app-bar>
       <v-main>
+        <active-school-term-banner
+          v-if="$root.activeSchoolTerm && !$root.activeSchoolTerm.current"
+        />
         <div
           :class="{
             'main-container': true,
@@ -259,6 +263,8 @@ import routesMixin from "../../mixins/routes";
 import error404Mixin from "../../mixins/error404";
 
 import { browsersRegex } from "virtual:supported-browsers";
+import ActiveSchoolTermSelect from "../school_term/ActiveSchoolTermSelect.vue";
+import ActiveSchoolTermBanner from "../school_term/ActiveSchoolTermBanner.vue";
 
 export default {
   data() {
@@ -326,6 +332,8 @@ export default {
   },
   name: "App",
   components: {
+    ActiveSchoolTermBanner,
+    ActiveSchoolTermSelect,
     AccountMenu,
     ErrorPage,
     NotificationList,
diff --git a/aleksis/core/frontend/components/notifications/NotificationList.vue b/aleksis/core/frontend/components/notifications/NotificationList.vue
index 54c0f4d181d42a1d8246a5a1f600000499b71e84..47b741fbeb122f49d6fdbcb0d276df4fcf0d3761 100644
--- a/aleksis/core/frontend/components/notifications/NotificationList.vue
+++ b/aleksis/core/frontend/components/notifications/NotificationList.vue
@@ -9,14 +9,13 @@
     <template #activator="{ on, attrs }">
       <v-btn
         icon
-        color="primary"
+        dark
         v-bind="attrs"
         v-on="on"
         :loading="$apollo.queries.myNotifications.loading"
         class="mx-2"
       >
         <v-icon
-          color="white"
           v-if="
             myNotifications &&
             myNotifications.person &&
diff --git a/aleksis/core/frontend/components/person/PersonOverview.vue b/aleksis/core/frontend/components/person/PersonOverview.vue
index ac23625d38dcd34af93011bf6365721b3bf8df51..55826e5cc139eda682343ab4e8d7d2d87f1fdee5 100644
--- a/aleksis/core/frontend/components/person/PersonOverview.vue
+++ b/aleksis/core/frontend/components/person/PersonOverview.vue
@@ -263,7 +263,7 @@
 
           <template v-for="widget in widgets">
             <v-col
-              v-if="widget.shouldDisplay(person, currentSchoolTerm)"
+              v-if="widget.shouldDisplay(person, $root.activeSchoolTerm)"
               v-bind="widget.colProps"
               :key="widget.key"
             >
@@ -271,7 +271,7 @@
               <component
                 :is="widget.component"
                 :person="person"
-                :school-term="currentSchoolTerm"
+                :school-term="$root.activeSchoolTerm"
                 :maximized="widgetSlug === widget.key"
                 @maximize="maximizeWidget(widget.key)"
                 @minimize="minimizeWidgets()"
@@ -292,7 +292,6 @@ import PersonActions from "./PersonActions.vue";
 import PersonAvatarClickbox from "./PersonAvatarClickbox.vue";
 import PersonCollection from "./PersonCollection.vue";
 
-import gqlCurrentSchoolTerm from "../school_term/currentSchoolTerm.graphql";
 import gqlPersonOverview from "./personOverview.graphql";
 
 import { collections } from "aleksisAppImporter";
@@ -307,15 +306,9 @@ export default {
     PersonAvatarClickbox,
     PersonCollection,
   },
-  apollo: {
-    currentSchoolTerm: {
-      query: gqlCurrentSchoolTerm,
-    },
-  },
   data() {
     return {
       query: gqlPersonOverview,
-      currentSchoolTerm: null,
     };
   },
   props: {
diff --git a/aleksis/core/frontend/components/school_term/ActiveSchoolTermBanner.vue b/aleksis/core/frontend/components/school_term/ActiveSchoolTermBanner.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a0e795ede81b9e45cc10b9fbd98635b5404de064
--- /dev/null
+++ b/aleksis/core/frontend/components/school_term/ActiveSchoolTermBanner.vue
@@ -0,0 +1,35 @@
+<script>
+export default {
+  name: "ActiveSchoolTermBanner",
+};
+</script>
+
+<template>
+  <v-banner
+    :single-line="!$vuetify.breakpoint.mobile"
+    v-bind="$attrs"
+    v-on="$listeners"
+    color="warning white--text"
+    icon="$warning"
+    icon-color="white"
+    id="banner"
+  >
+    {{
+      $t("school_term.active_school_term.warning", {
+        termName: $root.activeSchoolTerm?.name,
+      })
+    }}
+
+    <template #actions>
+      <v-btn text color="black" @click="$root.activeSchoolTerm = null">
+        {{ $t("school_term.active_school_term.select_action") }}
+      </v-btn>
+    </template>
+  </v-banner>
+</template>
+
+<style>
+#banner > .v-banner__wrapper {
+  padding-block: 4px;
+}
+</style>
diff --git a/aleksis/core/frontend/components/school_term/ActiveSchoolTermSelect.vue b/aleksis/core/frontend/components/school_term/ActiveSchoolTermSelect.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f56bb19639844bfb7a8cbac560fb192a597df664
--- /dev/null
+++ b/aleksis/core/frontend/components/school_term/ActiveSchoolTermSelect.vue
@@ -0,0 +1,156 @@
+<script>
+import {
+  activeSchoolTerm,
+  schoolTerms,
+  setActiveSchoolTerm,
+} from "./activeSchoolTerm.graphql";
+import loadingMixin from "../../mixins/loadingMixin";
+export default {
+  name: "ActiveSchoolTermSelect",
+  mixins: [loadingMixin],
+  apollo: {
+    schoolTerms: {
+      query: schoolTerms,
+    },
+    activeSchoolTerm: {
+      query: activeSchoolTerm,
+      result() {
+        this.$emit("input", this.activeSchoolTerm);
+      },
+    },
+  },
+  props: {
+    affectedQuery: {
+      type: Number,
+      default: 0,
+    },
+    value: {
+      type: Object,
+      required: false,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      activeSchoolTerm: null,
+      schoolTerms: [],
+      showSuccess: false,
+    };
+  },
+  computed: {
+    schoolTerm: {
+      get() {
+        return this.activeSchoolTerm?.id;
+      },
+      set(value) {
+        if (this.activeSchoolTerm?.id === value) {
+          return;
+        }
+
+        this.handleLoading(true);
+
+        this.$apollo
+          .mutate({
+            mutation: setActiveSchoolTerm,
+            variables: { id: value },
+            update: (store, data) => {
+              const newTerm = data.data.setActiveSchoolTerm;
+
+              // Update cached data
+              store.writeQuery({ query: activeSchoolTerm, data: newTerm });
+              this.$emit("input", newTerm);
+            },
+          })
+          .catch((error) => {
+            this.handleMutationError(error);
+          })
+          .finally(() => {
+            this.handleLoading(false);
+            this.showSuccess = true;
+            setTimeout(() => {
+              this.showSuccess = false;
+            }, 2000);
+          });
+      },
+    },
+  },
+  watch: {
+    value(value) {
+      if (!value) {
+        value = this.schoolTerms.find((term) => term.current);
+      }
+      if (Object.hasOwn(value, "activeSchoolTerm")) {
+        value = value.activeSchoolTerm;
+      }
+      if (Object.hasOwn(value, "id")) {
+        value = value.id;
+      }
+
+      if (this.schoolTerm === value) {
+        return;
+      }
+
+      this.schoolTerm = value;
+    },
+  },
+};
+</script>
+
+<template>
+  <v-menu offset-y :close-on-content-click="false">
+    <template #activator="{ on, attrs }">
+      <v-btn
+        icon
+        v-bind="attrs"
+        v-on="on"
+        dark
+        :loading="$apollo.queries.activeSchoolTerm.loading"
+      >
+        <v-icon v-if="activeSchoolTerm?.current">$schoolTerm</v-icon>
+        <v-icon v-else>mdi-calendar-alert-outline</v-icon>
+      </v-btn>
+    </template>
+    <v-list :disabled="loading">
+      <v-list-item disabled>
+        <v-list-item-content>
+          <v-list-item-title>
+            {{ $t("school_term.active_school_term.title") }}
+          </v-list-item-title>
+          <v-list-item-subtitle>
+            {{ $t("school_term.active_school_term.subtitle") }}
+          </v-list-item-subtitle>
+        </v-list-item-content>
+
+        <v-avatar>
+          <v-progress-circular
+            v-if="loading"
+            indeterminate
+            :size="16"
+            :width="2"
+          />
+          <v-icon v-else-if="showSuccess" color="success">$success</v-icon>
+        </v-avatar>
+      </v-list-item>
+
+      <v-list-item-group v-model="schoolTerm" :mandatory="!!activeSchoolTerm">
+        <v-list-item
+          v-for="term in schoolTerms"
+          :key="term.id"
+          :value="term.id"
+        >
+          <v-list-item-content>
+            <v-list-item-title>
+              {{ term.name }}
+            </v-list-item-title>
+          </v-list-item-content>
+
+          <v-list-item-action v-if="term.current">
+            <v-chip label color="secondary">
+              {{ $t("school_term.current") }}
+            </v-chip>
+          </v-list-item-action>
+        </v-list-item>
+      </v-list-item-group>
+    </v-list>
+  </v-menu>
+</template>
diff --git a/aleksis/core/frontend/components/school_term/activeSchoolTerm.graphql b/aleksis/core/frontend/components/school_term/activeSchoolTerm.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..95b5fc170064e4dea9a24a17f3b10ad4bccb5af8
--- /dev/null
+++ b/aleksis/core/frontend/components/school_term/activeSchoolTerm.graphql
@@ -0,0 +1,37 @@
+query activeSchoolTerm {
+  activeSchoolTerm {
+    id
+    name
+    dateStart
+    dateEnd
+    current
+    canEdit
+    canDelete
+  }
+}
+
+query schoolTerms {
+  schoolTerms {
+    id
+    name
+    dateStart
+    dateEnd
+    current
+    canEdit
+    canDelete
+  }
+}
+
+mutation setActiveSchoolTerm($id: ID!) {
+  setActiveSchoolTerm(id: $id) {
+    activeSchoolTerm {
+      id
+      name
+      dateStart
+      dateEnd
+      current
+      canEdit
+      canDelete
+    }
+  }
+}
diff --git a/aleksis/core/frontend/components/school_term/currentSchoolTerm.graphql b/aleksis/core/frontend/components/school_term/currentSchoolTerm.graphql
deleted file mode 100644
index 82a7741f54770fc1a7edb7d6ce5ebcf74ffbb1f5..0000000000000000000000000000000000000000
--- a/aleksis/core/frontend/components/school_term/currentSchoolTerm.graphql
+++ /dev/null
@@ -1,10 +0,0 @@
-query currentSchoolTerm {
-  currentSchoolTerm {
-    id
-    name
-    dateStart
-    dateEnd
-    canEdit
-    canDelete
-  }
-}
diff --git a/aleksis/core/frontend/index.js b/aleksis/core/frontend/index.js
index 21ebd05d93130a30d115bf7f78ba0db0a6595d26..00411a66a968a2e1d1eda36987cc325000634610 100644
--- a/aleksis/core/frontend/index.js
+++ b/aleksis/core/frontend/index.js
@@ -80,6 +80,7 @@ const app = new Vue({
     permissions: [],
     permissionNames: [],
     frequentCeleryPolling: false,
+    activeSchoolTerm: null,
   }),
   computed: {
     matchedComponents() {
diff --git a/aleksis/core/frontend/messages/de.json b/aleksis/core/frontend/messages/de.json
index fb3ae338ac4bb71428c2d5f8feb764904353dbbb..70c4b17328c9d85fc97ad8307a0601c578545bd2 100644
--- a/aleksis/core/frontend/messages/de.json
+++ b/aleksis/core/frontend/messages/de.json
@@ -407,7 +407,14 @@
     "menu_title": "Schuljahre",
     "name": "Name",
     "title": "Schuljahr",
-    "title_plural": "Schuljahre"
+    "title_plural": "Schuljahre",
+    "current": "Aktuell",
+    "active_school_term": {
+      "title": "Aktives Schuljahr",
+      "subtitle": "Die Auswahl wird auf allen Seiten in AlekSIS berücksichtigt.",
+      "warning": "Hinweis: Sie sehen aktuell Daten aus einem anderen Schuljahr ({termName}). Informationen können daher veraltet sein und entsprechen möglicherweise nicht dem aktuellen Stand.",
+      "select_action": "Aktuelles auswählen"
+    }
   },
   "selection": {
     "num_items_selected": "Keine Objekte ausgewählt | 1 Objekt ausgewählt | {n} Objekte ausgewählt"
diff --git a/aleksis/core/frontend/messages/en.json b/aleksis/core/frontend/messages/en.json
index efdb3a89575d9d2a816db183862e468ace66dcca..dda331d9ae8105f1ac43b7bdf8c011859597271d 100644
--- a/aleksis/core/frontend/messages/en.json
+++ b/aleksis/core/frontend/messages/en.json
@@ -348,7 +348,14 @@
     "menu_title": "School Terms",
     "name": "Name",
     "title": "School Term",
-    "title_plural": "School Terms"
+    "title_plural": "School Terms",
+    "current": "Current",
+    "active_school_term": {
+      "title": "Active School Term",
+      "subtitle": "This selection affects all pages in AlekSIS.",
+      "warning": "Warning: You are currently viewing data from a different school term ({termName}). Information may be outdated and may not reflect the current situation.",
+      "select_action": "Select current"
+    }
   },
   "selection": {
     "num_items_selected": "No items selected | 1 item selected | {n} items selected"
diff --git a/aleksis/core/frontend/routes.js b/aleksis/core/frontend/routes.js
index 116d7936687352665f5f89a18f40af9ecf91d434..c90c3facf230c73ec0685610b22663433ab21eeb 100644
--- a/aleksis/core/frontend/routes.js
+++ b/aleksis/core/frontend/routes.js
@@ -317,7 +317,7 @@ const routes = [
         meta: {
           inMenu: true,
           titleKey: "school_term.menu_title",
-          icon: "mdi-calendar-range-outline",
+          icon: "$schoolTerm",
           iconActive: "mdi-calendar-range",
           permission: "core.view_schoolterm_rule",
         },
diff --git a/aleksis/core/management/commands/convert_urls_to_routes.py b/aleksis/core/management/commands/convert_urls_to_routes.py
index e484988ef8bb2c37014daa91d18ab4ee41864255..f6cd8b8297e8b114c2030933ac826266f3e1274b 100644
--- a/aleksis/core/management/commands/convert_urls_to_routes.py
+++ b/aleksis/core/management/commands/convert_urls_to_routes.py
@@ -45,7 +45,7 @@ class Command(BaseCommand):
 
         for url in urlpatterns:
             # Convert route name and url pattern to vue-router format
-            menu = menu_by_urls[url.name] if url.name in menu_by_urls else None
+            menu = menu_by_urls.get(url.name, None)
             route_name = f"{app_camel_case}.{camelcase(url.name)}"
             url_pattern = url.pattern._route
             new_url_pattern_list = []
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 2d41b0438ab1d34420f835b097f8f451ac9b43fb..4acdb6b6724610c6a9328049394e4935399a232f 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -1698,10 +1698,8 @@ class BirthdayEvent(CalendarEventMixin):
 
     @classmethod
     def value_description(cls, reference_object: Person, request: HttpRequest | None = None) -> str:
-        return ("{name} was born on {birthday}.").format(
-            name=reference_object.addressing_name,
-            birthday=date_format(reference_object.date_of_birth),
-        )
+        return f"{reference_object.addressing_name} \
+            was born on {date_format(reference_object.date_of_birth)}."
 
     @classmethod
     def value_start_datetime(
diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py
index d91cef5af4dbb3cd9271a70228af200f4f676253..c6cdaa70407cca99db94df1158010514c3e8141a 100644
--- a/aleksis/core/schema/__init__.py
+++ b/aleksis/core/schema/__init__.py
@@ -23,6 +23,7 @@ from ..models import (
 )
 from ..util.apps import AppConfig
 from ..util.core_helpers import (
+    get_active_school_term,
     get_allowed_object_ids,
     get_app_module,
     get_app_packages,
@@ -68,6 +69,7 @@ from .school_term import (
     SchoolTermBatchDeleteMutation,
     SchoolTermBatchPatchMutation,
     SchoolTermType,
+    SetActiveSchoolTermMutation,
 )
 from .search import SearchResultType
 from .system_properties import SystemPropertiesType
@@ -115,8 +117,8 @@ class Query(graphene.ObjectType):
     rooms = FilterOrderList(RoomType)
     room_by_id = graphene.Field(RoomType, id=graphene.ID())
 
+    active_school_term = graphene.Field(SchoolTermType)
     school_terms = FilterOrderList(SchoolTermType)
-    current_school_term = graphene.Field(SchoolTermType)
 
     holidays = FilterOrderList(HolidayType)
     calendar = graphene.Field(CalendarBaseType)
@@ -282,6 +284,10 @@ class Query(graphene.ObjectType):
 
         return room_object
 
+    @staticmethod
+    def resolve_active_school_term(root, info, **kwargs):
+        return get_active_school_term(info.context)
+
     @staticmethod
     def resolve_calendar(root, info, **kwargs):
         return True
@@ -310,6 +316,7 @@ class Mutation(graphene.ObjectType):
     create_school_terms = SchoolTermBatchCreateMutation.Field()
     delete_school_terms = SchoolTermBatchDeleteMutation.Field()
     update_school_terms = SchoolTermBatchPatchMutation.Field()
+    set_active_school_term = SetActiveSchoolTermMutation.Field()
 
     create_holidays = HolidayBatchCreateMutation.Field()
     delete_holidays = HolidayBatchDeleteMutation.Field()
diff --git a/aleksis/core/schema/group.py b/aleksis/core/schema/group.py
index 85ba440092798471701fdc999a1da2b2e198f88a..32832a512e84b04a57e3d1f72ed20f8d9b99ee5a 100644
--- a/aleksis/core/schema/group.py
+++ b/aleksis/core/schema/group.py
@@ -5,7 +5,7 @@ from graphene_django import DjangoObjectType
 from guardian.shortcuts import get_objects_for_user
 
 from ..models import Group, Person
-from ..util.core_helpers import has_person
+from ..util.core_helpers import filter_active_school_term, has_person
 from .base import BaseBatchDeleteMutation, DjangoFilterMixin, PermissionsTypeMixin
 
 
@@ -123,7 +123,7 @@ class GroupType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
                 ),
             )
 
-        return qs
+        return filter_active_school_term(info.context, qs)
 
 
 class GroupBatchDeleteMutation(BaseBatchDeleteMutation):
diff --git a/aleksis/core/schema/school_term.py b/aleksis/core/schema/school_term.py
index 8dd36b47a8978bd448a28ba08f8948f2564cd2bd..6887097ff9ebcfd529887da8def1652a437217b1 100644
--- a/aleksis/core/schema/school_term.py
+++ b/aleksis/core/schema/school_term.py
@@ -1,3 +1,4 @@
+import graphene
 from graphene_django import DjangoObjectType
 
 from ..models import SchoolTerm
@@ -20,12 +21,18 @@ class SchoolTermType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
         }
         fields = ("id", "name", "date_start", "date_end")
 
+    current = graphene.Boolean()
+
     @classmethod
     def get_queryset(cls, queryset, info, **kwargs):
         if not info.context.user.has_perm("core.fetch_schoolterms_rule"):
             return []
         return queryset
 
+    @staticmethod
+    def resolve_current(root, info):
+        return (current := SchoolTerm.current) and root.pk == current.pk
+
 
 class SchoolTermBatchCreateMutation(BaseBatchCreateMutation):
     class Meta:
@@ -45,3 +52,17 @@ class SchoolTermBatchPatchMutation(BaseBatchPatchMutation):
         model = SchoolTerm
         permissions = ("core.edit_schoolterm_rule",)
         only_fields = ("id", "name", "date_start", "date_end")
+
+
+class SetActiveSchoolTermMutation(graphene.Mutation):
+    class Arguments:
+        id = graphene.ID(required=True)  # noqa
+
+    active_school_term = graphene.Field(SchoolTermType)
+
+    @classmethod
+    def mutate(cls, root, info, id):  # noqa
+        school_term = SchoolTerm.objects.get(id=id)
+        info.context.session["active_school_term"] = school_term.pk
+
+        return SetActiveSchoolTermMutation(active_school_term=school_term)
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index d9331c3f00668de5ddf917b7073cf47c494d6ecb..bf44b9e1816498aec55760e4e494d9935d17a55d 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -11,7 +11,7 @@ from warnings import warn
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
 from django.core.files import File
-from django.db.models import Model, QuerySet
+from django.db.models import Model, Q, QuerySet
 from django.http import HttpRequest
 from django.shortcuts import get_object_or_404
 from django.urls import reverse
@@ -568,3 +568,25 @@ class ExtendedICal20Feed(feedgenerator.ICal20Feed):
         if start and end:
             return events.between(start, end)
         return events.all()
+
+
+def get_active_school_term(request):
+    from ..models import SchoolTerm
+
+    pk = request.session.get("active_school_term", None)
+    if pk:
+        return SchoolTerm.objects.get(pk=pk)
+    else:
+        return SchoolTerm.current
+
+
+def filter_active_school_term(
+    request,
+    q,
+    school_term_field="school_term",
+):
+    if active_school_term := get_active_school_term(request):
+        return q.filter(
+            Q(**{school_term_field: active_school_term}) | Q(**{school_term_field: None})
+        )
+    return q
diff --git a/aleksis/core/util/tables.py b/aleksis/core/util/tables.py
index 8bb9fd5c0b4212a1361ebd12b6acd5087731ca58..00bcc95297f2481eb81e81395bca2c7e1b2b368f 100644
--- a/aleksis/core/util/tables.py
+++ b/aleksis/core/util/tables.py
@@ -30,7 +30,7 @@ class MaterializeCheckboxColumn(CheckBoxColumn):
         attrs = dict(default, **(specific or general or {}))
         attrs = computed_values(attrs, kwargs={"record": record, "value": value})
         return mark_safe(  # noqa
-            "<label><input %s/><span></span</label>" % AttributeDict(attrs).as_html()
+            f"<label><input {AttributeDict(attrs).as_html()}/><span></span</label>"
         )