chore(api): rename schedule models fields
This commit is contained in:
parent
8ceb1419a0
commit
57046e2a9d
|
@ -2,8 +2,14 @@ from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Schedule(models.Model):
|
class Schedule(models.Model):
|
||||||
starts = models.DateTimeField()
|
starts_at = models.DateTimeField(db_column="starts")
|
||||||
ends = models.DateTimeField()
|
ends_at = models.DateTimeField(db_column="ends")
|
||||||
|
|
||||||
|
instance = models.ForeignKey(
|
||||||
|
"schedule.ShowInstance",
|
||||||
|
on_delete=models.DO_NOTHING,
|
||||||
|
)
|
||||||
|
|
||||||
file = models.ForeignKey(
|
file = models.ForeignKey(
|
||||||
"storage.File",
|
"storage.File",
|
||||||
on_delete=models.DO_NOTHING,
|
on_delete=models.DO_NOTHING,
|
||||||
|
@ -16,31 +22,60 @@ class Schedule(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
clip_length = models.DurationField(blank=True, null=True)
|
|
||||||
|
length = models.DurationField(blank=True, null=True, db_column="clip_length")
|
||||||
fade_in = models.TimeField(blank=True, null=True)
|
fade_in = models.TimeField(blank=True, null=True)
|
||||||
fade_out = models.TimeField(blank=True, null=True)
|
fade_out = models.TimeField(blank=True, null=True)
|
||||||
cue_in = models.DurationField()
|
cue_in = models.DurationField()
|
||||||
cue_out = models.DurationField()
|
cue_out = models.DurationField()
|
||||||
media_item_played = models.BooleanField(blank=True, null=True)
|
|
||||||
instance = models.ForeignKey("schedule.ShowInstance", on_delete=models.DO_NOTHING)
|
class PositionStatus(models.IntegerChoices):
|
||||||
playout_status = models.SmallIntegerField()
|
FILLER = -1, "Filler" # Used to fill a show that already started
|
||||||
broadcasted = models.SmallIntegerField()
|
OUTSIDE = 0, "Outside" # Is outside the show time frame
|
||||||
|
INSIDE = 1, "Inside" # Is inside the show time frame
|
||||||
|
BOUNDARY = 2, "Boundary" # Is at the boundary of the show time frame
|
||||||
|
|
||||||
position = models.IntegerField()
|
position = models.IntegerField()
|
||||||
|
position_status = models.SmallIntegerField(
|
||||||
|
choices=PositionStatus.choices,
|
||||||
|
default=PositionStatus.INSIDE,
|
||||||
|
db_column="playout_status",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Broadcasted is set to 1 when a live source is not
|
||||||
|
# on. Used for the playout history.
|
||||||
|
broadcasted = models.SmallIntegerField()
|
||||||
|
played = models.BooleanField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
db_column="media_item_played",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def overbooked(self):
|
||||||
|
"""
|
||||||
|
A schedule item is overbooked if it starts after the end of the show
|
||||||
|
instance it is in.
|
||||||
|
|
||||||
|
Related to self.position_status
|
||||||
|
"""
|
||||||
|
return self.starts_at >= self.instance.ends_at
|
||||||
|
|
||||||
def get_owner(self):
|
def get_owner(self):
|
||||||
return self.instance.get_owner()
|
return self.instance.get_owner()
|
||||||
|
|
||||||
def get_cueout(self):
|
def get_cue_out(self):
|
||||||
"""
|
"""
|
||||||
Returns a scheduled item cueout that is based on the current show instance.
|
Returns a scheduled item cue out that is based on the current show
|
||||||
|
instance.
|
||||||
|
|
||||||
Cueout of a specific item can potentially overrun the show that it is
|
Cue out 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.
|
scheduled in. In that case, the cue out should be the end of the show.
|
||||||
This prevents the next show having overlapping items playing.
|
This prevents the next show having overlapping items playing.
|
||||||
|
|
||||||
Cases:
|
Cases:
|
||||||
- When the schedule ends before the end of the show instance,
|
- When the schedule ends before the end of the show instance,
|
||||||
return the stored cueout.
|
return the stored cue out.
|
||||||
|
|
||||||
- When the schedule starts before the end of the show instance
|
- When the schedule starts before the end of the show instance
|
||||||
and ends after the show instance ends,
|
and ends after the show instance ends,
|
||||||
|
@ -49,13 +84,17 @@ class Schedule(models.Model):
|
||||||
- When the schedule starts after the end of the show instance,
|
- When the schedule starts after the end of the show instance,
|
||||||
return the stored cue_out even if the schedule WILL NOT BE PLAYED.
|
return the stored cue_out even if the schedule WILL NOT BE PLAYED.
|
||||||
"""
|
"""
|
||||||
if self.starts < self.instance.ends and self.instance.ends < self.ends:
|
if (
|
||||||
return self.instance.ends - self.starts
|
self.starts_at < self.instance.ends_at
|
||||||
|
and self.instance.ends_at < self.ends_at
|
||||||
|
):
|
||||||
|
return self.instance.ends_at - self.starts_at
|
||||||
return self.cue_out
|
return self.cue_out
|
||||||
|
|
||||||
def get_ends(self):
|
def get_ends_at(self):
|
||||||
"""
|
"""
|
||||||
Returns a scheduled item ends that is based on the current show instance.
|
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
|
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.
|
scheduled in. In that case, the end should be the end of the show.
|
||||||
|
@ -72,17 +111,9 @@ class Schedule(models.Model):
|
||||||
- When the schedule starts after the end of the show instance,
|
- When the schedule starts after the end of the show instance,
|
||||||
return the show instance ends.
|
return the show instance ends.
|
||||||
"""
|
"""
|
||||||
if self.instance.ends < self.ends:
|
if self.instance.ends_at < self.ends_at:
|
||||||
return self.instance.ends
|
return self.instance.ends_at
|
||||||
return self.ends
|
return self.ends_at
|
||||||
|
|
||||||
@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:
|
class Meta:
|
||||||
managed = False
|
managed = False
|
||||||
|
|
|
@ -7,29 +7,30 @@ class ScheduleSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
file_id = serializers.IntegerField(source="file.id", read_only=True)
|
file_id = serializers.IntegerField(source="file.id", read_only=True)
|
||||||
stream_id = serializers.IntegerField(source="stream.id", read_only=True)
|
stream_id = serializers.IntegerField(source="stream.id", read_only=True)
|
||||||
instance_id = serializers.IntegerField(source="instance.id", read_only=True)
|
instance_id = serializers.IntegerField(source="instance.id", read_only=True)
|
||||||
cue_out = serializers.DurationField(source="get_cueout", read_only=True)
|
cue_out = serializers.DurationField(source="get_cue_out", read_only=True)
|
||||||
ends = serializers.DateTimeField(source="get_ends", read_only=True)
|
ends_at = serializers.DateTimeField(source="get_ends_at", read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Schedule
|
model = Schedule
|
||||||
fields = [
|
fields = [
|
||||||
"item_url",
|
"item_url",
|
||||||
"id",
|
"id",
|
||||||
"starts",
|
"starts_at",
|
||||||
"ends",
|
"ends_at",
|
||||||
|
"instance",
|
||||||
|
"instance_id",
|
||||||
"file",
|
"file",
|
||||||
"file_id",
|
"file_id",
|
||||||
"stream",
|
"stream",
|
||||||
"stream_id",
|
"stream_id",
|
||||||
"clip_length",
|
"length",
|
||||||
"fade_in",
|
"fade_in",
|
||||||
"fade_out",
|
"fade_out",
|
||||||
"cue_in",
|
"cue_in",
|
||||||
"cue_out",
|
"cue_out",
|
||||||
"media_item_played",
|
|
||||||
"instance",
|
|
||||||
"instance_id",
|
|
||||||
"playout_status",
|
|
||||||
"broadcasted",
|
|
||||||
"position",
|
"position",
|
||||||
|
"position_status",
|
||||||
|
"broadcasted",
|
||||||
|
"played",
|
||||||
|
"overbooked",
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,49 +9,49 @@ class TestSchedule(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
cls.show_instance = ShowInstance(
|
cls.show_instance = ShowInstance(
|
||||||
created=datetime(year=2021, month=10, day=1, hour=12),
|
created_at=datetime(year=2021, month=10, day=1, hour=12),
|
||||||
starts=datetime(year=2021, month=10, day=2, hour=1),
|
starts_at=datetime(year=2021, month=10, day=2, hour=1),
|
||||||
ends=datetime(year=2021, month=10, day=2, hour=2),
|
ends_at=datetime(year=2021, month=10, day=2, hour=2),
|
||||||
)
|
)
|
||||||
cls.length = timedelta(minutes=10)
|
cls.length = timedelta(minutes=10)
|
||||||
cls.cue_in = timedelta(seconds=1)
|
cls.cue_in = timedelta(seconds=1)
|
||||||
cls.cue_out = cls.length - timedelta(seconds=4)
|
cls.cue_out = cls.length - timedelta(seconds=4)
|
||||||
|
|
||||||
def create_schedule(self, starts):
|
def create_schedule(self, starts_at):
|
||||||
return Schedule(
|
return Schedule(
|
||||||
starts=starts,
|
starts_at=starts_at,
|
||||||
ends=starts + self.length,
|
ends_at=starts_at + self.length,
|
||||||
cue_in=self.cue_in,
|
cue_in=self.cue_in,
|
||||||
cue_out=self.cue_out,
|
cue_out=self.cue_out,
|
||||||
instance=self.show_instance,
|
instance=self.show_instance,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_cueout(self):
|
def test_get_cue_out(self):
|
||||||
# No overlapping schedule datetimes, normal usecase:
|
# No overlapping schedule datetimes, normal usecase:
|
||||||
item1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30)
|
item1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30)
|
||||||
item1 = self.create_schedule(item1_starts)
|
item1 = self.create_schedule(item1_starts)
|
||||||
self.assertEqual(item1.get_cueout(), self.cue_out)
|
self.assertEqual(item1.get_cue_out(), self.cue_out)
|
||||||
self.assertEqual(item1.get_ends(), item1_starts + self.length)
|
self.assertEqual(item1.get_ends_at(), item1_starts + self.length)
|
||||||
|
|
||||||
# Mixed overlapping schedule datetimes (only ends is overlapping):
|
# Mixed overlapping schedule datetimes (only ends is overlapping):
|
||||||
item_2_starts = datetime(year=2021, month=10, day=2, hour=1, minute=55)
|
item_2_starts = datetime(year=2021, month=10, day=2, hour=1, minute=55)
|
||||||
item_2 = self.create_schedule(item_2_starts)
|
item_2 = self.create_schedule(item_2_starts)
|
||||||
self.assertEqual(item_2.get_cueout(), timedelta(minutes=5))
|
self.assertEqual(item_2.get_cue_out(), timedelta(minutes=5))
|
||||||
self.assertEqual(item_2.get_ends(), self.show_instance.ends)
|
self.assertEqual(item_2.get_ends_at(), self.show_instance.ends_at)
|
||||||
|
|
||||||
# Fully overlapping schedule datetimes (starts and ends are overlapping):
|
# Fully overlapping schedule datetimes (starts and ends are overlapping):
|
||||||
item3_starts = datetime(year=2021, month=10, day=2, hour=2, minute=1)
|
item3_starts = datetime(year=2021, month=10, day=2, hour=2, minute=1)
|
||||||
item3 = self.create_schedule(item3_starts)
|
item3 = self.create_schedule(item3_starts)
|
||||||
self.assertEqual(item3.get_cueout(), self.cue_out)
|
self.assertEqual(item3.get_cue_out(), self.cue_out)
|
||||||
self.assertEqual(item3.get_ends(), self.show_instance.ends)
|
self.assertEqual(item3.get_ends_at(), self.show_instance.ends_at)
|
||||||
|
|
||||||
def test_is_valid(self):
|
def test_overbooked(self):
|
||||||
# Starts before the schedule ends
|
# Starts before the schedule ends
|
||||||
item1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30)
|
item1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30)
|
||||||
item1 = self.create_schedule(item1_starts)
|
item1 = self.create_schedule(item1_starts)
|
||||||
self.assertTrue(item1.is_valid)
|
self.assertFalse(item1.overbooked)
|
||||||
|
|
||||||
# Starts after the schedule ends
|
# Starts after the schedule ends
|
||||||
item_2_starts = datetime(year=2021, month=10, day=2, hour=3)
|
item_2_starts = datetime(year=2021, month=10, day=2, hour=3)
|
||||||
item_2 = self.create_schedule(item_2_starts)
|
item_2 = self.create_schedule(item_2_starts)
|
||||||
self.assertFalse(item_2.is_valid)
|
self.assertTrue(item_2.overbooked)
|
||||||
|
|
|
@ -25,13 +25,13 @@ class TestScheduleViewSet(APITestCase):
|
||||||
)
|
)
|
||||||
show = baker.make(
|
show = baker.make(
|
||||||
"schedule.ShowInstance",
|
"schedule.ShowInstance",
|
||||||
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
starts_at=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
||||||
ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
|
ends_at=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
|
||||||
)
|
)
|
||||||
schedule_item = baker.make(
|
schedule_item = baker.make(
|
||||||
"schedule.Schedule",
|
"schedule.Schedule",
|
||||||
starts=datetime.now(tz=timezone.utc),
|
starts_at=datetime.now(tz=timezone.utc),
|
||||||
ends=datetime.now(tz=timezone.utc) + file.length,
|
ends_at=datetime.now(tz=timezone.utc) + file.length,
|
||||||
cue_out=file.cue_out,
|
cue_out=file.cue_out,
|
||||||
instance=show,
|
instance=show,
|
||||||
file=file,
|
file=file,
|
||||||
|
@ -41,7 +41,7 @@ class TestScheduleViewSet(APITestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
dateparse.parse_datetime(result[0]["ends"]), schedule_item.ends
|
dateparse.parse_datetime(result[0]["ends_at"]), schedule_item.ends_at
|
||||||
)
|
)
|
||||||
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), file.cue_out)
|
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), file.cue_out)
|
||||||
|
|
||||||
|
@ -56,13 +56,13 @@ class TestScheduleViewSet(APITestCase):
|
||||||
)
|
)
|
||||||
show = baker.make(
|
show = baker.make(
|
||||||
"schedule.ShowInstance",
|
"schedule.ShowInstance",
|
||||||
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
starts_at=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
||||||
ends=datetime.now(tz=timezone.utc) + timedelta(seconds=20),
|
ends_at=datetime.now(tz=timezone.utc) + timedelta(seconds=20),
|
||||||
)
|
)
|
||||||
schedule_item = baker.make(
|
schedule_item = baker.make(
|
||||||
"schedule.Schedule",
|
"schedule.Schedule",
|
||||||
starts=datetime.now(tz=timezone.utc),
|
starts_at=datetime.now(tz=timezone.utc),
|
||||||
ends=datetime.now(tz=timezone.utc) + file.length,
|
ends_at=datetime.now(tz=timezone.utc) + file.length,
|
||||||
instance=show,
|
instance=show,
|
||||||
file=file,
|
file=file,
|
||||||
)
|
)
|
||||||
|
@ -70,11 +70,11 @@ class TestScheduleViewSet(APITestCase):
|
||||||
response = self.client.get(self.path)
|
response = self.client.get(self.path)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
self.assertEqual(dateparse.parse_datetime(result[0]["ends"]), show.ends)
|
self.assertEqual(dateparse.parse_datetime(result[0]["ends_at"]), show.ends_at)
|
||||||
expected = show.ends - schedule_item.starts
|
expected = show.ends_at - schedule_item.starts_at
|
||||||
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), expected)
|
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), expected)
|
||||||
self.assertNotEqual(
|
self.assertNotEqual(
|
||||||
dateparse.parse_datetime(result[0]["ends"]), schedule_item.ends
|
dateparse.parse_datetime(result[0]["ends_at"]), schedule_item.ends_at
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_schedule_item_invalid(self):
|
def test_schedule_item_invalid(self):
|
||||||
|
@ -88,33 +88,33 @@ class TestScheduleViewSet(APITestCase):
|
||||||
)
|
)
|
||||||
show = baker.make(
|
show = baker.make(
|
||||||
"schedule.ShowInstance",
|
"schedule.ShowInstance",
|
||||||
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
starts_at=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
||||||
ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
|
ends_at=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
|
||||||
)
|
)
|
||||||
schedule_item = baker.make(
|
schedule_item = baker.make(
|
||||||
"schedule.Schedule",
|
"schedule.Schedule",
|
||||||
starts=datetime.now(tz=timezone.utc),
|
starts_at=datetime.now(tz=timezone.utc),
|
||||||
ends=datetime.now(tz=timezone.utc) + file.length,
|
ends_at=datetime.now(tz=timezone.utc) + file.length,
|
||||||
cue_out=file.cue_out,
|
cue_out=file.cue_out,
|
||||||
instance=show,
|
instance=show,
|
||||||
file=file,
|
file=file,
|
||||||
)
|
)
|
||||||
invalid_schedule_item = baker.make( # pylint: disable=unused-variable
|
invalid_schedule_item = baker.make( # pylint: disable=unused-variable
|
||||||
"schedule.Schedule",
|
"schedule.Schedule",
|
||||||
starts=show.ends + timedelta(minutes=1),
|
starts_at=show.ends_at + timedelta(minutes=1),
|
||||||
ends=show.ends + timedelta(minutes=1) + file.length,
|
ends_at=show.ends_at + timedelta(minutes=1) + file.length,
|
||||||
cue_out=file.cue_out,
|
cue_out=file.cue_out,
|
||||||
instance=show,
|
instance=show,
|
||||||
file=file,
|
file=file,
|
||||||
)
|
)
|
||||||
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
|
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
|
||||||
response = self.client.get(self.path, {"is_valid": True})
|
response = self.client.get(self.path, {"overbooked": False})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
# The invalid item should be filtered out and not returned
|
# The invalid item should be filtered out and not returned
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
dateparse.parse_datetime(result[0]["ends"]), schedule_item.ends
|
dateparse.parse_datetime(result[0]["ends_at"]), schedule_item.ends_at
|
||||||
)
|
)
|
||||||
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), file.cue_out)
|
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), file.cue_out)
|
||||||
|
|
||||||
|
@ -131,21 +131,21 @@ class TestScheduleViewSet(APITestCase):
|
||||||
|
|
||||||
show = baker.make(
|
show = baker.make(
|
||||||
"schedule.ShowInstance",
|
"schedule.ShowInstance",
|
||||||
starts=filter_point - timedelta(minutes=5),
|
starts_at=filter_point - timedelta(minutes=5),
|
||||||
ends=filter_point + timedelta(minutes=5),
|
ends_at=filter_point + timedelta(minutes=5),
|
||||||
)
|
)
|
||||||
schedule_item = baker.make(
|
schedule_item = baker.make(
|
||||||
"schedule.Schedule",
|
"schedule.Schedule",
|
||||||
starts=filter_point,
|
starts_at=filter_point,
|
||||||
ends=filter_point + file.length,
|
ends_at=filter_point + file.length,
|
||||||
cue_out=file.cue_out,
|
cue_out=file.cue_out,
|
||||||
instance=show,
|
instance=show,
|
||||||
file=file,
|
file=file,
|
||||||
)
|
)
|
||||||
previous_item = baker.make( # pylint: disable=unused-variable
|
previous_item = baker.make( # pylint: disable=unused-variable
|
||||||
"schedule.Schedule",
|
"schedule.Schedule",
|
||||||
starts=filter_point - timedelta(minutes=5),
|
starts_at=filter_point - timedelta(minutes=5),
|
||||||
ends=filter_point - timedelta(minutes=5) + file.length,
|
ends_at=filter_point - timedelta(minutes=5) + file.length,
|
||||||
cue_out=file.cue_out,
|
cue_out=file.cue_out,
|
||||||
instance=show,
|
instance=show,
|
||||||
file=file,
|
file=file,
|
||||||
|
@ -156,12 +156,13 @@ class TestScheduleViewSet(APITestCase):
|
||||||
)
|
)
|
||||||
range_end = (filter_point + timedelta(minutes=1)).isoformat(timespec="seconds")
|
range_end = (filter_point + timedelta(minutes=1)).isoformat(timespec="seconds")
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
self.path, {"starts__range": f"{range_start},{range_end}"}
|
self.path,
|
||||||
|
{"starts_after": range_start, "starts_before": range_end},
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
# The previous_item should be filtered out and not returned
|
# The previous_item should be filtered out and not returned
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
dateparse.parse_datetime(result[0]["starts"]), schedule_item.starts
|
dateparse.parse_datetime(result[0]["starts_at"]), schedule_item.starts_at
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,42 +1,33 @@
|
||||||
from django.db.models import F
|
from django.db import models
|
||||||
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
|
from django_filters import rest_framework as filters
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
|
||||||
from ..._constants import FILTER_NUMERICAL_LOOKUPS
|
|
||||||
from ..models import Schedule
|
from ..models import Schedule
|
||||||
from ..serializers import ScheduleSerializer
|
from ..serializers import ScheduleSerializer
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
class ScheduleFilter(filters.FilterSet):
|
||||||
list=extend_schema(
|
starts = filters.DateTimeFromToRangeFilter(field_name="starts_at")
|
||||||
parameters=[
|
ends = filters.DateTimeFromToRangeFilter(field_name="ends_at")
|
||||||
OpenApiParameter(
|
position_status = filters.NumberFilter()
|
||||||
name="is_valid",
|
broadcasted = filters.NumberFilter()
|
||||||
description="Filter on valid instances",
|
|
||||||
required=False,
|
overbooked = filters.BooleanFilter(method="overbooked_filter")
|
||||||
type=bool,
|
|
||||||
),
|
# pylint: disable=unused-argument
|
||||||
]
|
def overbooked_filter(self, queryset, name, value):
|
||||||
)
|
# TODO: deduplicate code using the overbooked property
|
||||||
)
|
if value:
|
||||||
|
return queryset.filter(starts_at__gte=models.F("instance__ends_at"))
|
||||||
|
return queryset.filter(starts_at__lt=models.F("instance__ends_at"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Schedule
|
||||||
|
fields = [] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class ScheduleViewSet(viewsets.ModelViewSet):
|
class ScheduleViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Schedule.objects.all()
|
queryset = Schedule.objects.all()
|
||||||
serializer_class = ScheduleSerializer
|
serializer_class = ScheduleSerializer
|
||||||
filterset_fields = {
|
filterset_class = ScheduleFilter
|
||||||
"starts": FILTER_NUMERICAL_LOOKUPS,
|
|
||||||
"ends": FILTER_NUMERICAL_LOOKUPS,
|
|
||||||
"playout_status": FILTER_NUMERICAL_LOOKUPS,
|
|
||||||
"broadcasted": FILTER_NUMERICAL_LOOKUPS,
|
|
||||||
}
|
|
||||||
model_permission_name = "schedule"
|
model_permission_name = "schedule"
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
filter_valid = self.request.query_params.get("is_valid")
|
|
||||||
if filter_valid is None:
|
|
||||||
return self.queryset.all()
|
|
||||||
|
|
||||||
filter_valid = filter_valid.strip().lower() in ("true", "yes", "1")
|
|
||||||
if filter_valid:
|
|
||||||
return self.queryset.filter(starts__lt=F("instance__ends"))
|
|
||||||
|
|
||||||
return self.queryset.filter(starts__gte=F("instance__ends"))
|
|
||||||
|
|
214
api/schema.yml
214
api/schema.yml
|
@ -2540,134 +2540,33 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
- in: query
|
- in: query
|
||||||
name: broadcasted__gt
|
name: ends_after
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: broadcasted__gte
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: broadcasted__lt
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: broadcasted__lte
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: broadcasted__range
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: integer
|
|
||||||
description: Multiple values may be separated by commas.
|
|
||||||
explode: false
|
|
||||||
style: form
|
|
||||||
- in: query
|
|
||||||
name: ends
|
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
- in: query
|
- in: query
|
||||||
name: ends__gt
|
name: ends_before
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
- in: query
|
- in: query
|
||||||
name: ends__gte
|
name: overbooked
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
- in: query
|
|
||||||
name: ends__lt
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
- in: query
|
|
||||||
name: ends__lte
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
- in: query
|
|
||||||
name: ends__range
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
description: Multiple values may be separated by commas.
|
|
||||||
explode: false
|
|
||||||
style: form
|
|
||||||
- in: query
|
|
||||||
name: is_valid
|
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Filter on valid instances
|
|
||||||
- in: query
|
- in: query
|
||||||
name: playout_status
|
name: position_status
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
- in: query
|
- in: query
|
||||||
name: playout_status__gt
|
name: starts_after
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: playout_status__gte
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: playout_status__lt
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: playout_status__lte
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: playout_status__range
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: integer
|
|
||||||
description: Multiple values may be separated by commas.
|
|
||||||
explode: false
|
|
||||||
style: form
|
|
||||||
- in: query
|
|
||||||
name: starts
|
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
- in: query
|
- in: query
|
||||||
name: starts__gt
|
name: starts_before
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
- in: query
|
|
||||||
name: starts__gte
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
- in: query
|
|
||||||
name: starts__lt
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
- in: query
|
|
||||||
name: starts__lte
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
- in: query
|
|
||||||
name: starts__range
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
description: Multiple values may be separated by commas.
|
|
||||||
explode: false
|
|
||||||
style: form
|
|
||||||
tags:
|
tags:
|
||||||
- schedule
|
- schedule
|
||||||
security:
|
security:
|
||||||
|
@ -6553,13 +6452,19 @@ components:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
readOnly: true
|
readOnly: true
|
||||||
starts:
|
starts_at:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
ends:
|
ends_at:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
instance:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
instance_id:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
file:
|
file:
|
||||||
type: string
|
type: string
|
||||||
format: uri
|
format: uri
|
||||||
|
@ -6574,7 +6479,7 @@ components:
|
||||||
stream_id:
|
stream_id:
|
||||||
type: integer
|
type: integer
|
||||||
readOnly: true
|
readOnly: true
|
||||||
clip_length:
|
length:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
fade_in:
|
fade_in:
|
||||||
|
@ -6590,27 +6495,25 @@ components:
|
||||||
cue_out:
|
cue_out:
|
||||||
type: string
|
type: string
|
||||||
readOnly: true
|
readOnly: true
|
||||||
media_item_played:
|
|
||||||
type: boolean
|
|
||||||
nullable: true
|
|
||||||
instance:
|
|
||||||
type: string
|
|
||||||
format: uri
|
|
||||||
instance_id:
|
|
||||||
type: integer
|
|
||||||
readOnly: true
|
|
||||||
playout_status:
|
|
||||||
type: integer
|
|
||||||
maximum: 32767
|
|
||||||
minimum: -32768
|
|
||||||
broadcasted:
|
|
||||||
type: integer
|
|
||||||
maximum: 32767
|
|
||||||
minimum: -32768
|
|
||||||
position:
|
position:
|
||||||
type: integer
|
type: integer
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
|
position_status:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/PositionStatusEnum"
|
||||||
|
minimum: -32768
|
||||||
|
maximum: 32767
|
||||||
|
broadcasted:
|
||||||
|
type: integer
|
||||||
|
maximum: 32767
|
||||||
|
minimum: -32768
|
||||||
|
played:
|
||||||
|
type: boolean
|
||||||
|
nullable: true
|
||||||
|
overbooked:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
PatchedServiceRegister:
|
PatchedServiceRegister:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -7381,6 +7284,13 @@ components:
|
||||||
- item_url
|
- item_url
|
||||||
- podcast
|
- podcast
|
||||||
- published_at
|
- published_at
|
||||||
|
PositionStatusEnum:
|
||||||
|
enum:
|
||||||
|
- -1
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
type: integer
|
||||||
Preference:
|
Preference:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -7433,13 +7343,19 @@ components:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
readOnly: true
|
readOnly: true
|
||||||
starts:
|
starts_at:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
ends:
|
ends_at:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
instance:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
instance_id:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
file:
|
file:
|
||||||
type: string
|
type: string
|
||||||
format: uri
|
format: uri
|
||||||
|
@ -7454,7 +7370,7 @@ components:
|
||||||
stream_id:
|
stream_id:
|
||||||
type: integer
|
type: integer
|
||||||
readOnly: true
|
readOnly: true
|
||||||
clip_length:
|
length:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
fade_in:
|
fade_in:
|
||||||
|
@ -7470,40 +7386,38 @@ components:
|
||||||
cue_out:
|
cue_out:
|
||||||
type: string
|
type: string
|
||||||
readOnly: true
|
readOnly: true
|
||||||
media_item_played:
|
|
||||||
type: boolean
|
|
||||||
nullable: true
|
|
||||||
instance:
|
|
||||||
type: string
|
|
||||||
format: uri
|
|
||||||
instance_id:
|
|
||||||
type: integer
|
|
||||||
readOnly: true
|
|
||||||
playout_status:
|
|
||||||
type: integer
|
|
||||||
maximum: 32767
|
|
||||||
minimum: -32768
|
|
||||||
broadcasted:
|
|
||||||
type: integer
|
|
||||||
maximum: 32767
|
|
||||||
minimum: -32768
|
|
||||||
position:
|
position:
|
||||||
type: integer
|
type: integer
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
|
position_status:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/PositionStatusEnum"
|
||||||
|
minimum: -32768
|
||||||
|
maximum: 32767
|
||||||
|
broadcasted:
|
||||||
|
type: integer
|
||||||
|
maximum: 32767
|
||||||
|
minimum: -32768
|
||||||
|
played:
|
||||||
|
type: boolean
|
||||||
|
nullable: true
|
||||||
|
overbooked:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
- broadcasted
|
- broadcasted
|
||||||
- cue_in
|
- cue_in
|
||||||
- cue_out
|
- cue_out
|
||||||
- ends
|
- ends_at
|
||||||
- file_id
|
- file_id
|
||||||
- id
|
- id
|
||||||
- instance
|
- instance
|
||||||
- instance_id
|
- instance_id
|
||||||
- item_url
|
- item_url
|
||||||
- playout_status
|
- overbooked
|
||||||
- position
|
- position
|
||||||
- starts
|
- starts_at
|
||||||
- stream_id
|
- stream_id
|
||||||
ServiceRegister:
|
ServiceRegister:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
@ -27,16 +27,17 @@ def get_schedule(api_client: ApiClient):
|
||||||
|
|
||||||
schedule = api_client.services.schedule_url(
|
schedule = api_client.services.schedule_url(
|
||||||
params={
|
params={
|
||||||
"ends__range": (f"{current_time_str}Z,{end_time_str}Z"),
|
"ends_after": f"{current_time_str}Z",
|
||||||
"is_valid": True,
|
"ends_before": f"{end_time_str}Z",
|
||||||
"playout_status__gt": 0,
|
"overbooked": False,
|
||||||
|
"position_status__gt": 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
events = {}
|
events = {}
|
||||||
for item in schedule:
|
for item in schedule:
|
||||||
item["starts"] = isoparse(item["starts"])
|
item["starts_at"] = isoparse(item["starts_at"])
|
||||||
item["ends"] = isoparse(item["ends"])
|
item["ends_at"] = isoparse(item["ends_at"])
|
||||||
|
|
||||||
show_instance = api_client.services.show_instance_url(id=item["instance_id"])
|
show_instance = api_client.services.show_instance_url(id=item["instance_id"])
|
||||||
show = api_client.services.show_url(id=show_instance["show_id"])
|
show = api_client.services.show_url(id=show_instance["show_id"])
|
||||||
|
@ -62,8 +63,8 @@ def generate_file_events(
|
||||||
"""
|
"""
|
||||||
events = {}
|
events = {}
|
||||||
|
|
||||||
schedule_start_event_key = datetime_to_event_key(schedule["starts"])
|
schedule_start_event_key = datetime_to_event_key(schedule["starts_at"])
|
||||||
schedule_end_event_key = datetime_to_event_key(schedule["ends"])
|
schedule_end_event_key = datetime_to_event_key(schedule["ends_at"])
|
||||||
|
|
||||||
events[schedule_start_event_key] = {
|
events[schedule_start_event_key] = {
|
||||||
"type": EventKind.FILE,
|
"type": EventKind.FILE,
|
||||||
|
@ -102,15 +103,15 @@ def generate_webstream_events(
|
||||||
"""
|
"""
|
||||||
events = {}
|
events = {}
|
||||||
|
|
||||||
schedule_start_event_key = datetime_to_event_key(schedule["starts"])
|
schedule_start_event_key = datetime_to_event_key(schedule["starts_at"])
|
||||||
schedule_end_event_key = datetime_to_event_key(schedule["ends"])
|
schedule_end_event_key = datetime_to_event_key(schedule["ends_at"])
|
||||||
|
|
||||||
events[schedule_start_event_key] = {
|
events[schedule_start_event_key] = {
|
||||||
"type": EventKind.STREAM_BUFFER_START,
|
"type": EventKind.STREAM_BUFFER_START,
|
||||||
"independent_event": True,
|
"independent_event": True,
|
||||||
"row_id": schedule["id"],
|
"row_id": schedule["id"],
|
||||||
"start": datetime_to_event_key(schedule["starts"] - timedelta(seconds=5)),
|
"start": datetime_to_event_key(schedule["starts_at"] - timedelta(seconds=5)),
|
||||||
"end": datetime_to_event_key(schedule["starts"] - timedelta(seconds=5)),
|
"end": datetime_to_event_key(schedule["starts_at"] - timedelta(seconds=5)),
|
||||||
"uri": webstream["url"],
|
"uri": webstream["url"],
|
||||||
"id": webstream["id"],
|
"id": webstream["id"],
|
||||||
}
|
}
|
||||||
|
@ -127,7 +128,8 @@ def generate_webstream_events(
|
||||||
"show_name": show["name"],
|
"show_name": show["name"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# NOTE: stream_*_end were previously triggerered 1 second before the schedule end.
|
# NOTE: stream_*_end were previously triggered 1 second before
|
||||||
|
# the schedule end.
|
||||||
events[schedule_end_event_key] = {
|
events[schedule_end_event_key] = {
|
||||||
"type": EventKind.STREAM_BUFFER_END,
|
"type": EventKind.STREAM_BUFFER_END,
|
||||||
"independent_event": True,
|
"independent_event": True,
|
||||||
|
|
|
@ -3,92 +3,50 @@ from libretime_playout.schedule import get_schedule
|
||||||
|
|
||||||
class ApiClientServicesMock:
|
class ApiClientServicesMock:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def schedule_url(_post_data=None, params=None, **kwargs):
|
def schedule_url(*args, **kwargs):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"item_url": "http://192.168.10.100:8081/api/v2/schedule/17/",
|
|
||||||
"id": 17,
|
"id": 17,
|
||||||
"starts": "2022-03-04T15:30:00Z",
|
"starts_at": "2022-03-04T15:30:00Z",
|
||||||
"ends": "2022-03-04T15:33:50.674340Z",
|
"ends_at": "2022-03-04T15:33:50.674340Z",
|
||||||
"file": "http://192.168.10.100:8081/api/v2/files/1/",
|
"file": "http://192.168.10.100:8081/api/v2/files/1/",
|
||||||
"file_id": 1,
|
"file_id": 1,
|
||||||
"stream": None,
|
"stream": None,
|
||||||
"clip_length": "00:03:50.674340",
|
|
||||||
"fade_in": "00:00:00.500000",
|
"fade_in": "00:00:00.500000",
|
||||||
"fade_out": "00:00:00.500000",
|
"fade_out": "00:00:00.500000",
|
||||||
"cue_in": "00:00:01.310660",
|
"cue_in": "00:00:01.310660",
|
||||||
"cue_out": "00:03:51.985000",
|
"cue_out": "00:03:51.985000",
|
||||||
"media_item_played": False,
|
|
||||||
"instance": "http://192.168.10.100:8081/api/v2/show-instances/3/",
|
|
||||||
"instance_id": 3,
|
"instance_id": 3,
|
||||||
"playout_status": 1,
|
|
||||||
"broadcasted": 0,
|
|
||||||
"position": 0,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"item_url": "http://192.168.10.100:8081/api/v2/schedule/18/",
|
|
||||||
"id": 18,
|
"id": 18,
|
||||||
"starts": "2022-03-04T15:33:50.674340Z",
|
"starts_at": "2022-03-04T15:33:50.674340Z",
|
||||||
"ends": "2022-03-04T16:03:50.674340Z",
|
"ends_at": "2022-03-04T16:03:50.674340Z",
|
||||||
"file": None,
|
"file": None,
|
||||||
"stream": "http://192.168.10.100:8081/api/v2/webstreams/1/",
|
"stream": "http://192.168.10.100:8081/api/v2/webstreams/1/",
|
||||||
"stream_id": 1,
|
"stream_id": 1,
|
||||||
"clip_length": "00:30:00",
|
|
||||||
"fade_in": "00:00:00.500000",
|
"fade_in": "00:00:00.500000",
|
||||||
"fade_out": "00:00:00.500000",
|
"fade_out": "00:00:00.500000",
|
||||||
"cue_in": "00:00:00",
|
"cue_in": "00:00:00",
|
||||||
"cue_out": "00:30:00",
|
"cue_out": "00:30:00",
|
||||||
"media_item_played": False,
|
|
||||||
"instance": "http://192.168.10.100:8081/api/v2/show-instances/3/",
|
|
||||||
"instance_id": 3,
|
"instance_id": 3,
|
||||||
"playout_status": 1,
|
|
||||||
"broadcasted": 0,
|
|
||||||
"position": 1,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def show_instance_url(_post_data=None, params=None, **kwargs):
|
def show_instance_url(*args, **kwargs):
|
||||||
return {
|
return {
|
||||||
"item_url": "http://192.168.10.100:8081/api/v2/show-instances/3/",
|
|
||||||
"id": 3,
|
|
||||||
"description": "",
|
|
||||||
"starts": "2022-03-04T15:30:00Z",
|
|
||||||
"ends": "2022-03-04T16:30:00Z",
|
|
||||||
"record": 0,
|
|
||||||
"rebroadcast": 0,
|
|
||||||
"time_filled": "00:33:50.674340",
|
|
||||||
"created": "2022-03-04T15:05:36Z",
|
|
||||||
"last_scheduled": "2022-03-04T15:05:46Z",
|
|
||||||
"modified_instance": False,
|
|
||||||
"autoplaylist_built": False,
|
|
||||||
"show": "http://192.168.10.100:8081/api/v2/shows/3/",
|
|
||||||
"show_id": 3,
|
"show_id": 3,
|
||||||
"instance": None,
|
|
||||||
"file": None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def show_url(_post_data=None, params=None, **kwargs):
|
def show_url(*args, **kwargs):
|
||||||
return {
|
return {
|
||||||
"item_url": "http://192.168.10.100:8081/api/v2/shows/3/",
|
|
||||||
"id": 3,
|
|
||||||
"name": "Test",
|
"name": "Test",
|
||||||
"url": "",
|
|
||||||
"genre": "",
|
|
||||||
"description": "",
|
|
||||||
"color": "",
|
|
||||||
"background_color": "",
|
|
||||||
"linked": False,
|
|
||||||
"is_linkable": True,
|
|
||||||
"image_path": "",
|
|
||||||
"has_autoplaylist": False,
|
|
||||||
"autoplaylist_repeat": False,
|
|
||||||
"autoplaylist": None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def file_url(_post_data=None, params=None, **kwargs):
|
def file_url(*args, **kwargs):
|
||||||
return {
|
return {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"url": None,
|
"url": None,
|
||||||
|
@ -100,19 +58,11 @@ class ApiClientServicesMock:
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def webstream_url(_post_data=None, params=None, **kwargs):
|
def webstream_url(*args, **kwargs):
|
||||||
return {
|
return {
|
||||||
"item_url": "http://192.168.10.100:8081/api/v2/webstreams/1/",
|
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "Test",
|
"name": "Test",
|
||||||
"description": "",
|
|
||||||
"url": "http://some-other-radio:8800/main.ogg",
|
"url": "http://some-other-radio:8800/main.ogg",
|
||||||
"length": "00:30:00",
|
|
||||||
"creator_id": 1,
|
|
||||||
"mtime": "2022-03-04T13:11:20Z",
|
|
||||||
"utime": "2022-03-04T13:11:20Z",
|
|
||||||
"lptime": None,
|
|
||||||
"mime": "application/ogg",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue