From 74ac670a74e517abed97b72e285280486b97ecce Mon Sep 17 00:00:00 2001
From: Felix Blanke <info@fblanke.de>
Date: Thu, 6 Feb 2025 17:41:16 +0100
Subject: [PATCH] Add export slot length to event

---
 AKModel/migrations/0061_event_export_slot.py | 24 ++++++++++++++++++++
 AKModel/models.py                            | 18 +++++++++++----
 AKModel/views/ak.py                          | 10 ++------
 3 files changed, 39 insertions(+), 13 deletions(-)
 create mode 100644 AKModel/migrations/0061_event_export_slot.py

diff --git a/AKModel/migrations/0061_event_export_slot.py b/AKModel/migrations/0061_event_export_slot.py
new file mode 100644
index 00000000..3b40b88c
--- /dev/null
+++ b/AKModel/migrations/0061_event_export_slot.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.2.13 on 2025-02-06 16:09
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("AKModel", "0060_orga_message_resolved"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="event",
+            name="export_slot",
+            field=models.DecimalField(
+                decimal_places=2,
+                default=1,
+                help_text="Slot duration in hours that is used in the timeslot discretization, when this event is exported for the solver.",
+                max_digits=4,
+                verbose_name="Export Slot Length",
+            ),
+        ),
+    ]
diff --git a/AKModel/models.py b/AKModel/models.py
index 1cd3f1ba..b3820a15 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -151,6 +151,12 @@ class Event(models.Model):
     wiki_export_template_name = models.CharField(verbose_name=_("Wiki Export Template Name"), blank=True, max_length=50)
     default_slot = models.DecimalField(max_digits=4, decimal_places=2, default=2, verbose_name=_('Default Slot Length'),
                                        help_text=_('Default length in hours that is assumed for AKs in this event.'))
+    export_slot = models.DecimalField(max_digits=4, decimal_places=2, default=1, verbose_name=_('Export Slot Length'),
+                                        help_text=_(
+                                            'Slot duration in hours that is used in the timeslot discretization, when this event '
+                                            'is exported for the solver.'
+                                        ))
+
 
     contact_email = models.EmailField(verbose_name=_("Contact email address"), blank=True,
                                         help_text=_("An email address that is displayed on every page "
@@ -336,7 +342,7 @@ class Event(models.Model):
 
         return slot_index
 
-    def uniform_time_slots(self, *, slots_in_an_hour: float = 1.0) -> Iterable[TimeslotBlock]:
+    def uniform_time_slots(self, *, slots_in_an_hour: float) -> Iterable[TimeslotBlock]:
         """Uniformly discretize the entire event into blocks of timeslots.
 
         Discretizes entire event uniformly. May not necessarily result in a single block
@@ -358,7 +364,7 @@ class Event(models.Model):
             constraints=all_category_constraints,
         )
 
-    def default_time_slots(self, *, slots_in_an_hour: float = 1.0) -> Iterable[TimeslotBlock]:
+    def default_time_slots(self, *, slots_in_an_hour: float) -> Iterable[TimeslotBlock]:
         """Discretize all default slots into blocks of timeslots.
 
         In the discretization each default slot corresponds to one block.
@@ -384,7 +390,7 @@ class Event(models.Model):
                 constraints=category_constraints,
             )
 
-    def discretize_timeslots(self, *, slots_in_an_hour: float = 1.0) -> Iterable[TimeslotBlock]:
+    def discretize_timeslots(self, *, slots_in_an_hour: float | None = None) -> Iterable[TimeslotBlock]:
         """"Choose discretization scheme.
 
         Uses default_time_slots if the event has any DefaultSlot, otherwise uniform_time_slots.
@@ -395,6 +401,9 @@ class Event(models.Model):
         :ytype: list of TimeslotBlock
         """
 
+        if slots_in_an_hour is None:
+            slots_in_an_hour = float(self.export_slot)
+
         if DefaultSlot.objects.filter(event=self).exists():
             # discretize default slots if they exists
             yield from merge_blocks(self.default_time_slots(slots_in_an_hour=slots_in_an_hour))
@@ -1007,10 +1016,9 @@ class AKSlot(models.Model):
 
         ceil_offet_eps = decimal.Decimal(1e-4)
 
-        # self.slots_in_an_hour is set in AKJSONExportView
         data = {
             "id": str(self.pk),
-            "duration": math.ceil(self.duration * self.slots_in_an_hour - ceil_offet_eps),
+            "duration": math.ceil(self.duration * self.event.export_slot - ceil_offet_eps),
             "properties": {
                 "conflicts":
                     [str(conflict.pk) for conflict in conflict_slots.all()]
diff --git a/AKModel/views/ak.py b/AKModel/views/ak.py
index bca1c5b3..d9a5e6a8 100644
--- a/AKModel/views/ak.py
+++ b/AKModel/views/ak.py
@@ -91,17 +91,11 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
         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), },
+            "info": {"duration": (1.0 / float(self.event.export_slot)), },
             "blocks": [],
             }
 
-        for slot in context["slots"]:
-            slot.slots_in_an_hour = SLOTS_IN_AN_HOUR
-
         ak_availabilities = {
             ak.pk: Availability.union(ak.availabilities.all())
             for ak in AK.objects.filter(event=self.event).all()
@@ -115,7 +109,7 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView):
             for person in AKOwner.objects.filter(event=self.event)
         }
 
-        blocks = self.event.discretize_timeslots(slots_in_an_hour=SLOTS_IN_AN_HOUR)
+        blocks = self.event.discretize_timeslots()
 
         for block in blocks:
             current_block = []
-- 
GitLab