From 0e96698dd29538547d7a6bef0185ce62e15b6799 Mon Sep 17 00:00:00 2001 From: Felix Blanke <info@fblanke.de> Date: Sat, 8 Feb 2025 14:51:00 +0100 Subject: [PATCH 1/9] Do not export empty blocks --- AKModel/views/ak.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AKModel/views/ak.py b/AKModel/views/ak.py index f6fd7932..786f389c 100644 --- a/AKModel/views/ak.py +++ b/AKModel/views/ak.py @@ -114,6 +114,9 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): for block in blocks: current_block = [] + if not block: + continue + for timeslot in block: time_constraints = [] # if reso_deadline is set and timeslot ends before it, -- GitLab From 66035f4d23130be265e6f5c9396a0bace4aebe1e Mon Sep 17 00:00:00 2001 From: Felix Blanke <info@fblanke.de> Date: Sat, 8 Feb 2025 15:13:26 +0100 Subject: [PATCH 2/9] Add blocknames to export --- AKModel/views/ak.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/AKModel/views/ak.py b/AKModel/views/ak.py index 786f389c..676b01da 100644 --- a/AKModel/views/ak.py +++ b/AKModel/views/ak.py @@ -1,4 +1,5 @@ import json +from datetime import datetime from typing import List from django.contrib import messages @@ -111,12 +112,26 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): blocks = self.event.discretize_timeslots() + block_names = [] + for block in blocks: current_block = [] if not block: continue + block_start = block[0].avail.start + block_end = block[-1].avail.end + + start_day = block_start.strftime("%A, %d. %b") + if block_start.date() == block_end.date(): + # same day + time_str = block_start.strftime("%H:%M") + " – " + block_end.strftime("%H:%M") + else: + # different days + time_str = block_start.strftime("%a %H:%M") + " – " + block_end.strftime("%a %H:%M") + block_names.append([start_day, time_str]) + for timeslot in block: time_constraints = [] # if reso_deadline is set and timeslot ends before it, @@ -160,6 +175,8 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): timeslots["blocks"].append(current_block) + timeslots["info"]["blocknames"] = block_names + context["timeslots"] = json.dumps(timeslots) info_dict = { -- GitLab From 21d7a1e8877a3db57cc7b1c0a6dced4ddd75064e Mon Sep 17 00:00:00 2001 From: Felix Blanke <info@fblanke.de> Date: Sat, 8 Feb 2025 15:14:51 +0100 Subject: [PATCH 3/9] Update tests for blocknames --- AKModel/tests/test_json_export.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/AKModel/tests/test_json_export.py b/AKModel/tests/test_json_export.py index 5a604dc7..280c7b54 100644 --- a/AKModel/tests/test_json_export.py +++ b/AKModel/tests/test_json_export.py @@ -248,7 +248,7 @@ class JSONExportTest(TestCase): ) self.assertEqual( self.export_dict["timeslots"]["info"].keys(), - {"duration"}, + {"duration", "blocknames"}, "timeslot info keys not as expected", ) self._check_type( @@ -257,6 +257,21 @@ class JSONExportTest(TestCase): "info/duration", item=item, ) + self._check_lst( + self.export_dict["timeslots"]["info"]["blocknames"], + "info/blocknames", + item=item, + contained_type=list, + ) + for blockname in self.export_dict["timeslots"]["info"]["blocknames"]: + self.assertEqual(len(blockname), 2) + self._check_lst( + blockname, + "info/blocknames/entry", + item=item, + contained_type=str, + ) + self._check_lst( self.export_dict["timeslots"]["blocks"], "blocks", -- GitLab From 324715051b74f6b29a8e34a1fabaa37a530df856 Mon Sep 17 00:00:00 2001 From: Felix Blanke <info@fblanke.de> Date: Sat, 8 Feb 2025 15:30:56 +0100 Subject: [PATCH 4/9] Add tests on timeslot info dicts --- AKModel/tests/test_json_export.py | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/AKModel/tests/test_json_export.py b/AKModel/tests/test_json_export.py index 280c7b54..84d8840c 100644 --- a/AKModel/tests/test_json_export.py +++ b/AKModel/tests/test_json_export.py @@ -794,3 +794,42 @@ class JSONExportTest(TestCase): fulfilled_time_constraints, set(timeslot["fulfilled_time_constraints"]), ) + + def test_timeslots_info(self): + """Test timeslots info dict""" + for event in Event.objects.all(): + with self.subTest(event=event): + self.set_up_event(event=event) + + self.assertAlmostEqual( + self.export_dict["timeslots"]["info"]["duration"], + float(self.event.export_slot), + ) + + block_names = [] + for block in self.export_dict["timeslots"]["blocks"]: + if not block: + continue + + block_start, _ = self._get_timeslot_start_end(block[0]) + _, block_end = self._get_timeslot_start_end(block[-1]) + + start_day = block_start.strftime("%A, %d. %b") + if block_start.date() == block_end.date(): + # same day + time_str = ( + block_start.strftime("%H:%M") + + " – " + + block_end.strftime("%H:%M") + ) + else: + # different days + time_str = ( + block_start.strftime("%a %H:%M") + + " – " + + block_end.strftime("%a %H:%M") + ) + block_names.append([start_day, time_str]) + self.assertEqual( + block_names, self.export_dict["timeslots"]["info"]["blocknames"] + ) -- GitLab From c7764497c41e2090ca5b85e4aad482cb02641dcb Mon Sep 17 00:00:00 2001 From: Felix Blanke <info@fblanke.de> Date: Sat, 8 Feb 2025 15:31:14 +0100 Subject: [PATCH 5/9] Use timezone for blocknames --- 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 676b01da..9555d17e 100644 --- a/AKModel/views/ak.py +++ b/AKModel/views/ak.py @@ -120,8 +120,8 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): if not block: continue - block_start = block[0].avail.start - block_end = block[-1].avail.end + block_start = block[0].avail.start.astimezone(self.event.timezone) + block_end = block[-1].avail.end.astimezone(self.event.timezone) start_day = block_start.strftime("%A, %d. %b") if block_start.date() == block_end.date(): -- GitLab From 7b9190a30e1473ca07be8472fa88ef57c65af9cc Mon Sep 17 00:00:00 2001 From: Felix Blanke <info@fblanke.de> Date: Sat, 8 Feb 2025 15:48:03 +0100 Subject: [PATCH 6/9] Split block iteration --- AKModel/tests/test_json_export.py | 129 +++++++++++++++--------------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/AKModel/tests/test_json_export.py b/AKModel/tests/test_json_export.py index 84d8840c..6dba21c9 100644 --- a/AKModel/tests/test_json_export.py +++ b/AKModel/tests/test_json_export.py @@ -725,75 +725,74 @@ class JSONExportTest(TestCase): self.set_up_event(event=event) cat_avails = self._get_cat_availability() - for timeslot in chain.from_iterable( - self.export_dict["timeslots"]["blocks"] - ): - start, end = self._get_timeslot_start_end(timeslot) - timeslot_avail = Availability( - event=self.event, start=start, end=end - ) - - fulfilled_time_constraints = set() - - # reso deadline - if self.event.reso_deadline is not None: - # timeslot ends before deadline - if end < self.event.reso_deadline.astimezone( - self.event.timezone - ): - fulfilled_time_constraints.add("resolution") - - # add category constraints - fulfilled_time_constraints |= ( - AKCategory.create_category_constraints( - [ - cat - for cat in AKCategory.objects.filter( - event=self.event - ).all() - if timeslot_avail.is_covered(cat_avails[cat.name]) - ] + for block_idx, block in enumerate(self.export_dict["timeslots"]["blocks"]): + for timeslot in block: + start, end = self._get_timeslot_start_end(timeslot) + timeslot_avail = Availability( + event=self.event, start=start, end=end ) - ) - # add owner constraints - fulfilled_time_constraints |= { - f"availability-person-{owner.id}" - for owner in AKOwner.objects.filter(event=self.event).all() - if self._is_restricted_and_contained_slot( - timeslot_avail, - Availability.union(owner.availabilities.all()), - ) - } - - # add room constraints - fulfilled_time_constraints |= { - f"availability-room-{room.id}" - for room in self.rooms - if self._is_restricted_and_contained_slot( - timeslot_avail, - Availability.union(room.availabilities.all()), - ) - } - - # add ak constraints - fulfilled_time_constraints |= { - f"availability-ak-{ak.id}" - for ak in AK.objects.filter(event=event) - if self._is_restricted_and_contained_slot( - timeslot_avail, Availability.union(ak.availabilities.all()) + fulfilled_time_constraints = set() + + # reso deadline + if self.event.reso_deadline is not None: + # timeslot ends before deadline + if end < self.event.reso_deadline.astimezone( + self.event.timezone + ): + fulfilled_time_constraints.add("resolution") + + # add category constraints + fulfilled_time_constraints |= ( + AKCategory.create_category_constraints( + [ + cat + for cat in AKCategory.objects.filter( + event=self.event + ).all() + if timeslot_avail.is_covered(cat_avails[cat.name]) + ] + ) ) - } - fulfilled_time_constraints |= { - f"fixed-akslot-{slot.id}" - for slot in self.ak_slots - if self._is_ak_fixed_in_slot(slot, timeslot_avail) - } - self.assertEqual( - fulfilled_time_constraints, - set(timeslot["fulfilled_time_constraints"]), - ) + # add owner constraints + fulfilled_time_constraints |= { + f"availability-person-{owner.id}" + for owner in AKOwner.objects.filter(event=self.event).all() + if self._is_restricted_and_contained_slot( + timeslot_avail, + Availability.union(owner.availabilities.all()), + ) + } + + # add room constraints + fulfilled_time_constraints |= { + f"availability-room-{room.id}" + for room in self.rooms + if self._is_restricted_and_contained_slot( + timeslot_avail, + Availability.union(room.availabilities.all()), + ) + } + + # add ak constraints + fulfilled_time_constraints |= { + f"availability-ak-{ak.id}" + for ak in AK.objects.filter(event=event) + if self._is_restricted_and_contained_slot( + timeslot_avail, Availability.union(ak.availabilities.all()) + ) + } + fulfilled_time_constraints |= { + f"fixed-akslot-{slot.id}" + for slot in self.ak_slots + if self._is_ak_fixed_in_slot(slot, timeslot_avail) + } + + self.assertEqual( + fulfilled_time_constraints, + set(timeslot["fulfilled_time_constraints"]), + ) def test_timeslots_info(self): """Test timeslots info dict""" -- GitLab From f7993e753dfdd80056be5e5c2783dbde2410402b Mon Sep 17 00:00:00 2001 From: Felix Blanke <info@fblanke.de> Date: Sat, 8 Feb 2025 15:48:15 +0100 Subject: [PATCH 7/9] Add notblock constraints --- AKModel/views/ak.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AKModel/views/ak.py b/AKModel/views/ak.py index 9555d17e..f8b83f04 100644 --- a/AKModel/views/ak.py +++ b/AKModel/views/ak.py @@ -110,11 +110,11 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): for person in AKOwner.objects.filter(event=self.event) } - blocks = self.event.discretize_timeslots() + blocks = list(self.event.discretize_timeslots()) block_names = [] - for block in blocks: + for block_idx, block in enumerate(blocks): current_block = [] if not block: @@ -132,6 +132,8 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): time_str = block_start.strftime("%a %H:%M") + " – " + block_end.strftime("%a %H:%M") block_names.append([start_day, time_str]) + block_timeconstraints = [f"notblock{idx}" for idx in range(len(blocks)) if idx != block_idx] + for timeslot in block: time_constraints = [] # if reso_deadline is set and timeslot ends before it, @@ -163,6 +165,7 @@ class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): ]) time_constraints.extend(timeslot.constraints) + time_constraints.extend(block_timeconstraints) current_block.append({ "id": str(timeslot.idx), -- GitLab From c8de7e51b8b7cffb50458be083f6d38a258bd6c7 Mon Sep 17 00:00:00 2001 From: Felix Blanke <info@fblanke.de> Date: Sat, 8 Feb 2025 15:52:01 +0100 Subject: [PATCH 8/9] Format --- AKModel/tests/test_json_export.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AKModel/tests/test_json_export.py b/AKModel/tests/test_json_export.py index 6dba21c9..232121c9 100644 --- a/AKModel/tests/test_json_export.py +++ b/AKModel/tests/test_json_export.py @@ -725,7 +725,9 @@ class JSONExportTest(TestCase): self.set_up_event(event=event) cat_avails = self._get_cat_availability() - for block_idx, block in enumerate(self.export_dict["timeslots"]["blocks"]): + for block_idx, block in enumerate( + self.export_dict["timeslots"]["blocks"] + ): for timeslot in block: start, end = self._get_timeslot_start_end(timeslot) timeslot_avail = Availability( @@ -780,7 +782,8 @@ class JSONExportTest(TestCase): f"availability-ak-{ak.id}" for ak in AK.objects.filter(event=event) if self._is_restricted_and_contained_slot( - timeslot_avail, Availability.union(ak.availabilities.all()) + timeslot_avail, + Availability.union(ak.availabilities.all()), ) } fulfilled_time_constraints |= { -- GitLab From 109f1bad9f90e9e69dcacbe1ce112ea0876c15fa Mon Sep 17 00:00:00 2001 From: Felix Blanke <info@fblanke.de> Date: Sat, 8 Feb 2025 15:52:12 +0100 Subject: [PATCH 9/9] Add notblock constraints in unit tests --- AKModel/tests/test_json_export.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AKModel/tests/test_json_export.py b/AKModel/tests/test_json_export.py index 232121c9..48c8d7fd 100644 --- a/AKModel/tests/test_json_export.py +++ b/AKModel/tests/test_json_export.py @@ -725,6 +725,7 @@ class JSONExportTest(TestCase): self.set_up_event(event=event) cat_avails = self._get_cat_availability() + num_blocks = len(self.export_dict["timeslots"]["blocks"]) for block_idx, block in enumerate( self.export_dict["timeslots"]["blocks"] ): @@ -792,6 +793,12 @@ class JSONExportTest(TestCase): if self._is_ak_fixed_in_slot(slot, timeslot_avail) } + fulfilled_time_constraints |= { + f"notblock{idx}" + for idx in range(num_blocks) + if idx != block_idx + } + self.assertEqual( fulfilled_time_constraints, set(timeslot["fulfilled_time_constraints"]), -- GitLab