From d8c5206e2ed23b9577146ddf3e66922136374399 Mon Sep 17 00:00:00 2001 From: jo Date: Sun, 19 Sep 2021 20:48:19 +0200 Subject: [PATCH 1/4] Fix cueout for overlapping starts & ends schedule --- api/libretimeapi/models/schedule.py | 42 ++++++++++--- api/libretimeapi/tests/models/__init__.py | 0 .../tests/models/test_schedule.py | 62 +++++++++++++++++++ 3 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 api/libretimeapi/tests/models/__init__.py create mode 100644 api/libretimeapi/tests/models/test_schedule.py diff --git a/api/libretimeapi/models/schedule.py b/api/libretimeapi/models/schedule.py index 314ef5ec2..f4bc7aa61 100644 --- a/api/libretimeapi/models/schedule.py +++ b/api/libretimeapi/models/schedule.py @@ -24,21 +24,45 @@ class Schedule(models.Model): def get_cueout(self): """ - Returns a cueout that is based on the current show. Cueout of a specific - item can potentially overrun the show that it is scheduled in. In that - case, the cueout should be the end of the show. This prevents the next - show having overlapping items playing. + Returns a scheduled item cueout that is based on the current show instance. + + Cueout of a specific item can potentially overrun the show that it is + scheduled in. In that case, the cueout should be the end of the show. + This prevents the next show having overlapping items playing. + + Cases: + - When the schedule ends before the end of the show instance, + return the stored cueout. + + - When the schedule starts before the end of the show instance + and ends after the show instance ends, + return timedelta between schedule starts and show instance ends. + + - When the schedule starts after the end of the show instance, + return the stored cue_out even if the schedule WILL NOT BE PLAYED. """ - if self.instance.ends < self.ends: + if self.starts < self.instance.ends and self.instance.ends < self.ends: return self.instance.ends - self.starts return self.cue_out def get_ends(self): """ - Returns an item end that is based on the current show. Ends of a - specific item can potentially overrun the show that it is scheduled in. - In that case, the end should be the end of the show. This prevents the - next show having overlapping items playing. + Returns a scheduled item ends that is based on the current show instance. + + End of a specific item can potentially overrun the show that it is + scheduled in. In that case, the end should be the end of the show. + This prevents the next show having overlapping items playing. + + Cases: + - When the schedule ends before the end of the show instance, + return the scheduled item ends. + + - When the schedule starts before the end of the show instance + and ends after the show instance ends, + return the show instance ends. + + - When the schedule starts after the end of the show instance, + return the show instance ends. """ if self.instance.ends < self.ends: return self.instance.ends diff --git a/api/libretimeapi/tests/models/__init__.py b/api/libretimeapi/tests/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/libretimeapi/tests/models/test_schedule.py b/api/libretimeapi/tests/models/test_schedule.py new file mode 100644 index 000000000..b04e25dd8 --- /dev/null +++ b/api/libretimeapi/tests/models/test_schedule.py @@ -0,0 +1,62 @@ +from datetime import datetime, timedelta + +from django.test import SimpleTestCase +from libretimeapi.models import Schedule, Show, ShowInstance + + +class TestSchedule(SimpleTestCase): + def test_get_cueout(self): + show_instance = ShowInstance( + created=datetime(year=2021, month=10, day=1, hour=12), + starts=datetime(year=2021, month=10, day=2, hour=1), + ends=datetime(year=2021, month=10, day=2, hour=2), + ) + + length = timedelta(minutes=10) + cue_in = timedelta(seconds=1) + cue_out = length - timedelta(seconds=4) + + # No overlapping schedule datetimes, normal usecase: + s1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30) + s1_ends = s1_starts + length + + s1 = Schedule( + starts=s1_starts, + ends=s1_ends, + cue_in=cue_in, + cue_out=cue_out, + instance=show_instance, + ) + + self.assertEqual(s1.get_cueout(), cue_out) + self.assertEqual(s1.get_ends(), s1_ends) + + # Mixed overlapping schedule datetimes (only ends is overlapping): + s2_starts = datetime(year=2021, month=10, day=2, hour=1, minute=55) + s2_ends = s2_starts + length + + s2 = Schedule( + starts=s2_starts, + ends=s2_ends, + cue_in=cue_in, + cue_out=cue_out, + instance=show_instance, + ) + + self.assertEqual(s2.get_cueout(), timedelta(minutes=5)) + self.assertEqual(s2.get_ends(), show_instance.ends) + + # Fully overlapping schedule datetimes (starts and ends are overlapping): + s3_starts = datetime(year=2021, month=10, day=2, hour=2, minute=1) + s3_ends = s3_starts + length + + s3 = Schedule( + starts=s3_starts, + ends=s3_ends, + cue_in=cue_in, + cue_out=cue_out, + instance=show_instance, + ) + + self.assertEqual(s3.get_cueout(), cue_out) + self.assertEqual(s3.get_ends(), show_instance.ends) From f84e40dc06943f8ce7e32b2eb0ab97e58a9c1bde Mon Sep 17 00:00:00 2001 From: jo Date: Sun, 19 Sep 2021 22:38:49 +0200 Subject: [PATCH 2/4] Small refactoring --- .../tests/models/test_schedule.py | 44 ++++++------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/api/libretimeapi/tests/models/test_schedule.py b/api/libretimeapi/tests/models/test_schedule.py index b04e25dd8..1e166dfaa 100644 --- a/api/libretimeapi/tests/models/test_schedule.py +++ b/api/libretimeapi/tests/models/test_schedule.py @@ -16,47 +16,29 @@ class TestSchedule(SimpleTestCase): cue_in = timedelta(seconds=1) cue_out = length - timedelta(seconds=4) + def create_schedule(starts): + return Schedule( + starts=starts, + ends=starts + length, + cue_in=cue_in, + cue_out=cue_out, + instance=show_instance, + ) + # No overlapping schedule datetimes, normal usecase: s1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30) - s1_ends = s1_starts + length - - s1 = Schedule( - starts=s1_starts, - ends=s1_ends, - cue_in=cue_in, - cue_out=cue_out, - instance=show_instance, - ) - + s1 = create_schedule(s1_starts) self.assertEqual(s1.get_cueout(), cue_out) - self.assertEqual(s1.get_ends(), s1_ends) + self.assertEqual(s1.get_ends(), s1_starts + length) # Mixed overlapping schedule datetimes (only ends is overlapping): s2_starts = datetime(year=2021, month=10, day=2, hour=1, minute=55) - s2_ends = s2_starts + length - - s2 = Schedule( - starts=s2_starts, - ends=s2_ends, - cue_in=cue_in, - cue_out=cue_out, - instance=show_instance, - ) - + s2 = create_schedule(s2_starts) self.assertEqual(s2.get_cueout(), timedelta(minutes=5)) self.assertEqual(s2.get_ends(), show_instance.ends) # Fully overlapping schedule datetimes (starts and ends are overlapping): s3_starts = datetime(year=2021, month=10, day=2, hour=2, minute=1) - s3_ends = s3_starts + length - - s3 = Schedule( - starts=s3_starts, - ends=s3_ends, - cue_in=cue_in, - cue_out=cue_out, - instance=show_instance, - ) - + s3 = create_schedule(s3_starts) self.assertEqual(s3.get_cueout(), cue_out) self.assertEqual(s3.get_ends(), show_instance.ends) From fc02f8cafb30b31d88eb82eca5c61767b6eb3fea Mon Sep 17 00:00:00 2001 From: jo Date: Sun, 19 Sep 2021 22:39:10 +0200 Subject: [PATCH 3/4] Cleanup --- api/libretimeapi/tests/models/test_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/libretimeapi/tests/models/test_schedule.py b/api/libretimeapi/tests/models/test_schedule.py index 1e166dfaa..8250b7cc9 100644 --- a/api/libretimeapi/tests/models/test_schedule.py +++ b/api/libretimeapi/tests/models/test_schedule.py @@ -1,7 +1,7 @@ from datetime import datetime, timedelta from django.test import SimpleTestCase -from libretimeapi.models import Schedule, Show, ShowInstance +from libretimeapi.models import Schedule, ShowInstance class TestSchedule(SimpleTestCase): From 6caa29e0c97c3d11fa66dae306e2897c8cbb972e Mon Sep 17 00:00:00 2001 From: Kyle Robbertze Date: Tue, 21 Sep 2021 11:36:49 +0200 Subject: [PATCH 4/4] filter out invalid schedule items --- api/libretimeapi/models/schedule.py | 8 +++ .../tests/models/test_schedule.py | 56 +++++++++++-------- api/libretimeapi/views.py | 2 +- .../api_clients/api_clients/version2.py | 1 + 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/api/libretimeapi/models/schedule.py b/api/libretimeapi/models/schedule.py index f4bc7aa61..56f68a70a 100644 --- a/api/libretimeapi/models/schedule.py +++ b/api/libretimeapi/models/schedule.py @@ -68,6 +68,14 @@ class Schedule(models.Model): return self.instance.ends return self.ends + @property + def is_valid(self): + """ + A schedule item is valid if it starts before the end of the show instance + it is in + """ + return self.starts < self.instance.ends + class Meta: managed = False db_table = "cc_schedule" diff --git a/api/libretimeapi/tests/models/test_schedule.py b/api/libretimeapi/tests/models/test_schedule.py index 8250b7cc9..4892c3385 100644 --- a/api/libretimeapi/tests/models/test_schedule.py +++ b/api/libretimeapi/tests/models/test_schedule.py @@ -5,40 +5,52 @@ from libretimeapi.models import Schedule, ShowInstance class TestSchedule(SimpleTestCase): - def test_get_cueout(self): - show_instance = ShowInstance( + @classmethod + def setUpTestData(cls): + cls.show_instance = ShowInstance( created=datetime(year=2021, month=10, day=1, hour=12), starts=datetime(year=2021, month=10, day=2, hour=1), ends=datetime(year=2021, month=10, day=2, hour=2), ) + cls.length = timedelta(minutes=10) + cls.cue_in = timedelta(seconds=1) + cls.cue_out = cls.length - timedelta(seconds=4) - length = timedelta(minutes=10) - cue_in = timedelta(seconds=1) - cue_out = length - timedelta(seconds=4) - - def create_schedule(starts): - return Schedule( - starts=starts, - ends=starts + length, - cue_in=cue_in, - cue_out=cue_out, - instance=show_instance, - ) + def create_schedule(self, starts): + return Schedule( + starts=starts, + ends=starts + self.length, + cue_in=self.cue_in, + cue_out=self.cue_out, + instance=self.show_instance, + ) + def test_get_cueout(self): # No overlapping schedule datetimes, normal usecase: s1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30) - s1 = create_schedule(s1_starts) - self.assertEqual(s1.get_cueout(), cue_out) - self.assertEqual(s1.get_ends(), s1_starts + length) + s1 = self.create_schedule(s1_starts) + self.assertEqual(s1.get_cueout(), self.cue_out) + self.assertEqual(s1.get_ends(), s1_starts + self.length) # Mixed overlapping schedule datetimes (only ends is overlapping): s2_starts = datetime(year=2021, month=10, day=2, hour=1, minute=55) - s2 = create_schedule(s2_starts) + s2 = self.create_schedule(s2_starts) self.assertEqual(s2.get_cueout(), timedelta(minutes=5)) - self.assertEqual(s2.get_ends(), show_instance.ends) + self.assertEqual(s2.get_ends(), self.show_instance.ends) # Fully overlapping schedule datetimes (starts and ends are overlapping): s3_starts = datetime(year=2021, month=10, day=2, hour=2, minute=1) - s3 = create_schedule(s3_starts) - self.assertEqual(s3.get_cueout(), cue_out) - self.assertEqual(s3.get_ends(), show_instance.ends) + s3 = self.create_schedule(s3_starts) + self.assertEqual(s3.get_cueout(), self.cue_out) + self.assertEqual(s3.get_ends(), self.show_instance.ends) + + def test_is_valid(self): + # Starts before the schedule ends + s1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30) + s1 = self.create_schedule(s1_starts) + self.assertTrue(s1.is_valid) + + # Starts after the schedule ends + s2_starts = datetime(year=2021, month=10, day=2, hour=3) + s2 = self.create_schedule(s2_starts) + self.assertFalse(s2.is_valid) diff --git a/api/libretimeapi/views.py b/api/libretimeapi/views.py index 793a3b593..7bc360717 100644 --- a/api/libretimeapi/views.py +++ b/api/libretimeapi/views.py @@ -141,7 +141,7 @@ class PreferenceViewSet(viewsets.ModelViewSet): class ScheduleViewSet(viewsets.ModelViewSet): queryset = Schedule.objects.all() serializer_class = ScheduleSerializer - filter_fields = ("starts", "ends", "playout_status", "broadcasted") + filter_fields = ("starts", "ends", "playout_status", "broadcasted", "is_valid") model_permission_name = "schedule" diff --git a/python_apps/api_clients/api_clients/version2.py b/python_apps/api_clients/api_clients/version2.py index f15d1f28a..74ab74939 100644 --- a/python_apps/api_clients/api_clients/version2.py +++ b/python_apps/api_clients/api_clients/version2.py @@ -54,6 +54,7 @@ class AirtimeApiClient: data = self.services.schedule_url( params={ "ends__range": ("{}Z,{}Z".format(str_current, str_end)), + "is_valid": True, } ) result = {"media": {}}