diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fe4d4446e760b1ef6a270759725ffa6ba2cd7c5e..55ef59013fcb8d31491024c17bbbfb9a707653b4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: python:3.9
+image: python:3.10
 
 services:
   - mysql
diff --git a/AKModel/availability/models.py b/AKModel/availability/models.py
index 7ce794dcda52fcbde462584379e1447ad2b124f2..35814ee06d2bf49fe9416710c71cbebbb4fc7bb4 100644
--- a/AKModel/availability/models.py
+++ b/AKModel/availability/models.py
@@ -151,9 +151,12 @@ class Availability(models.Model):
         if not other.overlaps(self, strict=False):
             raise Exception('Only overlapping Availabilities can be merged.')
 
-        return Availability(
+        avail = Availability(
             start=min(self.start, other.start), end=max(self.end, other.end)
         )
+        if self.event == other.event:
+            avail.event = self.event
+        return avail
 
     def __or__(self, other: 'Availability') -> 'Availability':
         """Performs the merge operation: ``availability1 | availability2``"""
@@ -168,9 +171,12 @@ class Availability(models.Model):
         if not other.overlaps(self, False):
             raise Exception('Only overlapping Availabilities can be intersected.')
 
-        return Availability(
+        avail = Availability(
             start=max(self.start, other.start), end=min(self.end, other.end)
         )
+        if self.event == other.event:
+            avail.event = self.event
+        return avail
 
     def __and__(self, other: 'Availability') -> 'Availability':
         """Performs the intersect operation: ``availability1 &
@@ -247,7 +253,14 @@ class Availability(models.Model):
                 f'{self.end.astimezone(self.event.timezone).strftime("%a %H:%M")}')
 
     @classmethod
-    def with_event_length(cls, event, person=None, room=None, ak=None, ak_category=None):
+    def with_event_length(
+        cls,
+        event: Event,
+        person: AKOwner | None = None,
+        room: Room | None = None,
+        ak: AK | None = None,
+        ak_category: AKCategory | None = None,
+    ) -> "Availability":
         """
         Create an availability covering exactly the time between event start and event end.
         Can e.g., be used to create default availabilities.
@@ -267,6 +280,21 @@ class Availability(models.Model):
         return Availability(start=timeframe_start, end=timeframe_end, event=event, person=person,
                                     room=room, ak=ak, ak_category=ak_category)
 
+    @classmethod
+    def is_event_covered(cls, event: Event, availabilities: List['Availability']) -> bool:
+        """Check if list of availibilities cover whole event.
+
+        :param event: event to check.
+        :param availabilities: availabilities to check.
+        :return: whether the availabilities cover full event.
+        :rtype: bool
+        """
+        # NOTE: Cannot use `Availability.with_event_length` as its end is the
+        #       event end + 1 day
+        full_event = Availability(event=event, start=event.start, end=event.end)
+        avail_union = Availability.union(availabilities)
+        return not avail_union or avail_union[0].contains(full_event)
+
     class Meta:
         verbose_name = _('Availability')
         verbose_name_plural = _('Availabilities')
diff --git a/AKModel/forms.py b/AKModel/forms.py
index 4d1fe7ef7bfa45d41b377fa4c9815e951e35cf19..74ca1b6813f0365d1179166da18aeb8a8c59ca4e 100644
--- a/AKModel/forms.py
+++ b/AKModel/forms.py
@@ -272,3 +272,13 @@ class RoomFormWithAvailabilities(AvailabilitiesFormMixin, RoomForm):
         # Filter possible values for m2m when event is specified
         if hasattr(self.instance, "event") and self.instance.event is not None:
             self.fields["properties"].queryset = AKRequirement.objects.filter(event=self.instance.event)
+
+
+class JSONImportForm(AdminIntermediateForm):
+    """Form to import an AK schedule from a json file."""
+    json_data = forms.CharField(
+        required=True,
+        widget=forms.Textarea,
+        label=_("JSON data"),
+        help_text=_("JSON data from the scheduling solver"),
+    )
diff --git a/AKModel/locale/de_DE/LC_MESSAGES/django.po b/AKModel/locale/de_DE/LC_MESSAGES/django.po
index 261c5b2c06e56ee965f4d11f895e00b83f017cbd..4cc752ae7d4ac4da883547ad18a5fbe866d816d7 100644
--- a/AKModel/locale/de_DE/LC_MESSAGES/django.po
+++ b/AKModel/locale/de_DE/LC_MESSAGES/django.po
@@ -2,7 +2,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-04-25 01:29+0200\n"
+"POT-Creation-Date: 2024-05-31 14:22+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -25,18 +25,17 @@ msgstr "Status"
 msgid "Toggle plan visibility"
 msgstr "Plansichtbarkeit ändern"
 
-#: AKModel/admin.py:110 AKModel/admin.py:121 AKModel/views/manage.py:138
+#: AKModel/admin.py:110 AKModel/admin.py:121 AKModel/views/manage.py:140
 msgid "Publish plan"
 msgstr "Plan veröffentlichen"
 
-#: AKModel/admin.py:113 AKModel/admin.py:129 AKModel/views/manage.py:151
+#: AKModel/admin.py:113 AKModel/admin.py:129 AKModel/views/manage.py:153
 msgid "Unpublish plan"
 msgstr "Plan verbergen"
 
-#: AKModel/admin.py:168 AKModel/models.py:360 AKModel/models.py:682
-#: AKModel/templates/admin/AKModel/aks_by_user.html:12
+#: AKModel/admin.py:168 AKModel/models.py:612 AKModel/models.py:1028
 #: AKModel/templates/admin/AKModel/status/event_aks.html:10
-#: AKModel/views/manage.py:73 AKModel/views/status.py:98
+#: AKModel/views/manage.py:75 AKModel/views/status.py:97
 msgid "AKs"
 msgstr "AKs"
 
@@ -60,11 +59,11 @@ msgstr "In Wiki-Syntax exportieren"
 msgid "Cannot export AKs from more than one event at the same time."
 msgstr "Kann nicht AKs von mehreren Events zur selben Zeit exportieren."
 
-#: AKModel/admin.py:320 AKModel/views/ak.py:99
+#: AKModel/admin.py:320 AKModel/views/ak.py:226
 msgid "Reset interest in AKs"
 msgstr "Interesse an AKs zurücksetzen"
 
-#: AKModel/admin.py:330 AKModel/views/ak.py:114
+#: AKModel/admin.py:330 AKModel/views/ak.py:241
 msgid "Reset AKs' interest counters"
 msgstr "Interessenszähler der AKs zurücksetzen"
 
@@ -72,19 +71,19 @@ msgstr "Interessenszähler der AKs zurücksetzen"
 msgid "AK Details"
 msgstr "AK-Details"
 
-#: AKModel/admin.py:505 AKModel/views/manage.py:99
+#: AKModel/admin.py:505 AKModel/views/manage.py:101
 msgid "Mark Constraint Violations as manually resolved"
 msgstr "Markiere Constraintverletzungen als manuell behoben"
 
-#: AKModel/admin.py:514 AKModel/views/manage.py:112
+#: AKModel/admin.py:514 AKModel/views/manage.py:114
 msgid "Set Constraint Violations to level \"violation\""
 msgstr "Constraintverletzungen auf Level \"Violation\" setzen"
 
-#: AKModel/admin.py:523 AKModel/views/manage.py:125
+#: AKModel/admin.py:523 AKModel/views/manage.py:127
 msgid "Set Constraint Violations to level \"warning\""
 msgstr "Constraintverletzungen auf Level \"Warning\" setzen"
 
-#: AKModel/availability/forms.py:25 AKModel/availability/models.py:271
+#: AKModel/availability/forms.py:25 AKModel/availability/models.py:299
 msgid "Availability"
 msgstr "Verfügbarkeit"
 
@@ -109,17 +108,17 @@ msgstr "Die eingegebene Verfügbarkeit enthält ein ungültiges Datum."
 msgid "Please fill in your availabilities!"
 msgstr "Bitte Verfügbarkeiten eintragen!"
 
-#: AKModel/availability/models.py:43 AKModel/models.py:60 AKModel/models.py:174
-#: AKModel/models.py:251 AKModel/models.py:270 AKModel/models.py:296
-#: AKModel/models.py:350 AKModel/models.py:492 AKModel/models.py:531
-#: AKModel/models.py:621 AKModel/models.py:678 AKModel/models.py:869
+#: AKModel/availability/models.py:43 AKModel/models.py:91 AKModel/models.py:412
+#: AKModel/models.py:489 AKModel/models.py:522 AKModel/models.py:548
+#: AKModel/models.py:602 AKModel/models.py:744 AKModel/models.py:820
+#: AKModel/models.py:967 AKModel/models.py:1024 AKModel/models.py:1215
 msgid "Event"
 msgstr "Event"
 
-#: AKModel/availability/models.py:44 AKModel/models.py:175
-#: AKModel/models.py:252 AKModel/models.py:271 AKModel/models.py:297
-#: AKModel/models.py:351 AKModel/models.py:493 AKModel/models.py:532
-#: AKModel/models.py:622 AKModel/models.py:679 AKModel/models.py:870
+#: AKModel/availability/models.py:44 AKModel/models.py:413
+#: AKModel/models.py:490 AKModel/models.py:523 AKModel/models.py:549
+#: AKModel/models.py:603 AKModel/models.py:745 AKModel/models.py:821
+#: AKModel/models.py:968 AKModel/models.py:1025 AKModel/models.py:1216
 msgid "Associated event"
 msgstr "Zugehöriges Event"
 
@@ -131,8 +130,8 @@ msgstr "Person"
 msgid "Person whose availability this is"
 msgstr "Person deren Verfügbarkeit hier abgebildet wird"
 
-#: AKModel/availability/models.py:61 AKModel/models.py:496
-#: AKModel/models.py:521 AKModel/models.py:688
+#: AKModel/availability/models.py:61 AKModel/models.py:748
+#: AKModel/models.py:810 AKModel/models.py:1034
 msgid "Room"
 msgstr "Raum"
 
@@ -140,8 +139,8 @@ msgstr "Raum"
 msgid "Room whose availability this is"
 msgstr "Raum dessen Verfügbarkeit hier abgebildet wird"
 
-#: AKModel/availability/models.py:70 AKModel/models.py:359
-#: AKModel/models.py:520 AKModel/models.py:616
+#: AKModel/availability/models.py:70 AKModel/models.py:611
+#: AKModel/models.py:809 AKModel/models.py:962
 msgid "AK"
 msgstr "AK"
 
@@ -149,8 +148,8 @@ msgstr "AK"
 msgid "AK whose availability this is"
 msgstr "Verfügbarkeiten"
 
-#: AKModel/availability/models.py:79 AKModel/models.py:255
-#: AKModel/models.py:694
+#: AKModel/availability/models.py:79 AKModel/models.py:493
+#: AKModel/models.py:1040
 msgid "AK Category"
 msgstr "AK-Kategorie"
 
@@ -158,7 +157,7 @@ msgstr "AK-Kategorie"
 msgid "AK Category whose availability this is"
 msgstr "AK-Kategorie, deren Verfügbarkeit hier abgebildet wird"
 
-#: AKModel/availability/models.py:272
+#: AKModel/availability/models.py:300
 msgid "Availabilities"
 msgstr "Verfügbarkeiten"
 
@@ -220,7 +219,7 @@ msgstr ""
 "fürWünsche markieren, z.B. um während der Präsentation auf einem Touchscreen "
 "ausgefüllt zu werden?"
 
-#: AKModel/forms.py:189 AKModel/models.py:863
+#: AKModel/forms.py:189 AKModel/models.py:1209
 msgid "Default Slots"
 msgstr "Standardslots"
 
@@ -259,7 +258,15 @@ msgstr "Standardverfügbarkeiten für alle Räume anlegen?"
 msgid "CSV must contain a name column"
 msgstr "CSV muss eine name-Spalte enthalten"
 
-#: AKModel/metaviews/admin.py:156 AKModel/models.py:29
+#: AKModel/forms.py:282
+msgid "JSON data"
+msgstr "JSON-Daten"
+
+#: AKModel/forms.py:283
+msgid "JSON data from the scheduling solver"
+msgstr "JSON-Daten, die der scheduling-solver produziert hat"
+
+#: AKModel/metaviews/admin.py:156 AKModel/models.py:60
 msgid "Start"
 msgstr "Start"
 
@@ -284,66 +291,66 @@ msgstr "Aktivieren?"
 msgid "Finish"
 msgstr "Abschluss"
 
-#: AKModel/models.py:20 AKModel/models.py:243 AKModel/models.py:267
-#: AKModel/models.py:294 AKModel/models.py:312 AKModel/models.py:484
+#: AKModel/models.py:51 AKModel/models.py:481 AKModel/models.py:519
+#: AKModel/models.py:546 AKModel/models.py:564 AKModel/models.py:736
 msgid "Name"
 msgstr "Name"
 
-#: AKModel/models.py:21
+#: AKModel/models.py:52
 msgid "Name or iteration of the event"
 msgstr "Name oder Iteration des Events"
 
-#: AKModel/models.py:22
+#: AKModel/models.py:53
 msgid "Short Form"
 msgstr "Kurzer Name"
 
-#: AKModel/models.py:23
+#: AKModel/models.py:54
 msgid "Short name of letters/numbers/dots/dashes/underscores used in URLs."
 msgstr ""
 "Kurzname bestehend aus Buchstaben, Nummern, Punkten und Unterstrichen zur "
 "Nutzung in URLs"
 
-#: AKModel/models.py:25
+#: AKModel/models.py:56
 msgid "Place"
 msgstr "Ort"
 
-#: AKModel/models.py:26
+#: AKModel/models.py:57
 msgid "City etc. the event takes place in"
 msgstr "Stadt o.ä. in der das Event stattfindet"
 
-#: AKModel/models.py:28
+#: AKModel/models.py:59
 msgid "Time Zone"
 msgstr "Zeitzone"
 
-#: AKModel/models.py:28
+#: AKModel/models.py:59
 msgid "Time Zone where this event takes place in"
 msgstr "Zeitzone in der das Event stattfindet"
 
-#: AKModel/models.py:29
+#: AKModel/models.py:60
 msgid "Time the event begins"
 msgstr "Zeit zu der das Event beginnt"
 
-#: AKModel/models.py:30
+#: AKModel/models.py:61
 msgid "End"
 msgstr "Ende"
 
-#: AKModel/models.py:30
+#: AKModel/models.py:61
 msgid "Time the event ends"
 msgstr "Zeit zu der das Event endet"
 
-#: AKModel/models.py:31
+#: AKModel/models.py:62
 msgid "Resolution Deadline"
 msgstr "Resolutionsdeadline"
 
-#: AKModel/models.py:32
+#: AKModel/models.py:63
 msgid "When should AKs with intention to submit a resolution be done?"
 msgstr "Wann sollen AKs mit Resolutionsabsicht stattgefunden haben?"
 
-#: AKModel/models.py:34
+#: AKModel/models.py:65
 msgid "Interest Window Start"
 msgstr "Beginn Interessensbekundung"
 
-#: AKModel/models.py:36
+#: AKModel/models.py:67
 msgid ""
 "Opening time for expression of interest. When left blank, no interest "
 "indication will be possible."
@@ -351,71 +358,71 @@ msgstr ""
 "Öffnungszeitpunkt für die Angabe von Interesse an AKs.Wenn das Feld leer "
 "bleibt, wird keine Abgabe von Interesse möglich sein."
 
-#: AKModel/models.py:38
+#: AKModel/models.py:69
 msgid "Interest Window End"
 msgstr "Ende Interessensbekundung"
 
-#: AKModel/models.py:39
+#: AKModel/models.py:70
 msgid "Closing time for expression of interest."
 msgstr "Öffnungszeitpunkt für die Angabe von Interesse an AKs."
 
-#: AKModel/models.py:41
+#: AKModel/models.py:72
 msgid "Public event"
 msgstr "Öffentliches Event"
 
-#: AKModel/models.py:42
+#: AKModel/models.py:73
 msgid "Show this event on overview page."
 msgstr "Zeige dieses Event auf der Übersichtseite an"
 
-#: AKModel/models.py:44
+#: AKModel/models.py:75
 msgid "Active State"
 msgstr "Aktiver Status"
 
-#: AKModel/models.py:44
+#: AKModel/models.py:75
 msgid "Marks currently active events"
 msgstr "Markiert aktuell aktive Events"
 
-#: AKModel/models.py:45
+#: AKModel/models.py:76
 msgid "Plan Hidden"
 msgstr "Plan verborgen"
 
-#: AKModel/models.py:45
+#: AKModel/models.py:76
 msgid "Hides plan for non-staff users"
 msgstr "Verbirgt den Plan für Nutzer*innen ohne erweiterte Rechte"
 
-#: AKModel/models.py:47
+#: AKModel/models.py:78
 msgid "Plan published at"
 msgstr "Plan veröffentlicht am/um"
 
-#: AKModel/models.py:48
+#: AKModel/models.py:79
 msgid "Timestamp at which the plan was published"
 msgstr "Zeitpunkt, zu dem der Plan veröffentlicht wurde"
 
-#: AKModel/models.py:50
+#: AKModel/models.py:81
 msgid "Base URL"
 msgstr "URL-Prefix"
 
-#: AKModel/models.py:50
+#: AKModel/models.py:81
 msgid "Prefix for wiki link construction"
 msgstr "Prefix für die automatische Generierung von Wiki-Links"
 
-#: AKModel/models.py:51
+#: AKModel/models.py:82
 msgid "Wiki Export Template Name"
 msgstr "Wiki-Export Templatename"
 
-#: AKModel/models.py:52
+#: AKModel/models.py:83
 msgid "Default Slot Length"
 msgstr "Standardslotlänge"
 
-#: AKModel/models.py:53
+#: AKModel/models.py:84
 msgid "Default length in hours that is assumed for AKs in this event."
 msgstr "Standardlänge von Slots (in Stunden) für dieses Event"
 
-#: AKModel/models.py:55
+#: AKModel/models.py:86
 msgid "Contact email address"
 msgstr "E-Mail Kontaktadresse"
 
-#: AKModel/models.py:56
+#: AKModel/models.py:87
 msgid ""
 "An email address that is displayed on every page and can be used for all "
 "kinds of questions"
@@ -423,75 +430,75 @@ msgstr ""
 "Eine Mailadresse die auf jeder Seite angezeigt wird und für alle Arten von "
 "Fragen genutzt werden kann"
 
-#: AKModel/models.py:61
+#: AKModel/models.py:92
 msgid "Events"
 msgstr "Events"
 
-#: AKModel/models.py:169
+#: AKModel/models.py:407
 msgid "Nickname"
 msgstr "Spitzname"
 
-#: AKModel/models.py:169
+#: AKModel/models.py:407
 msgid "Name to identify an AK owner by"
 msgstr "Name, durch den eine AK-Leitung identifiziert wird"
 
-#: AKModel/models.py:170
+#: AKModel/models.py:408
 msgid "Slug"
 msgstr "Slug"
 
-#: AKModel/models.py:170
+#: AKModel/models.py:408
 msgid "Slug for URL generation"
 msgstr "Slug für URL-Generierung"
 
-#: AKModel/models.py:171
+#: AKModel/models.py:409
 msgid "Institution"
 msgstr "Instutution"
 
-#: AKModel/models.py:171
+#: AKModel/models.py:409
 msgid "Uni etc."
 msgstr "Universität o.ä."
 
-#: AKModel/models.py:172 AKModel/models.py:321
+#: AKModel/models.py:410 AKModel/models.py:573
 msgid "Web Link"
 msgstr "Internet Link"
 
-#: AKModel/models.py:172
+#: AKModel/models.py:410
 msgid "Link to Homepage"
 msgstr "Link zu Homepage oder Webseite"
 
-#: AKModel/models.py:178 AKModel/models.py:687
+#: AKModel/models.py:416 AKModel/models.py:1033
 msgid "AK Owner"
 msgstr "AK-Leitung"
 
-#: AKModel/models.py:179
+#: AKModel/models.py:417
 msgid "AK Owners"
 msgstr "AK-Leitungen"
 
-#: AKModel/models.py:243
+#: AKModel/models.py:481
 msgid "Name of the AK Category"
 msgstr "Name der AK-Kategorie"
 
-#: AKModel/models.py:244 AKModel/models.py:268
+#: AKModel/models.py:482 AKModel/models.py:520
 msgid "Color"
 msgstr "Farbe"
 
-#: AKModel/models.py:244 AKModel/models.py:268
+#: AKModel/models.py:482 AKModel/models.py:520
 msgid "Color for displaying"
 msgstr "Farbe für die Anzeige"
 
-#: AKModel/models.py:245 AKModel/models.py:315
+#: AKModel/models.py:483 AKModel/models.py:567
 msgid "Description"
 msgstr "Beschreibung"
 
-#: AKModel/models.py:246
+#: AKModel/models.py:484
 msgid "Short description of this AK Category"
 msgstr "Beschreibung der AK-Kategorie"
 
-#: AKModel/models.py:247
+#: AKModel/models.py:485
 msgid "Present by default"
 msgstr "Defaultmäßig präsentieren"
 
-#: AKModel/models.py:248
+#: AKModel/models.py:486
 msgid ""
 "Present AKs of this category by default if AK owner did not specify whether "
 "this AK should be presented?"
@@ -499,132 +506,132 @@ msgstr ""
 "AKs dieser Kategorie standardmäßig vorstellen, wenn die Leitungen das für "
 "ihren AK nicht explizit spezifiziert haben?"
 
-#: AKModel/models.py:256
+#: AKModel/models.py:494
 msgid "AK Categories"
 msgstr "AK-Kategorien"
 
-#: AKModel/models.py:267
+#: AKModel/models.py:519
 msgid "Name of the AK Track"
 msgstr "Name des AK-Tracks"
 
-#: AKModel/models.py:274
+#: AKModel/models.py:526
 msgid "AK Track"
 msgstr "AK-Track"
 
-#: AKModel/models.py:275
+#: AKModel/models.py:527
 msgid "AK Tracks"
 msgstr "AK-Tracks"
 
-#: AKModel/models.py:294
+#: AKModel/models.py:546
 msgid "Name of the Requirement"
 msgstr "Name der Anforderung"
 
-#: AKModel/models.py:300 AKModel/models.py:691
+#: AKModel/models.py:552 AKModel/models.py:1037
 msgid "AK Requirement"
 msgstr "AK-Anforderung"
 
-#: AKModel/models.py:301
+#: AKModel/models.py:553
 msgid "AK Requirements"
 msgstr "AK-Anforderungen"
 
-#: AKModel/models.py:312
+#: AKModel/models.py:564
 msgid "Name of the AK"
 msgstr "Name des AKs"
 
-#: AKModel/models.py:313
+#: AKModel/models.py:565
 msgid "Short Name"
 msgstr "Kurzer Name"
 
-#: AKModel/models.py:314
+#: AKModel/models.py:566
 msgid "Name displayed in the schedule"
 msgstr "Name zur Anzeige im AK-Plan"
 
-#: AKModel/models.py:315
+#: AKModel/models.py:567
 msgid "Description of the AK"
 msgstr "Beschreibung des AKs"
 
-#: AKModel/models.py:317
+#: AKModel/models.py:569
 msgid "Owners"
 msgstr "Leitungen"
 
-#: AKModel/models.py:318
+#: AKModel/models.py:570
 msgid "Those organizing the AK"
 msgstr "Menschen, die den AK organisieren und halten"
 
-#: AKModel/models.py:321
+#: AKModel/models.py:573
 msgid "Link to wiki page"
 msgstr "Link zur Wiki Seite"
 
-#: AKModel/models.py:322
+#: AKModel/models.py:574
 msgid "Protocol Link"
 msgstr "Protokolllink"
 
-#: AKModel/models.py:322
+#: AKModel/models.py:574
 msgid "Link to protocol"
 msgstr "Link zum Protokoll"
 
-#: AKModel/models.py:324
+#: AKModel/models.py:576
 msgid "Category"
 msgstr "Kategorie"
 
-#: AKModel/models.py:325
+#: AKModel/models.py:577
 msgid "Category of the AK"
 msgstr "Kategorie des AKs"
 
-#: AKModel/models.py:326
+#: AKModel/models.py:578
 msgid "Track"
 msgstr "Track"
 
-#: AKModel/models.py:327
+#: AKModel/models.py:579
 msgid "Track the AK belongs to"
 msgstr "Track zu dem der AK gehört"
 
-#: AKModel/models.py:329
+#: AKModel/models.py:581
 msgid "Resolution Intention"
 msgstr "Resolutionsabsicht"
 
-#: AKModel/models.py:330
+#: AKModel/models.py:582
 msgid "Intends to submit a resolution"
 msgstr "Beabsichtigt eine Resolution einzureichen"
 
-#: AKModel/models.py:331
+#: AKModel/models.py:583
 msgid "Present this AK"
 msgstr "AK präsentieren"
 
-#: AKModel/models.py:332
+#: AKModel/models.py:584
 msgid "Present results of this AK"
 msgstr "Die Ergebnisse dieses AKs vorstellen"
 
-#: AKModel/models.py:334 AKModel/views/status.py:163
+#: AKModel/models.py:586 AKModel/views/status.py:170
 msgid "Requirements"
 msgstr "Anforderungen"
 
-#: AKModel/models.py:335
+#: AKModel/models.py:587
 msgid "AK's Requirements"
 msgstr "Anforderungen des AKs"
 
-#: AKModel/models.py:337
+#: AKModel/models.py:589
 msgid "Conflicting AKs"
 msgstr "AK-Konflikte"
 
-#: AKModel/models.py:338
+#: AKModel/models.py:590
 msgid "AKs that conflict and thus must not take place at the same time"
 msgstr ""
 "AKs, die Konflikte haben und deshalb nicht gleichzeitig stattfinden dürfen"
 
-#: AKModel/models.py:339
+#: AKModel/models.py:591
 msgid "Prerequisite AKs"
 msgstr "Vorausgesetzte AKs"
 
-#: AKModel/models.py:340
+#: AKModel/models.py:592
 msgid "AKs that should precede this AK in the schedule"
 msgstr "AKs die im AK-Plan vor diesem AK stattfinden müssen"
 
-#: AKModel/models.py:342
+#: AKModel/models.py:594
 msgid "Organizational Notes"
 msgstr "Notizen zur Organisation"
 
-#: AKModel/models.py:343
+#: AKModel/models.py:595
 msgid ""
 "Notes to organizers. These are public. For private notes, please use the "
 "button for private messages on the detail page of this AK (after creation/"
@@ -634,289 +641,291 @@ msgstr ""
 "Anmerkungen bitte den Button für Direktnachrichten verwenden (nach dem "
 "Anlegen/Bearbeiten)."
 
-#: AKModel/models.py:346
+#: AKModel/models.py:598
 msgid "Interest"
 msgstr "Interesse"
 
-#: AKModel/models.py:346
+#: AKModel/models.py:598
 msgid "Expected number of people"
 msgstr "Erwartete Personenzahl"
 
-#: AKModel/models.py:347
+#: AKModel/models.py:599
 msgid "Interest Counter"
 msgstr "Interessenszähler"
 
-#: AKModel/models.py:348
+#: AKModel/models.py:600
 msgid "People who have indicated interest online"
 msgstr "Anzahl Personen, die online Interesse bekundet haben"
 
-#: AKModel/models.py:353
+#: AKModel/models.py:605
 msgid "Export?"
 msgstr "Export?"
 
-#: AKModel/models.py:354
+#: AKModel/models.py:606
 msgid "Include AK in wiki export?"
 msgstr "AK bei Wiki-Export berücksichtigen?"
 
-#: AKModel/models.py:484
+#: AKModel/models.py:736
 msgid "Name or number of the room"
 msgstr "Name oder Nummer des Raums"
 
-#: AKModel/models.py:485
+#: AKModel/models.py:737
 msgid "Location"
 msgstr "Ort"
 
-#: AKModel/models.py:486
+#: AKModel/models.py:738
 msgid "Name or number of the location"
 msgstr "Name oder Nummer des Ortes"
 
-#: AKModel/models.py:487
+#: AKModel/models.py:739
 msgid "Capacity"
 msgstr "Kapazität"
 
-#: AKModel/models.py:488
+#: AKModel/models.py:740
 msgid "Maximum number of people (-1 for unlimited)."
 msgstr "Maximale Personenzahl (-1 wenn unbeschränkt)."
 
-#: AKModel/models.py:489
+#: AKModel/models.py:741
 msgid "Properties"
 msgstr "Eigenschaften"
 
-#: AKModel/models.py:490
+#: AKModel/models.py:742
 msgid "AK requirements fulfilled by the room"
 msgstr "AK-Anforderungen, die dieser Raum erfüllt"
 
-#: AKModel/models.py:497 AKModel/views/status.py:60
+#: AKModel/models.py:749 AKModel/views/status.py:59
 msgid "Rooms"
 msgstr "Räume"
 
-#: AKModel/models.py:520
+#: AKModel/models.py:809
 msgid "AK being mapped"
 msgstr "AK, der zugeordnet wird"
 
-#: AKModel/models.py:522
+#: AKModel/models.py:811
 msgid "Room the AK will take place in"
 msgstr "Raum in dem der AK stattfindet"
 
-#: AKModel/models.py:523 AKModel/models.py:866
+#: AKModel/models.py:812 AKModel/models.py:1212
 msgid "Slot Begin"
 msgstr "Beginn des Slots"
 
-#: AKModel/models.py:523 AKModel/models.py:866
+#: AKModel/models.py:812 AKModel/models.py:1212
 msgid "Time and date the slot begins"
 msgstr "Zeit und Datum zu der der AK beginnt"
 
-#: AKModel/models.py:525
+#: AKModel/models.py:814
 msgid "Duration"
 msgstr "Dauer"
 
-#: AKModel/models.py:526
+#: AKModel/models.py:815
 msgid "Length in hours"
 msgstr "Länge in Stunden"
 
-#: AKModel/models.py:528
+#: AKModel/models.py:817
 msgid "Scheduling fixed"
 msgstr "Planung fix"
 
-#: AKModel/models.py:529
+#: AKModel/models.py:818
 msgid "Length and time of this AK should not be changed"
 msgstr "Dauer und Zeit dieses AKs sollten nicht verändert werden"
 
-#: AKModel/models.py:534
+#: AKModel/models.py:823
 msgid "Last update"
 msgstr "Letzte Aktualisierung"
 
-#: AKModel/models.py:537
+#: AKModel/models.py:826
 msgid "AK Slot"
 msgstr "AK-Slot"
 
-#: AKModel/models.py:538 AKModel/models.py:684
+#: AKModel/models.py:827 AKModel/models.py:1030
 msgid "AK Slots"
 msgstr "AK-Slot"
 
-#: AKModel/models.py:560 AKModel/models.py:569
+#: AKModel/models.py:849 AKModel/models.py:858
 msgid "Not scheduled yet"
 msgstr "Noch nicht geplant"
 
-#: AKModel/models.py:617
+#: AKModel/models.py:963
 msgid "AK this message belongs to"
 msgstr "AK zu dem die Nachricht gehört"
 
-#: AKModel/models.py:618
+#: AKModel/models.py:964
 msgid "Message text"
 msgstr "Nachrichtentext"
 
-#: AKModel/models.py:619
+#: AKModel/models.py:965
 msgid "Message to the organizers. This is not publicly visible."
 msgstr ""
 "Nachricht an die Organisator*innen. Diese ist nicht öffentlich sichtbar."
 
-#: AKModel/models.py:623
+#: AKModel/models.py:969
 msgid "Resolved"
 msgstr "Erledigt"
 
-#: AKModel/models.py:624
+#: AKModel/models.py:970
 msgid "This message has been resolved (no further action needed)"
-msgstr "Diese Nachricht wurde vollständig bearbeitet (keine weiteren Aktionen notwendig)"
+msgstr ""
+"Diese Nachricht wurde vollständig bearbeitet (keine weiteren Aktionen "
+"notwendig)"
 
-#: AKModel/models.py:627
+#: AKModel/models.py:973
 msgid "AK Orga Message"
 msgstr "AK-Organachricht"
 
-#: AKModel/models.py:628
+#: AKModel/models.py:974
 msgid "AK Orga Messages"
 msgstr "AK-Organachrichten"
 
-#: AKModel/models.py:645
+#: AKModel/models.py:991
 msgid "Constraint Violation"
 msgstr "Constraintverletzung"
 
-#: AKModel/models.py:646
+#: AKModel/models.py:992
 msgid "Constraint Violations"
 msgstr "Constraintverletzungen"
 
-#: AKModel/models.py:653
+#: AKModel/models.py:999
 msgid "Owner has two parallel slots"
 msgstr "Leitung hat zwei Slots parallel"
 
-#: AKModel/models.py:654
+#: AKModel/models.py:1000
 msgid "AK Slot was scheduled outside the AK's availabilities"
 msgstr "AK Slot wurde außerhalb der Verfügbarkeit des AKs platziert"
 
-#: AKModel/models.py:655
+#: AKModel/models.py:1001
 msgid "Room has two AK slots scheduled at the same time"
 msgstr "Raum hat zwei AK Slots gleichzeitig"
 
-#: AKModel/models.py:656
+#: AKModel/models.py:1002
 msgid "Room does not satisfy the requirement of the scheduled AK"
 msgstr "Room erfüllt die Anforderungen des platzierten AKs nicht"
 
-#: AKModel/models.py:657
+#: AKModel/models.py:1003
 msgid "AK Slot is scheduled at the same time as an AK listed as a conflict"
 msgstr ""
 "AK Slot wurde wurde zur gleichen Zeit wie ein Konflikt des AKs platziert"
 
-#: AKModel/models.py:658
+#: AKModel/models.py:1004
 msgid "AK Slot is scheduled before an AK listed as a prerequisite"
 msgstr "AK Slot wurde vor einem als Voraussetzung gelisteten AK platziert"
 
-#: AKModel/models.py:660
+#: AKModel/models.py:1006
 msgid ""
 "AK Slot for AK with intention to submit a resolution is scheduled after "
 "resolution deadline"
 msgstr ""
 "AK Slot eines AKs mit Resoabsicht wurde nach der Resodeadline platziert"
 
-#: AKModel/models.py:661
+#: AKModel/models.py:1007
 msgid "AK Slot in a category is outside that categories availabilities"
 msgstr "AK Slot wurde außerhalb der Verfügbarkeiten seiner Kategorie"
 
-#: AKModel/models.py:662
+#: AKModel/models.py:1008
 msgid "Two AK Slots for the same AK scheduled at the same time"
 msgstr "Zwei AK Slots eines AKs wurden zur selben Zeit platziert"
 
-#: AKModel/models.py:663
+#: AKModel/models.py:1009
 msgid "Room does not have enough space for interest in scheduled AK Slot"
 msgstr "Room hat nicht genug Platz für das Interesse am geplanten AK-Slot"
 
-#: AKModel/models.py:664
+#: AKModel/models.py:1010
 msgid "AK Slot is scheduled outside the event's availabilities"
 msgstr "AK Slot wurde außerhalb der Verfügbarkeit des Events platziert"
 
-#: AKModel/models.py:670
+#: AKModel/models.py:1016
 msgid "Warning"
 msgstr "Warnung"
 
-#: AKModel/models.py:671
+#: AKModel/models.py:1017
 msgid "Violation"
 msgstr "Verletzung"
 
-#: AKModel/models.py:673
+#: AKModel/models.py:1019
 msgid "Type"
 msgstr "Art"
 
-#: AKModel/models.py:674
+#: AKModel/models.py:1020
 msgid "Type of violation, i.e. what kind of constraint was violated"
 msgstr "Art der Verletzung, gibt an welche Art Constraint verletzt wurde"
 
-#: AKModel/models.py:675
+#: AKModel/models.py:1021
 msgid "Level"
 msgstr "Level"
 
-#: AKModel/models.py:676
+#: AKModel/models.py:1022
 msgid "Severity level of the violation"
 msgstr "Schweregrad der Verletzung"
 
-#: AKModel/models.py:683
+#: AKModel/models.py:1029
 msgid "AK(s) belonging to this constraint"
 msgstr "AK(s), die zu diesem Constraint gehören"
 
-#: AKModel/models.py:685
+#: AKModel/models.py:1031
 msgid "AK Slot(s) belonging to this constraint"
 msgstr "AK Slot(s), die zu diesem Constraint gehören"
 
-#: AKModel/models.py:687
+#: AKModel/models.py:1033
 msgid "AK Owner belonging to this constraint"
 msgstr "AK Leitung(en), die zu diesem Constraint gehören"
 
-#: AKModel/models.py:689
+#: AKModel/models.py:1035
 msgid "Room belonging to this constraint"
 msgstr "Raum, der zu diesem Constraint gehört"
 
-#: AKModel/models.py:692
+#: AKModel/models.py:1038
 msgid "AK Requirement belonging to this constraint"
 msgstr "AK Anforderung, die zu diesem Constraint gehört"
 
-#: AKModel/models.py:694
+#: AKModel/models.py:1040
 msgid "AK Category belonging to this constraint"
 msgstr "AK Kategorie, di zu diesem Constraint gehört"
 
-#: AKModel/models.py:696
+#: AKModel/models.py:1042
 msgid "Comment"
 msgstr "Kommentar"
 
-#: AKModel/models.py:696
+#: AKModel/models.py:1042
 msgid "Comment or further details for this violation"
 msgstr "Kommentar oder weitere Details zu dieser Vereletzung"
 
-#: AKModel/models.py:699
+#: AKModel/models.py:1045
 msgid "Timestamp"
 msgstr "Timestamp"
 
-#: AKModel/models.py:699
+#: AKModel/models.py:1045
 msgid "Time of creation"
 msgstr "Zeitpunkt der ERstellung"
 
-#: AKModel/models.py:700
+#: AKModel/models.py:1046
 msgid "Manually Resolved"
 msgstr "Manuell behoben"
 
-#: AKModel/models.py:701
+#: AKModel/models.py:1047
 msgid "Mark this violation manually as resolved"
 msgstr "Markiere diese Verletzung manuell als behoben"
 
-#: AKModel/models.py:728 AKModel/templates/admin/AKModel/aks_by_user.html:22
+#: AKModel/models.py:1074
 #: AKModel/templates/admin/AKModel/requirements_overview.html:27
 msgid "Details"
 msgstr "Details"
 
-#: AKModel/models.py:862
+#: AKModel/models.py:1208
 msgid "Default Slot"
 msgstr "Standardslot"
 
-#: AKModel/models.py:867
+#: AKModel/models.py:1213
 msgid "Slot End"
 msgstr "Ende des Slots"
 
-#: AKModel/models.py:867
+#: AKModel/models.py:1213
 msgid "Time and date the slot ends"
 msgstr "Zeit und Datum zu der der Slot endet"
 
-#: AKModel/models.py:872
+#: AKModel/models.py:1218
 msgid "Primary categories"
 msgstr "Primäre Kategorien"
 
-#: AKModel/models.py:873
+#: AKModel/models.py:1219
 msgid "Categories that should be assigned to this slot primarily"
 msgstr "Kategorieren, die diesem Slot primär zugewiesen werden sollen"
 
@@ -953,19 +962,6 @@ msgstr "Bestätigen"
 msgid "Cancel"
 msgstr "Abbrechen"
 
-#: AKModel/templates/admin/AKModel/aks_by_user.html:8
-msgid "AKs by Owner"
-msgstr "AKs der Leitung"
-
-#: AKModel/templates/admin/AKModel/aks_by_user.html:26
-#: AKModel/templates/admin/AKModel/requirements_overview.html:31
-msgid "Edit"
-msgstr "Bearbeiten"
-
-#: AKModel/templates/admin/AKModel/aks_by_user.html:33
-msgid "This user does not have any AKs currently"
-msgstr "Diese Leitung hat aktuell keine AKs"
-
 #: AKModel/templates/admin/AKModel/event_wizard/activate.html:9
 #: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:9
 #: AKModel/templates/admin/AKModel/event_wizard/finish.html:9
@@ -1032,12 +1028,16 @@ msgstr ""
 msgid "Requirements Overview"
 msgstr "Übersicht Anforderungen"
 
+#: AKModel/templates/admin/AKModel/requirements_overview.html:31
+msgid "Edit"
+msgstr "Bearbeiten"
+
 #: AKModel/templates/admin/AKModel/requirements_overview.html:38
 msgid "No AKs with this requirement"
 msgstr "Kein AK mit dieser Anforderung"
 
 #: AKModel/templates/admin/AKModel/requirements_overview.html:45
-#: AKModel/views/status.py:179
+#: AKModel/views/status.py:186
 msgid "Add Requirement"
 msgstr "Anforderung hinzufügen"
 
@@ -1090,7 +1090,7 @@ msgstr "Bisher keine Räume"
 msgid "Active Events"
 msgstr "Aktive Events"
 
-#: AKModel/templates/admin/ak_index.html:16 AKModel/views/status.py:109
+#: AKModel/templates/admin/ak_index.html:16 AKModel/views/status.py:108
 msgid "Scheduling"
 msgstr "Scheduling"
 
@@ -1123,43 +1123,47 @@ msgstr "Login"
 msgid "Register"
 msgstr "Registrieren"
 
-#: AKModel/views/ak.py:17
+#: AKModel/views/ak.py:21
 msgid "Requirements for Event"
 msgstr "Anforderungen für das Event"
 
-#: AKModel/views/ak.py:34
+#: AKModel/views/ak.py:38
 msgid "AK CSV Export"
 msgstr "AK-CSV-Export"
 
-#: AKModel/views/ak.py:48
+#: AKModel/views/ak.py:51
+msgid "AK JSON Export"
+msgstr "AK-JSON-Export"
+
+#: AKModel/views/ak.py:175
 msgid "AK Wiki Export"
 msgstr "AK-Wiki-Export"
 
-#: AKModel/views/ak.py:59 AKModel/views/manage.py:53
+#: AKModel/views/ak.py:186 AKModel/views/manage.py:55
 msgid "Wishes"
 msgstr "Wünsche"
 
-#: AKModel/views/ak.py:71
+#: AKModel/views/ak.py:198
 msgid "Delete AK Orga Messages"
 msgstr "AK-Organachrichten löschen"
 
-#: AKModel/views/ak.py:89
+#: AKModel/views/ak.py:216
 msgid "AK Orga Messages successfully deleted"
 msgstr "AK-Organachrichten erfolgreich gelöscht"
 
-#: AKModel/views/ak.py:101
+#: AKModel/views/ak.py:228
 msgid "Interest of the following AKs will be set to not filled (-1):"
 msgstr "Interesse an den folgenden AKs wird auf nicht ausgefüllt (-1) gesetzt:"
 
-#: AKModel/views/ak.py:102
+#: AKModel/views/ak.py:229
 msgid "Reset of interest in AKs successful."
 msgstr "Interesse an AKs erfolgreich zurückgesetzt."
 
-#: AKModel/views/ak.py:116
+#: AKModel/views/ak.py:243
 msgid "Interest counter of the following AKs will be set to 0:"
 msgstr "Interessensbekundungszähler der folgenden AKs wird auf 0 gesetzt:"
 
-#: AKModel/views/ak.py:117
+#: AKModel/views/ak.py:244
 msgid "AKs' interest counters set back to 0."
 msgstr "Interessenszähler der AKs zurückgesetzt"
 
@@ -1173,96 +1177,100 @@ msgstr "'%(obj)s' kopiert"
 msgid "Could not copy '%(obj)s' (%(error)s)"
 msgstr "'%(obj)s' konnte nicht kopiert werden (%(error)s)"
 
-#: AKModel/views/manage.py:35 AKModel/views/status.py:146
+#: AKModel/views/manage.py:37 AKModel/views/status.py:153
 msgid "Export AK Slides"
 msgstr "AK-Folien exportieren"
 
-#: AKModel/views/manage.py:48
+#: AKModel/views/manage.py:50
 msgid "Symbols"
 msgstr "Symbole"
 
-#: AKModel/views/manage.py:49
+#: AKModel/views/manage.py:51
 msgid "Who?"
 msgstr "Wer?"
 
-#: AKModel/views/manage.py:50
+#: AKModel/views/manage.py:52
 msgid "Duration(s)"
 msgstr "Dauer(n)"
 
-#: AKModel/views/manage.py:51
+#: AKModel/views/manage.py:53
 msgid "Reso intention?"
 msgstr "Resolutionsabsicht?"
 
-#: AKModel/views/manage.py:52
+#: AKModel/views/manage.py:54
 msgid "Category (for Wishes)"
 msgstr "Kategorie (für Wünsche)"
 
-#: AKModel/views/manage.py:101
+#: AKModel/views/manage.py:103
 msgid "The following Constraint Violations will be marked as manually resolved"
 msgstr ""
 "Die folgenden Constraintverletzungen werden als manuell behoben markiert."
 
-#: AKModel/views/manage.py:102
+#: AKModel/views/manage.py:104
 msgid "Constraint Violations marked as resolved"
 msgstr "Constraintverletzungen als manuell behoben markiert"
 
-#: AKModel/views/manage.py:114
+#: AKModel/views/manage.py:116
 msgid "The following Constraint Violations will be set to level 'violation'"
 msgstr ""
 "Die folgenden Constraintverletzungen werden auf das Level \"Violation\" "
 "gesetzt."
 
-#: AKModel/views/manage.py:115
+#: AKModel/views/manage.py:117
 msgid "Constraint Violations set to level 'violation'"
 msgstr "Constraintverletzungen auf Level \"Violation\" gesetzt"
 
-#: AKModel/views/manage.py:127
+#: AKModel/views/manage.py:129
 msgid "The following Constraint Violations will be set to level 'warning'"
 msgstr ""
 "Die folgenden Constraintverletzungen werden auf das Level 'warning' gesetzt."
 
-#: AKModel/views/manage.py:128
+#: AKModel/views/manage.py:130
 msgid "Constraint Violations set to level 'warning'"
 msgstr "Constraintverletzungen auf Level \"Warning\" gesetzt"
 
-#: AKModel/views/manage.py:140
+#: AKModel/views/manage.py:142
 msgid "Publish the plan(s) of:"
 msgstr "Den Plan/die Pläne veröffentlichen von:"
 
-#: AKModel/views/manage.py:141
+#: AKModel/views/manage.py:143
 msgid "Plan published"
 msgstr "Plan veröffentlicht"
 
-#: AKModel/views/manage.py:153
+#: AKModel/views/manage.py:155
 msgid "Unpublish the plan(s) of:"
 msgstr "Den Plan/die Pläne verbergen von:"
 
-#: AKModel/views/manage.py:154
+#: AKModel/views/manage.py:156
 msgid "Plan unpublished"
 msgstr "Plan verborgen"
 
-#: AKModel/views/manage.py:166 AKModel/views/status.py:130
+#: AKModel/views/manage.py:168 AKModel/views/status.py:129
 msgid "Edit Default Slots"
 msgstr "Standardslots bearbeiten"
 
-#: AKModel/views/manage.py:204
+#: AKModel/views/manage.py:206
 #, python-brace-format
 msgid "Could not update slot {id} since it does not belong to {event}"
 msgstr ""
 "Konnte  Slot {id} nicht bearbeiten, da er nicht zum Event {event} gehört"
 
-#: AKModel/views/manage.py:235
+#: AKModel/views/manage.py:237
 #, python-brace-format
 msgid "Updated {u} slot(s). created {c} new slot(s) and deleted {d} slot(s)"
 msgstr ""
 "{u} Slot(s) aktualisiert, {c} Slot(s) hinzugefügt und {d} Slot(s) gelöscht"
 
+#: AKModel/views/manage.py:257
+msgid "AK JSON Import"
+msgstr "AK-JSON-Import"
+
 #: AKModel/views/room.py:37
 #, python-format
 msgid "Created Room '%(room)s'"
 msgstr "Raum '%(room)s' angelegt"
 
-#: AKModel/views/room.py:51 AKModel/views/status.py:82
+#: AKModel/views/room.py:51 AKModel/views/status.py:81
 msgid "Import Rooms from CSV"
 msgstr "Räume aus CSV importieren"
 
@@ -1280,50 +1288,64 @@ msgstr "{count} Raum/Räume importiert"
 msgid "No rooms imported"
 msgstr "Keine Räume importiert"
 
-#: AKModel/views/status.py:17
+#: AKModel/views/status.py:16
 msgid "Overview"
 msgstr "Überblick"
 
-#: AKModel/views/status.py:33
+#: AKModel/views/status.py:32
 msgid "Categories"
 msgstr "Kategorien"
 
-#: AKModel/views/status.py:37
+#: AKModel/views/status.py:36
 msgid "Add category"
 msgstr "Kategorie hinzufügen"
 
-#: AKModel/views/status.py:64
+#: AKModel/views/status.py:63
 msgid "Add Room"
 msgstr "Raum hinzufügen"
 
-#: AKModel/views/status.py:116
+#: AKModel/views/status.py:115
 msgid "AKs requiring special attention"
 msgstr "AKs, die besondere Aufmerksamkeit benötigen"
 
-#: AKModel/views/status.py:122
+#: AKModel/views/status.py:121
 msgid "Enter Interest"
 msgstr "Interesse erfassen"
 
-#: AKModel/views/status.py:134
+#: AKModel/views/status.py:133
 msgid "Manage ak tracks"
 msgstr "AK-Tracks verwalten"
 
-#: AKModel/views/status.py:138
+#: AKModel/views/status.py:137
+msgid "Import AK schedule from JSON"
+msgstr "AK-Plan aus JSON importieren"
+
+#: AKModel/views/status.py:141
 msgid "Export AKs as CSV"
 msgstr "AKs als CSV exportieren"
 
-#: AKModel/views/status.py:142
+#: AKModel/views/status.py:145
+msgid "Export AKs as JSON"
+msgstr "AKs als JSON exportieren"
+
+#: AKModel/views/status.py:149
 msgid "Export AKs for Wiki"
 msgstr "AKs im Wiki-Format exportieren"
 
-#: AKModel/views/status.py:175
+#: AKModel/views/status.py:182
 msgid "Show AKs for requirements"
 msgstr "Zu Anforderungen gehörige AKs anzeigen"
 
-#: AKModel/views/status.py:189
+#: AKModel/views/status.py:196
 msgid "Event Status"
 msgstr "Eventstatus"
 
+#~ msgid "AKs by Owner"
+#~ msgstr "AKs der Leitung"
+
+#~ msgid "This user does not have any AKs currently"
+#~ msgstr "Diese Leitung hat aktuell keine AKs"
+
 #~ msgid "Opening time for expression of interest."
 #~ msgstr "Öffnungszeitpunkt für die Angabe von Interesse an AKs."
 
diff --git a/AKModel/models.py b/AKModel/models.py
index 22586a57380b4ba368c440726ecccc3f6d73b1d0..2d58a45651b419899932171a0fdae2a394e85d6b 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -1,18 +1,49 @@
 import itertools
-from datetime import timedelta
+import json
+from dataclasses import dataclass
+from datetime import datetime, timedelta
+from typing import Iterable
 
 from django.db import models
 from django.apps import apps
 from django.db.models import Count
 from django.urls import reverse_lazy
 from django.utils import timezone
-from django.utils.datetime_safe import datetime
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 from simple_history.models import HistoricalRecords
 from timezone_field import TimeZoneField
 
 
+@dataclass
+class OptimizerTimeslot:
+    """Class describing a timeslot. Used to interface with an optimizer."""
+
+    avail: "Availability"
+    idx: int
+    constraints: set[str]
+
+    def merge(self, other: "OptimizerTimeslot") -> "OptimizerTimeslot":
+        """Merge with other OptimizerTimeslot.
+
+        Creates a new OptimizerTimeslot object.
+        Its availability is constructed by merging the availabilities of self and other,
+        its constraints by taking the union of both constraint sets.
+        As an index, the index of self is used.
+        """
+        avail = self.avail.merge_with(other.avail)
+        constraints = self.constraints.union(other.constraints)
+        # we simply use the index of result[-1]
+        return OptimizerTimeslot(
+            avail=avail, idx=self.idx, constraints=constraints
+        )
+
+    def __repr__(self) -> str:
+        return f"({self.avail.simplified}, {self.idx}, {self.constraints})"
+
+TimeslotBlock = list[OptimizerTimeslot]
+
+
 class Event(models.Model):
     """
     An event supplies the frame for all Aks.
@@ -162,6 +193,213 @@ class Event(models.Model):
                 .filter(availabilities__count=0, owners__count__gt=0)
                 )
 
+    def _generate_slots_from_block(
+        self,
+        start: datetime,
+        end: datetime,
+        slot_duration: timedelta,
+        slot_index: int = 0,
+        constraints: set[str] | None = None,
+    ) -> Iterable[TimeslotBlock]:
+        """Discretize a time range into timeslots.
+
+        Uses a uniform discretization into blocks of length `slot_duration`,
+        starting at `start`. No incomplete timeslots are generated, i.e.
+        if (`end` - `start`) is not a whole number multiple of `slot_duration`
+        then the last incomplete timeslot is dropped.
+
+        :param start: Start of the time range.
+        :param end: Start of the time range.
+        :param slot_duration: Duration of a single timeslot in the discretization.
+        :param slot_index: index of the first timeslot. Defaults to 0.
+
+        :yield: Block of optimizer timeslots as the discretization result.
+        :ytype: list of TimeslotBlock
+        """
+        # local import to prevent cyclic import
+        # pylint: disable=import-outside-toplevel
+        from AKModel.availability.models import Availability
+
+        current_slot_start = start
+        previous_slot_start: datetime | None = None
+
+        if constraints is None:
+            constraints = set()
+
+        current_block = []
+
+        room_availabilities = list({
+            availability
+            for room in Room.objects.filter(event=self)
+            for availability in room.availabilities.all()
+        })
+
+        while current_slot_start + slot_duration <= end:
+            slot = Availability(
+                event=self,
+                start=current_slot_start,
+                end=current_slot_start + slot_duration,
+            )
+
+            if any((availability.contains(slot) for availability in room_availabilities)):
+                # no gap in a block
+                if (
+                    previous_slot_start is not None
+                    and previous_slot_start + slot_duration < current_slot_start
+                ):
+                    yield current_block
+                    current_block = []
+
+                current_block.append(
+                    OptimizerTimeslot(avail=slot, idx=slot_index, constraints=constraints)
+                )
+                previous_slot_start = current_slot_start
+
+            slot_index += 1
+            current_slot_start += slot_duration
+
+        if current_block:
+            yield current_block
+
+        return slot_index
+
+    def uniform_time_slots(self, *, slots_in_an_hour: float = 1.0) -> Iterable[TimeslotBlock]:
+        """Uniformly discretize the entire event into a single block of timeslots.
+
+        :param slots_in_an_hour: The percentage of an hour covered by a single slot.
+            Determines the discretization granularity.
+        :yield: Block of optimizer timeslots as the discretization result.
+        :ytype: a single list of TimeslotBlock
+        """
+        all_category_constraints = AKCategory.create_category_constraints(
+            AKCategory.objects.filter(event=self).all()
+        )
+
+        yield from self._generate_slots_from_block(
+            start=self.start,
+            end=self.end,
+            slot_duration=timedelta(hours=1.0 / slots_in_an_hour),
+            constraints=all_category_constraints,
+        )
+
+    def default_time_slots(self, *, slots_in_an_hour: float = 1.0) -> Iterable[TimeslotBlock]:
+        """Discretize all default slots into blocks of timeslots.
+
+        In the discretization each default slot corresponds to one block.
+
+        :param slots_in_an_hour: The percentage of an hour covered by a single slot.
+            Determines the discretization granularity.
+        :yield: Block of optimizer timeslots as the discretization result.
+        :ytype: list of TimeslotBlock
+        """
+        slot_duration = timedelta(hours=1.0 / slots_in_an_hour)
+        slot_index = 0
+
+        for block_slot in DefaultSlot.objects.filter(event=self).order_by("start", "end"):
+            category_constraints = AKCategory.create_category_constraints(
+                block_slot.primary_categories.all()
+            )
+
+            slot_index = yield from self._generate_slots_from_block(
+                start=block_slot.start,
+                end=block_slot.end,
+                slot_duration=slot_duration,
+                slot_index=slot_index,
+                constraints=category_constraints,
+            )
+
+    def merge_blocks(
+        self, blocks: Iterable[TimeslotBlock]
+    ) -> Iterable[TimeslotBlock]:
+        """Merge iterable of blocks together.
+
+        The timeslots of all blocks are grouped into maximal blocks.
+        Timeslots with the same start and end are identified with each other
+        and merged (cf `OptimizerTimeslot.merge`).
+        Throws a ValueError if any timeslots are overlapping but do not
+        share the same start and end, i.e. partial overlap is not allowed.
+
+        :param blocks: iterable of blocks to merge.
+        :return: iterable of merged blocks.
+        :rtype: iterable over lists of OptimizerTimeslot objects
+        """
+        if not blocks:
+            return []
+
+        # flatten timeslot iterables to single chain
+        timeslot_chain = itertools.chain.from_iterable(blocks)
+
+        # sort timeslots according to start
+        timeslots = sorted(
+            timeslot_chain,
+            key=lambda slot: slot.avail.start
+        )
+
+        if not timeslots:
+            return []
+
+        all_blocks = []
+        current_block = [timeslots[0]]
+        timeslots = timeslots[1:]
+
+        for slot in timeslots:
+            if current_block and slot.avail.overlaps(current_block[-1].avail, strict=True):
+                if (
+                    slot.avail.start == current_block[-1].avail.start
+                    and slot.avail.end == current_block[-1].avail.end
+                ):
+                    # the same timeslot -> merge
+                    current_block[-1] = current_block[-1].merge(slot)
+                else:
+                    # partial overlap of interiors -> not supported
+                    # TODO: Show comprehensive message in production
+                    raise ValueError(
+                        "Partially overlapping timeslots are not supported!"
+                        f" ({current_block[-1].avail.simplified}, {slot.avail.simplified})"
+                    )
+            elif not current_block or slot.avail.overlaps(current_block[-1].avail, strict=False):
+                # only endpoints in intersection -> same block
+                current_block.append(slot)
+            else:
+                # no overlap at all -> new block
+                all_blocks.append(current_block)
+                current_block = [slot]
+
+        if current_block:
+            all_blocks.append(current_block)
+
+        return all_blocks
+
+    def schedule_from_json(self, schedule: str) -> None:
+        """Load AK schedule from a json string.
+
+        :param schedule: A string that can be decoded to json, describing
+            the AK schedule. The json data is assumed to be constructed
+            following the output specification of the KoMa conference optimizer, cf.
+            https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format
+        """
+        schedule = json.loads(schedule)
+
+        slots_in_an_hour = schedule["input"]["timeslots"]["info"]["duration"]
+
+        timeslot_dict = {
+            timeslot.idx: timeslot
+            for block in self.merge_blocks(self.default_time_slots(slots_in_an_hour=slots_in_an_hour))
+            for timeslot in block
+        }
+
+        for scheduled_slot in schedule["scheduled_aks"]:
+            slot = AKSlot.objects.get(id=int(scheduled_slot["ak_id"]))
+            slot.room = Room.objects.get(id=int(scheduled_slot["room_id"]))
+
+            scheduled_slot["timeslot_ids"] = list(map(int, scheduled_slot["timeslot_ids"]))
+
+            start_timeslot = timeslot_dict[min(scheduled_slot["timeslot_ids"])].avail
+            end_timeslot = timeslot_dict[max(scheduled_slot["timeslot_ids"])].avail
+
+            slot.start = start_timeslot.start
+            slot.duration = (end_timeslot.end - start_timeslot.start).total_seconds() / 3600.0
+            slot.save()
 
 class AKOwner(models.Model):
     """ An AKOwner describes the person organizing/holding an AK.
@@ -260,6 +498,20 @@ class AKCategory(models.Model):
     def __str__(self):
         return self.name
 
+    @staticmethod
+    def create_category_constraints(categories: Iterable["AKCategory"]) -> set[str]:
+        """Create a set of constraint strings from an AKCategory iterable.
+
+        :param categories: The iterable of categories to derive the constraint strings from.
+        :return: A set of category constraint strings, i.e. strings of the form
+            'availability-cat-<cat.name>'.
+        :rtype: set of strings.
+        """
+        return {
+            f"availability-cat-{cat.name}"
+            for cat in categories
+        }
+
 
 class AKTrack(models.Model):
     """ An AKTrack describes a set of semantically related AKs.
@@ -513,6 +765,43 @@ class Room(models.Model):
     def __str__(self):
         return self.title
 
+    def as_json(self) -> str:
+        """Return a json string representation of this room object.
+
+        :return: The json string representation is constructed
+            following the input specification of the KoMa conference optimizer, cf.
+            https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format
+        :rtype: str
+        """
+        # local import to prevent cyclic import
+        # pylint: disable=import-outside-toplevel
+        from AKModel.availability.models import Availability
+
+        # check if room is available for the whole event
+        # -> no time constraint needs to be introduced
+        if Availability.is_event_covered(self.event, self.availabilities.all()):
+            time_constraints = []
+        else:
+            time_constraints = [f"availability-room-{self.pk}"]
+
+        data = {
+            "id": str(self.pk),
+            "info": {
+                "name": self.name,
+            },
+            "capacity": self.capacity,
+            "fulfilled_room_constraints": [constraint.name
+                                           for constraint in self.properties.all()],
+            "time_constraints": time_constraints
+        }
+
+        data["fulfilled_room_constraints"].append(f"availability-room-{self.pk}")
+
+        if not any(constr.startswith("proxy") for constr in data["fulfilled_room_constraints"]):
+            data["fulfilled_room_constraints"].append("no-proxy")
+
+        return json.dumps(data)
+
 
 class AKSlot(models.Model):
     """ An AK Mapping matches an AK to a room during a certain time.
@@ -608,6 +897,63 @@ class AKSlot(models.Model):
             self.duration = min(self.duration, event_duration_hours)
         super().save(force_insert, force_update, using, update_fields)
 
+    def as_json(self) -> str:
+        """Return a json string representation of the AK object of this slot.
+
+        :return: The json string representation is constructed
+            following the input specification of the KoMa conference optimizer, cf.
+            https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format
+        :rtype: str
+        """
+        # local import to prevent cyclic import
+        # pylint: disable=import-outside-toplevel
+        from AKModel.availability.models import Availability
+
+        # check if ak resp. owner is available for the whole event
+        # -> no time constraint needs to be introduced
+
+        if not self.fixed and Availability.is_event_covered(self.event, self.ak.availabilities.all()):
+            ak_time_constraints = []
+        else:
+            ak_time_constraints = [f"availability-ak-{self.ak.pk}"]
+
+        def _owner_time_constraints(owner: AKOwner):
+            if Availability.is_event_covered(self.event, owner.availabilities.all()):
+                return []
+            return [f"availability-person-{owner.pk}"]
+
+        # self.slots_in_an_hour is set in AKJSONExportView
+        data = {
+            "id": str(self.pk),
+            "duration": int(self.duration * self.slots_in_an_hour),
+            "properties": {},
+            "room_constraints": [constraint.name
+                                 for constraint in self.ak.requirements.all()],
+            "time_constraints": ["resolution"] if self.ak.reso else [],
+            "info": {
+                "name": self.ak.name,
+                "head": ", ".join([str(owner)
+                                   for owner in self.ak.owners.all()]),
+                "description": self.ak.description,
+                "reso": self.ak.reso,
+                },
+            }
+
+        data["time_constraints"].extend(ak_time_constraints)
+        for owner in self.ak.owners.all():
+            data["time_constraints"].extend(_owner_time_constraints(owner))
+
+        if self.ak.category:
+            category_constraints = AKCategory.create_category_constraints([self.ak.category])
+            data["time_constraints"].extend(category_constraints)
+
+        if self.room is not None and self.fixed:
+            data["room_constraints"].append(f"availability-room-{self.room.pk}")
+
+        if not any(constr.startswith("proxy") for constr in data["room_constraints"]):
+            data["room_constraints"].append("no-proxy")
+
+        return json.dumps(data)
 
 class AKOrgaMessage(models.Model):
     """
diff --git a/AKModel/templates/admin/AKModel/ak_json_export.html b/AKModel/templates/admin/AKModel/ak_json_export.html
new file mode 100644
index 0000000000000000000000000000000000000000..38e5526edc8364faf75491e68cb893b10d64751a
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/ak_json_export.html
@@ -0,0 +1,20 @@
+{% extends "admin/base_site.html" %}
+
+{% load tz %}
+
+{% block content %}
+<pre>
+  {"aks": [
+      {% for slot in slots %}{{ slot.as_json }}{% if not forloop.last %},
+      {% endif %}{% endfor %}
+    ],
+  "rooms": [
+      {% for room in rooms %}{{ room.as_json }}{% if not forloop.last %},
+      {% endif %}{% endfor %}
+    ],
+  "participants": {{ participants }},
+  "timeslots": {{ timeslots }},
+  "info": {{ info_dict }}
+  }
+</pre>
+{% endblock %}
diff --git a/AKModel/urls.py b/AKModel/urls.py
index 3abf646058afdf71c7938032236942e7bb0ed995..9871b4119949d31350ecc64db568d515b61eb3cb 100644
--- a/AKModel/urls.py
+++ b/AKModel/urls.py
@@ -5,8 +5,9 @@ from rest_framework.routers import DefaultRouter
 
 import AKModel.views.api
 from AKModel.views.manage import ExportSlidesView, PlanPublishView, PlanUnpublishView, DefaultSlotEditorView, \
-    AKsByUserView
-from AKModel.views.ak import AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView
+    AKsByUserView, AKJSONImportView
+from AKModel.views.ak import AKRequirementOverview, AKCSVExportView, AKJSONExportView, AKWikiExportView, \
+     AKMessageDeleteView
 from AKModel.views.event_wizard import NewEventWizardStartView, NewEventWizardPrepareImportView, \
     NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, NewEventWizardSettingsView
 from AKModel.views.room import RoomBatchCreationView
@@ -96,6 +97,10 @@ def get_admin_urls_event(admin_site):
              name="aks_by_owner"),
         path('<slug:event_slug>/ak-csv-export/', admin_site.admin_view(AKCSVExportView.as_view()),
              name="ak_csv_export"),
+        path('<slug:event_slug>/ak-json-export/', admin_site.admin_view(AKJSONExportView.as_view()),
+             name="ak_json_export"),
+        path('<slug:event_slug>/ak-json-import/', admin_site.admin_view(AKJSONImportView.as_view()),
+             name="ak_json_import"),
         path('<slug:slug>/ak-wiki-export/', admin_site.admin_view(AKWikiExportView.as_view()),
              name="ak_wiki_export"),
         path('<slug:event_slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()),
diff --git a/AKModel/views/ak.py b/AKModel/views/ak.py
index 3afec5ac2de03a62ad3d103d17062927c3b97026..76bfdb284e42cc71dc9be791a171ba762e2a9b5f 100644
--- a/AKModel/views/ak.py
+++ b/AKModel/views/ak.py
@@ -1,11 +1,15 @@
+import json
+from typing import List
+
 from django.contrib import messages
 from django.urls import reverse_lazy
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import ListView, DetailView
 
+from AKModel.availability.models import Availability
 from AKModel.metaviews.admin import AdminViewMixin, FilterByEventSlugMixin, EventSlugMixin, IntermediateAdminView, \
     IntermediateAdminActionView
-from AKModel.models import AKRequirement, AKSlot, Event, AKOrgaMessage, AK
+from AKModel.models import AKRequirement, AKSlot, Event, AKOrgaMessage, AK, Room, AKOwner
 
 
 class AKRequirementOverview(AdminViewMixin, FilterByEventSlugMixin, ListView):
@@ -37,6 +41,129 @@ class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
         return super().get_queryset().order_by("ak__track")
 
 
+class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
+    """
+    View: Export all AK slots of this event in JSON format ordered by tracks
+    """
+    template_name = "admin/AKModel/ak_json_export.html"
+    model = AKSlot
+    context_object_name = "slots"
+    title = _("AK JSON Export")
+
+
+    def _test_slot_contained(self, slot: Availability, availabilities: List[Availability]) -> bool:
+        return any(availability.contains(slot) for availability in availabilities)
+
+    def _test_event_covered(self, availabilities: List[Availability]) -> bool:
+        return not Availability.is_event_covered(self.event, availabilities)
+
+    def _test_fixed_ak(self, ak_id, slot: Availability, ak_fixed: dict) -> bool:
+        if not ak_id in ak_fixed:
+            return False
+
+        fixed_slot = Availability(self.event, start=ak_fixed[ak_id].start, end=ak_fixed[ak_id].end)
+        return fixed_slot.overlaps(slot, strict=True)
+
+    def _test_add_constraint(self, slot: Availability, availabilities: List[Availability]) -> bool:
+        return (
+            self._test_event_covered(availabilities)
+            and self._test_slot_contained(slot, availabilities)
+        )
+
+    def get_queryset(self):
+        return super().get_queryset().order_by("ak__track")
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context["participants"] = json.dumps([])
+
+        rooms = Room.objects.filter(event=self.event)
+        context["rooms"] = rooms
+
+        # TODO: Configure magic number in event
+        SLOTS_IN_AN_HOUR = 1
+
+        timeslots = {
+            "info": {"duration": (1.0 / SLOTS_IN_AN_HOUR), },
+            "blocks": [],
+            }
+
+        for slot in context["slots"]:
+            slot.slots_in_an_hour = SLOTS_IN_AN_HOUR
+
+        ak_availabilities = {
+            slot.ak.pk: Availability.union(slot.ak.availabilities.all())
+            for slot in context["slots"]
+        }
+        room_availabilities = {
+            room.pk: Availability.union(room.availabilities.all())
+            for room in rooms
+        }
+        person_availabilities = {
+            person.pk: Availability.union(person.availabilities.all())
+            for person in AKOwner.objects.filter(event=self.event)
+        }
+
+        ak_fixed = {
+            ak_id: values.get()
+            for ak_id in ak_availabilities.keys()
+            if (values := AKSlot.objects.select_related().filter(ak__pk=ak_id, fixed=True)).exists()
+        }
+
+        for block in self.event.merge_blocks(self.event.default_time_slots(slots_in_an_hour=SLOTS_IN_AN_HOUR)):
+            current_block = []
+
+            for timeslot in block:
+                time_constraints = []
+                if self.event.reso_deadline is None or timeslot.avail.end < self.event.reso_deadline:
+                    time_constraints.append("resolution")
+
+                time_constraints.extend([
+                    f"availability-ak-{ak_id}"
+                    for ak_id, availabilities in ak_availabilities.items()
+                    if (
+                        self._test_add_constraint(timeslot.avail, availabilities)
+                        or self._test_fixed_ak(ak_id, timeslot.avail, ak_fixed)
+                    )
+                ])
+                time_constraints.extend([
+                    f"availability-person-{person_id}"
+                    for person_id, availabilities in person_availabilities.items()
+                    if self._test_add_constraint(timeslot.avail, availabilities)
+                ])
+                time_constraints.extend([
+                    f"availability-room-{room_id}"
+                    for room_id, availabilities in room_availabilities.items()
+                    if self._test_add_constraint(timeslot.avail, availabilities)
+                ])
+                time_constraints.extend(timeslot.constraints)
+
+                current_block.append({
+                    "id": str(timeslot.idx),
+                    "info": {
+                        "start": timeslot.avail.simplified,
+                    },
+                    "fulfilled_time_constraints": time_constraints,
+                    })
+
+            timeslots["blocks"].append(current_block)
+
+        context["timeslots"] = json.dumps(timeslots)
+
+        info_dict = {
+            "title": self.event.name,
+            "slug": self.event.slug
+        }
+        for attr in ["contact_email", "place"]:
+            if hasattr(self.event, attr) and getattr(self.event, attr):
+                info_dict[attr] = getattr(self.event, attr)
+
+        context["info_dict"] = json.dumps(info_dict)
+
+        return context
+
+
+
 class AKWikiExportView(AdminViewMixin, DetailView):
     """
     View: Export AKs of this event in wiki syntax
diff --git a/AKModel/views/manage.py b/AKModel/views/manage.py
index 64443cb8f7de27832f518df75378f1fc2ea59571..ec5076fbd850c71a6f7d1a1d202a46461e43ebbd 100644
--- a/AKModel/views/manage.py
+++ b/AKModel/views/manage.py
@@ -4,15 +4,17 @@ import os
 import tempfile
 from itertools import zip_longest
 
+
 from django.contrib import messages
 from django.db.models.functions import Now
+from django.shortcuts import redirect
 from django.utils.dateparse import parse_datetime
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import TemplateView, DetailView
 from django_tex.core import render_template_with_context, run_tex_in_directory
 from django_tex.response import PDFResponse
 
-from AKModel.forms import SlideExportForm, DefaultSlotEditorForm
+from AKModel.forms import SlideExportForm, DefaultSlotEditorForm, JSONImportForm
 from AKModel.metaviews.admin import EventSlugMixin, IntermediateAdminView, IntermediateAdminActionView, AdminViewMixin
 from AKModel.models import ConstraintViolation, Event, DefaultSlot, AKOwner
 
@@ -58,7 +60,7 @@ class ExportSlidesView(EventSlugMixin, IntermediateAdminView):
             Create a list of tuples cosisting of an AK and a list of upcoming AKs (list length depending on setting)
             """
             next_aks_list = zip_longest(*[ak_list[i + 1:] for i in range(NEXT_AK_LIST_LENGTH)], fillvalue=None)
-            return [(ak, next_aks) for ak, next_aks in zip_longest(ak_list, next_aks_list, fillvalue=[])]
+            return list(zip_longest(ak_list, next_aks_list, fillvalue=[]))
 
         # Get all relevant AKs (wishes separately, and either all AKs or only those who should directly or indirectly
         # be presented when restriction setting was chosen)
@@ -245,3 +247,16 @@ class AKsByUserView(AdminViewMixin, EventSlugMixin, DetailView):
     model = AKOwner
     context_object_name = 'owner'
     template_name = "admin/AKModel/aks_by_user.html"
+
+
+class AKJSONImportView(EventSlugMixin, IntermediateAdminView):
+    """
+    View: Import an AK schedule from a json file that can be pasted into this view.
+    """
+    form_class = JSONImportForm
+    title = _("AK JSON Import")
+
+    def form_valid(self, form):
+        self.event.schedule_from_json(form.data["json_data"])
+
+        return redirect("admin:event_status", self.event.slug)
diff --git a/AKModel/views/status.py b/AKModel/views/status.py
index e14ce2fbbcc7c662f71d1711a6fa14b372fb3595..0c12b30348c63d6178f0b5c38d2fcec6cfebf664 100644
--- a/AKModel/views/status.py
+++ b/AKModel/views/status.py
@@ -133,10 +133,18 @@ class EventAKsWidget(TemplateStatusWidget):
                     "text": _("Manage ak tracks"),
                     "url": reverse_lazy("admin:tracks_manage", kwargs={"event_slug": context["event"].slug}),
                 },
+                {
+                    "text": _("Import AK schedule from JSON"),
+                    "url": reverse_lazy("admin:ak_json_import", kwargs={"event_slug": context["event"].slug}),
+                },
                 {
                     "text": _("Export AKs as CSV"),
                     "url": reverse_lazy("admin:ak_csv_export", kwargs={"event_slug": context["event"].slug}),
                 },
+                {
+                    "text": _("Export AKs as JSON"),
+                    "url": reverse_lazy("admin:ak_json_export", kwargs={"event_slug": context["event"].slug}),
+                },
                 {
                     "text": _("Export AKs for Wiki"),
                     "url": reverse_lazy("admin:ak_wiki_export", kwargs={"slug": context["event"].slug}),
diff --git a/AKPlan/locale/de_DE/LC_MESSAGES/django.po b/AKPlan/locale/de_DE/LC_MESSAGES/django.po
index 90ee78c19d2da3dc385e336b58a4b37d6b76c000..1bbfe72524190a8feb747dfe0e0c2e6ae88d09a6 100644
--- a/AKPlan/locale/de_DE/LC_MESSAGES/django.po
+++ b/AKPlan/locale/de_DE/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-05-15 20:03+0200\n"
+"POT-Creation-Date: 2024-05-27 01:57+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -38,7 +38,7 @@ msgstr "Veranstaltung"
 #: AKPlan/templates/AKPlan/plan_index.html:59
 #: AKPlan/templates/AKPlan/plan_room.html:13
 #: AKPlan/templates/AKPlan/plan_room.html:59
-#: AKPlan/templates/AKPlan/plan_wall.html:65
+#: AKPlan/templates/AKPlan/plan_wall.html:67
 msgid "Room"
 msgstr "Raum"
 
@@ -63,12 +63,12 @@ msgid "AK Wall"
 msgstr "AK-Wall"
 
 #: AKPlan/templates/AKPlan/plan_index.html:130
-#: AKPlan/templates/AKPlan/plan_wall.html:130
+#: AKPlan/templates/AKPlan/plan_wall.html:132
 msgid "Current AKs"
 msgstr "Aktuelle AKs"
 
 #: AKPlan/templates/AKPlan/plan_index.html:137
-#: AKPlan/templates/AKPlan/plan_wall.html:135
+#: AKPlan/templates/AKPlan/plan_wall.html:137
 msgid "Next AKs"
 msgstr "Nächste AKs"
 
@@ -99,7 +99,7 @@ msgstr "Eigenschaften"
 msgid "Track"
 msgstr "Track"
 
-#: AKPlan/templates/AKPlan/plan_wall.html:145
+#: AKPlan/templates/AKPlan/plan_wall.html:147
 msgid "Reload page automatically?"
 msgstr "Seite automatisch neu laden?"
 
diff --git a/AKPlanning/locale/de_DE/LC_MESSAGES/django.po b/AKPlanning/locale/de_DE/LC_MESSAGES/django.po
index 3f47223fa6145f6d4c55f87c6f4c1c660f2bd311..68529ec4e06f679135124da17e2aa86e6d2757a7 100644
--- a/AKPlanning/locale/de_DE/LC_MESSAGES/django.po
+++ b/AKPlanning/locale/de_DE/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-08-16 16:30+0200\n"
+"POT-Creation-Date: 2024-05-27 01:57+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,10 +17,10 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: AKPlanning/settings.py:148
+#: AKPlanning/settings.py:147
 msgid "German"
 msgstr "Deutsch"
 
-#: AKPlanning/settings.py:149
+#: AKPlanning/settings.py:148
 msgid "English"
 msgstr "Englisch"
diff --git a/AKScheduling/models.py b/AKScheduling/models.py
index 1495f311935583a945aef0d737ca44e21a0a2663..aa6be3d0398ea82461db295ce9e853dfd27386d9 100644
--- a/AKScheduling/models.py
+++ b/AKScheduling/models.py
@@ -288,6 +288,8 @@ def ak_requirements_changed_handler(sender, instance: AK, action: str, **kwargs)
     for slot in slots_of_this_ak:
 
         room = slot.room
+        if room is None:
+            continue
         room_requirements = room.properties.all()
 
         for requirement in instance.requirements.all():
diff --git a/AKSubmission/locale/de_DE/LC_MESSAGES/django.po b/AKSubmission/locale/de_DE/LC_MESSAGES/django.po
index b25db7600b98a97b8498ea72067f9504f5b2b1c8..e8add44b31100539ff4a2b8311b40ffba14c4b8a 100644
--- a/AKSubmission/locale/de_DE/LC_MESSAGES/django.po
+++ b/AKSubmission/locale/de_DE/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-08-16 16:30+0200\n"
+"POT-Creation-Date: 2024-05-27 01:57+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,16 +17,16 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: AKSubmission/forms.py:93
+#: AKSubmission/forms.py:95
 #, python-format
 msgid "\"%(duration)s\" is not a valid duration"
 msgstr "\"%(duration)s\" ist keine gültige Dauer"
 
-#: AKSubmission/forms.py:159
+#: AKSubmission/forms.py:155
 msgid "Duration(s)"
 msgstr "Dauer(n)"
 
-#: AKSubmission/forms.py:161
+#: AKSubmission/forms.py:157
 msgid ""
 "Enter at least one planned duration (in hours). If your AK should have "
 "multiple slots, use multiple lines"
diff --git a/INSTALL.md b/INSTALL.md
index c887af92dba1d2661b5b53ef9a17041d2c1a730d..5344a998cffe2603aaaffb0583e91fb43e533c38 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -10,7 +10,7 @@ setup.
 
 ### System Requirements
 
-* Python 3.8+ incl. development tools
+* Python 3.10+ incl. development tools
 * Virtualenv
 * pdflatex & beamer
   class (`texlive-latex-base texlive-latex-recommended texlive-latex-extra texlive-fonts-extra texlive-luatex`)
@@ -37,7 +37,7 @@ Python requirements are listed in ``requirements.txt``. They can be installed wi
 
 ### Manual Setup
 
-1. setup a virtual environment using the proper python version ``virtualenv venv -p python3.7``
+1. setup a virtual environment using the proper python version ``virtualenv venv -p python3.10``
 1. activate virtualenv ``source venv/bin/activate``
 1. install python requirements ``pip install -r requirements.txt``
 1. setup necessary database tables etc. ``python manage.py migrate``
@@ -68,7 +68,7 @@ is not stored in any repository or similar, and disable DEBUG mode (``settings.p
 1. create a folder, e.g. ``mkdir /srv/AKPlanning/``
 1. change to the new directory ``cd /srv/AKPlanning/``
 1. clone this repository ``git clone URL .``
-1. setup a virtual environment using the proper python version ``virtualenv venv -p python3.7``
+1. setup a virtual environment using the proper python version ``virtualenv venv -p python3.10``
 1. activate virtualenv ``source venv/bin/activate``
 1. update tools ``pip install --upgrade setuptools pip wheel``
 1. install python requirements ``pip install -r requirements.txt``
diff --git a/Utils/setup.sh b/Utils/setup.sh
index 1c951824905e99e4650f40a562b633b92d7b8b02..6a93207d197e75da2875b31ea8e0e631e114e837 100755
--- a/Utils/setup.sh
+++ b/Utils/setup.sh
@@ -10,7 +10,7 @@ rm -rf venv/
 
 # Setup Python Environment
 # Requires: Virtualenv, appropriate Python installation
-virtualenv venv -p python3.9
+virtualenv venv -p python3.10
 source venv/bin/activate
 pip install --upgrade setuptools pip wheel
 pip install -r requirements.txt