diff --git a/api/libretimeapi/models/schedule.py b/api/libretimeapi/models/schedule.py index 314ef5ec2..56f68a70a 100644 --- a/api/libretimeapi/models/schedule.py +++ b/api/libretimeapi/models/schedule.py @@ -24,26 +24,58 @@ 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 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/__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..4892c3385 --- /dev/null +++ b/api/libretimeapi/tests/models/test_schedule.py @@ -0,0 +1,56 @@ +from datetime import datetime, timedelta + +from django.test import SimpleTestCase +from libretimeapi.models import Schedule, ShowInstance + + +class TestSchedule(SimpleTestCase): + @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) + + 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 = 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 = self.create_schedule(s2_starts) + self.assertEqual(s2.get_cueout(), timedelta(minutes=5)) + 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 = 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": {}}