From 57c40a50b029a5b817e3ae46694dbaf48667ef9b Mon Sep 17 00:00:00 2001
From: Felix Blanke <info@fblanke.de>
Date: Sun, 26 Jan 2025 23:10:53 +0100
Subject: [PATCH 1/5] Refactor

---
 AKModel/models.py   | 10 ++++----
 AKModel/views/ak.py | 57 ++++++++++++++++++++++++---------------------
 2 files changed, 36 insertions(+), 31 deletions(-)

diff --git a/AKModel/models.py b/AKModel/models.py
index 267f0bb0..b645c0c1 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -943,10 +943,12 @@ class AKSlot(models.Model):
         # 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:
+        if self.fixed and self.start is not None:
+            ak_time_constraints = [f"fixed-akslot-{self.id}"]
+        elif Availability.is_event_covered(self.event, self.ak.availabilities.all()):
             ak_time_constraints = [f"availability-ak-{self.ak.pk}"]
+        else:
+            ak_time_constraints = []
 
         def _owner_time_constraints(owner: AKOwner):
             if Availability.is_event_covered(self.event, owner.availabilities.all()):
@@ -987,7 +989,7 @@ class AKSlot(models.Model):
             category_constraints = AKCategory.create_category_constraints([self.ak.category])
             data["time_constraints"].extend(category_constraints)
 
-        if self.room is not None and self.fixed:
+        if self.fixed and self.room is not None:
             data["room_constraints"].append(f"availability-room-{self.room.pk}")
 
         if not any(constr.startswith("proxy") for constr in data["room_constraints"]):
diff --git a/AKModel/views/ak.py b/AKModel/views/ak.py
index 1d60717b..d266e9bc 100644
--- a/AKModel/views/ak.py
+++ b/AKModel/views/ak.py
@@ -59,13 +59,13 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
         """Test if event is not covered by availabilities."""
         return not Availability.is_event_covered(self.event, availabilities)
 
-    def _test_ak_fixed_in_slot(self, ak_id, slot: Availability, ak_fixed: dict) -> bool:
-        """Test if AK defined by `ak_id` is fixed to happen during slot."""
-        if not ak_id in ak_fixed:
+    def _test_akslot_fixed_in_timeslot(self, ak_slot: AKSlot, timeslot: Availability) -> bool:
+        """Test if an AKSlot is fixed to overlap a timeslot slot."""
+        if not ak_slot.fixed or ak_slot.start is None:
             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)
+        fixed_avail = Availability(event=self.event, start=ak_slot.start, end=ak_slot.end)
+        return fixed_avail.overlaps(timeslot, strict=True)
 
     def _test_add_constraint(self, slot: Availability, availabilities: List[Availability]) -> bool:
         """Test if object is not available for whole event and may happen during slot."""
@@ -74,6 +74,19 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
             and self._test_slot_contained(slot, availabilities)
         )
 
+    def _generate_time_constraints(
+        self,
+        avail_label: str,
+        avail_dict: dict,
+        timeslot_avail: Availability,
+        prefix: str = "availability",
+    ) -> list[str]:
+        return [
+            f"{prefix}-{avail_label}-{pk}"
+            for pk, availabilities in avail_dict.items()
+            if self._test_add_constraint(timeslot_avail, availabilities)
+        ]
+
     def get_queryset(self):
         return super().get_queryset().order_by("ak__track")
 
@@ -108,12 +121,6 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
             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()
-        }
-
         blocks = self.event.discretize_timeslots(slots_in_an_hour=SLOTS_IN_AN_HOUR)
 
         for block in blocks:
@@ -127,26 +134,22 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
                     time_constraints.append("resolution")
 
                 # add fulfilled time constraints for all AKs that cannot happen during full event
-                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_ak_fixed_in_slot(ak_id, timeslot.avail, ak_fixed)
-                    )
-                ])
+                self._generate_time_constraints("ak", ak_availabilities, timeslot.avail)
+
                 # add fulfilled time constraints for all persons that are not available for full event
-                time_constraints.extend([
-                    f"availability-person-{person_id}"
-                    for person_id, availabilities in person_availabilities.items()
-                    if self._test_add_constraint(timeslot.avail, availabilities)
-                ])
+                self._generate_time_constraints("person", person_availabilities, timeslot.avail)
+
                 # add fulfilled time constraints for all rooms that are not available for full event
+                self._generate_time_constraints("room", room_availabilities, timeslot.avail)
+
+                # add fulfilled time constraints for all AKSlots fixed to happen during timeslot
                 time_constraints.extend([
-                    f"availability-room-{room_id}"
-                    for room_id, availabilities in room_availabilities.items()
-                    if self._test_add_constraint(timeslot.avail, availabilities)
+                    f"fixed-akslot-{slot.id}"
+                    for slot in AKSlot.objects.filter(event=self.event, fixed=True)
+                                              .exclude(start__isnull=True)
+                    if self._test_akslot_fixed_in_timeslot(slot, timeslot.avail)
                 ])
+
                 time_constraints.extend(timeslot.constraints)
 
                 current_block.append({
-- 
GitLab


From cf2637d26115e30ed2e2ca10c919f45f14620ade Mon Sep 17 00:00:00 2001
From: Felix Blanke <info@fblanke.de>
Date: Sun, 26 Jan 2025 23:29:04 +0100
Subject: [PATCH 2/5] Rename constraint for fixed rooms

---
 AKModel/models.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/AKModel/models.py b/AKModel/models.py
index b645c0c1..895523fd 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -826,7 +826,7 @@ class Room(models.Model):
             "time_constraints": time_constraints
         }
 
-        data["fulfilled_room_constraints"].append(f"availability-room-{self.pk}")
+        data["fulfilled_room_constraints"].append(f"fixed-room-{self.pk}")
 
         if not any(constr.startswith("proxy") for constr in data["fulfilled_room_constraints"]):
             data["fulfilled_room_constraints"].append("no-proxy")
@@ -990,7 +990,7 @@ class AKSlot(models.Model):
             data["time_constraints"].extend(category_constraints)
 
         if self.fixed and self.room is not None:
-            data["room_constraints"].append(f"availability-room-{self.room.pk}")
+            data["room_constraints"].append(f"fixed-room-{self.room.pk}")
 
         if not any(constr.startswith("proxy") for constr in data["room_constraints"]):
             data["room_constraints"].append("no-proxy")
-- 
GitLab


From e3bdd34795a3c1b926bf568e17c05e8f21810082 Mon Sep 17 00:00:00 2001
From: Felix Blanke <info@fblanke.de>
Date: Mon, 27 Jan 2025 00:19:20 +0100
Subject: [PATCH 3/5] Only add constr if event is not covered

---
 AKModel/models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/AKModel/models.py b/AKModel/models.py
index 895523fd..ea5461bf 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -945,7 +945,7 @@ class AKSlot(models.Model):
 
         if self.fixed and self.start is not None:
             ak_time_constraints = [f"fixed-akslot-{self.id}"]
-        elif Availability.is_event_covered(self.event, self.ak.availabilities.all()):
+        elif not Availability.is_event_covered(self.event, self.ak.availabilities.all()):
             ak_time_constraints = [f"availability-ak-{self.ak.pk}"]
         else:
             ak_time_constraints = []
-- 
GitLab


From 205436f871305b7071729f33609be28137fe83cc Mon Sep 17 00:00:00 2001
From: Felix Blanke <info@fblanke.de>
Date: Mon, 27 Jan 2025 00:34:48 +0100
Subject: [PATCH 4/5] Iterate over AKs instead of AKSlots

---
 AKModel/views/ak.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/AKModel/views/ak.py b/AKModel/views/ak.py
index d266e9bc..caee99b4 100644
--- a/AKModel/views/ak.py
+++ b/AKModel/views/ak.py
@@ -109,8 +109,8 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
             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"]
+            ak.pk: Availability.union(ak.availabilities.all())
+            for ak in AK.objects.filter(event=self.event).all()
         }
         room_availabilities = {
             room.pk: Availability.union(room.availabilities.all())
-- 
GitLab


From 26f432ad285d715fea550ac09b4b868a9d4deccb Mon Sep 17 00:00:00 2001
From: Felix Blanke <info@fblanke.de>
Date: Mon, 27 Jan 2025 00:35:29 +0100
Subject: [PATCH 5/5] Add generated constraints to list

---
 AKModel/views/ak.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/AKModel/views/ak.py b/AKModel/views/ak.py
index caee99b4..5e642dd5 100644
--- a/AKModel/views/ak.py
+++ b/AKModel/views/ak.py
@@ -134,13 +134,19 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
                     time_constraints.append("resolution")
 
                 # add fulfilled time constraints for all AKs that cannot happen during full event
-                self._generate_time_constraints("ak", ak_availabilities, timeslot.avail)
+                time_constraints.extend(
+                    self._generate_time_constraints("ak", ak_availabilities, timeslot.avail)
+                )
 
                 # add fulfilled time constraints for all persons that are not available for full event
-                self._generate_time_constraints("person", person_availabilities, timeslot.avail)
+                time_constraints.extend(
+                    self._generate_time_constraints("person", person_availabilities, timeslot.avail)
+                )
 
                 # add fulfilled time constraints for all rooms that are not available for full event
-                self._generate_time_constraints("room", room_availabilities, timeslot.avail)
+                time_constraints.extend(
+                    self._generate_time_constraints("room", room_availabilities, timeslot.avail)
+                )
 
                 # add fulfilled time constraints for all AKSlots fixed to happen during timeslot
                 time_constraints.extend([
-- 
GitLab