chore(api): rename schedule models fields

This commit is contained in:
jo 2022-07-17 22:27:57 +02:00 committed by Kyle Robbertze
parent 8ceb1419a0
commit 57046e2a9d
8 changed files with 224 additions and 334 deletions

View File

@ -2,8 +2,14 @@ from django.db import models
class Schedule(models.Model):
starts = models.DateTimeField()
ends = models.DateTimeField()
starts_at = models.DateTimeField(db_column="starts")
ends_at = models.DateTimeField(db_column="ends")
instance = models.ForeignKey(
"schedule.ShowInstance",
on_delete=models.DO_NOTHING,
)
file = models.ForeignKey(
"storage.File",
on_delete=models.DO_NOTHING,
@ -16,31 +22,60 @@ class Schedule(models.Model):
blank=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_out = models.TimeField(blank=True, null=True)
cue_in = 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)
playout_status = models.SmallIntegerField()
broadcasted = models.SmallIntegerField()
class PositionStatus(models.IntegerChoices):
FILLER = -1, "Filler" # Used to fill a show that already started
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_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):
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
scheduled in. In that case, the cueout should be the end of the show.
Cue out of a specific item can potentially overrun the show that it is
scheduled in. In that case, the cue out 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.
return the stored cue out.
- When the schedule starts before the end of the show instance
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,
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:
return self.instance.ends - self.starts
if (
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
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
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,
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
if self.instance.ends_at < self.ends_at:
return self.instance.ends_at
return self.ends_at
class Meta:
managed = False

View File

@ -7,29 +7,30 @@ class ScheduleSerializer(serializers.HyperlinkedModelSerializer):
file_id = serializers.IntegerField(source="file.id", read_only=True)
stream_id = serializers.IntegerField(source="stream.id", read_only=True)
instance_id = serializers.IntegerField(source="instance.id", read_only=True)
cue_out = serializers.DurationField(source="get_cueout", read_only=True)
ends = serializers.DateTimeField(source="get_ends", read_only=True)
cue_out = serializers.DurationField(source="get_cue_out", read_only=True)
ends_at = serializers.DateTimeField(source="get_ends_at", read_only=True)
class Meta:
model = Schedule
fields = [
"item_url",
"id",
"starts",
"ends",
"starts_at",
"ends_at",
"instance",
"instance_id",
"file",
"file_id",
"stream",
"stream_id",
"clip_length",
"length",
"fade_in",
"fade_out",
"cue_in",
"cue_out",
"media_item_played",
"instance",
"instance_id",
"playout_status",
"broadcasted",
"position",
"position_status",
"broadcasted",
"played",
"overbooked",
]

View File

@ -9,49 +9,49 @@ class TestSchedule(TestCase):
@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),
created_at=datetime(year=2021, month=10, day=1, hour=12),
starts_at=datetime(year=2021, month=10, day=2, hour=1),
ends_at=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):
def create_schedule(self, starts_at):
return Schedule(
starts=starts,
ends=starts + self.length,
starts_at=starts_at,
ends_at=starts_at + self.length,
cue_in=self.cue_in,
cue_out=self.cue_out,
instance=self.show_instance,
)
def test_get_cueout(self):
def test_get_cue_out(self):
# No overlapping schedule datetimes, normal usecase:
item1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30)
item1 = self.create_schedule(item1_starts)
self.assertEqual(item1.get_cueout(), self.cue_out)
self.assertEqual(item1.get_ends(), item1_starts + self.length)
self.assertEqual(item1.get_cue_out(), self.cue_out)
self.assertEqual(item1.get_ends_at(), item1_starts + self.length)
# Mixed overlapping schedule datetimes (only ends is overlapping):
item_2_starts = datetime(year=2021, month=10, day=2, hour=1, minute=55)
item_2 = self.create_schedule(item_2_starts)
self.assertEqual(item_2.get_cueout(), timedelta(minutes=5))
self.assertEqual(item_2.get_ends(), self.show_instance.ends)
self.assertEqual(item_2.get_cue_out(), timedelta(minutes=5))
self.assertEqual(item_2.get_ends_at(), self.show_instance.ends_at)
# Fully overlapping schedule datetimes (starts and ends are overlapping):
item3_starts = datetime(year=2021, month=10, day=2, hour=2, minute=1)
item3 = self.create_schedule(item3_starts)
self.assertEqual(item3.get_cueout(), self.cue_out)
self.assertEqual(item3.get_ends(), self.show_instance.ends)
self.assertEqual(item3.get_cue_out(), self.cue_out)
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
item1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30)
item1 = self.create_schedule(item1_starts)
self.assertTrue(item1.is_valid)
self.assertFalse(item1.overbooked)
# Starts after the schedule ends
item_2_starts = datetime(year=2021, month=10, day=2, hour=3)
item_2 = self.create_schedule(item_2_starts)
self.assertFalse(item_2.is_valid)
self.assertTrue(item_2.overbooked)

View File

@ -25,13 +25,13 @@ class TestScheduleViewSet(APITestCase):
)
show = baker.make(
"schedule.ShowInstance",
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
starts_at=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
ends_at=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
)
schedule_item = baker.make(
"schedule.Schedule",
starts=datetime.now(tz=timezone.utc),
ends=datetime.now(tz=timezone.utc) + file.length,
starts_at=datetime.now(tz=timezone.utc),
ends_at=datetime.now(tz=timezone.utc) + file.length,
cue_out=file.cue_out,
instance=show,
file=file,
@ -41,7 +41,7 @@ class TestScheduleViewSet(APITestCase):
self.assertEqual(response.status_code, 200)
result = response.json()
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)
@ -56,13 +56,13 @@ class TestScheduleViewSet(APITestCase):
)
show = baker.make(
"schedule.ShowInstance",
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
ends=datetime.now(tz=timezone.utc) + timedelta(seconds=20),
starts_at=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
ends_at=datetime.now(tz=timezone.utc) + timedelta(seconds=20),
)
schedule_item = baker.make(
"schedule.Schedule",
starts=datetime.now(tz=timezone.utc),
ends=datetime.now(tz=timezone.utc) + file.length,
starts_at=datetime.now(tz=timezone.utc),
ends_at=datetime.now(tz=timezone.utc) + file.length,
instance=show,
file=file,
)
@ -70,11 +70,11 @@ class TestScheduleViewSet(APITestCase):
response = self.client.get(self.path)
self.assertEqual(response.status_code, 200)
result = response.json()
self.assertEqual(dateparse.parse_datetime(result[0]["ends"]), show.ends)
expected = show.ends - schedule_item.starts
self.assertEqual(dateparse.parse_datetime(result[0]["ends_at"]), show.ends_at)
expected = show.ends_at - schedule_item.starts_at
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), expected)
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):
@ -88,33 +88,33 @@ class TestScheduleViewSet(APITestCase):
)
show = baker.make(
"schedule.ShowInstance",
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
starts_at=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
ends_at=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
)
schedule_item = baker.make(
"schedule.Schedule",
starts=datetime.now(tz=timezone.utc),
ends=datetime.now(tz=timezone.utc) + file.length,
starts_at=datetime.now(tz=timezone.utc),
ends_at=datetime.now(tz=timezone.utc) + file.length,
cue_out=file.cue_out,
instance=show,
file=file,
)
invalid_schedule_item = baker.make( # pylint: disable=unused-variable
"schedule.Schedule",
starts=show.ends + timedelta(minutes=1),
ends=show.ends + timedelta(minutes=1) + file.length,
starts_at=show.ends_at + timedelta(minutes=1),
ends_at=show.ends_at + timedelta(minutes=1) + file.length,
cue_out=file.cue_out,
instance=show,
file=file,
)
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)
result = response.json()
# The invalid item should be filtered out and not returned
self.assertEqual(len(result), 1)
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)
@ -131,21 +131,21 @@ class TestScheduleViewSet(APITestCase):
show = baker.make(
"schedule.ShowInstance",
starts=filter_point - timedelta(minutes=5),
ends=filter_point + timedelta(minutes=5),
starts_at=filter_point - timedelta(minutes=5),
ends_at=filter_point + timedelta(minutes=5),
)
schedule_item = baker.make(
"schedule.Schedule",
starts=filter_point,
ends=filter_point + file.length,
starts_at=filter_point,
ends_at=filter_point + file.length,
cue_out=file.cue_out,
instance=show,
file=file,
)
previous_item = baker.make( # pylint: disable=unused-variable
"schedule.Schedule",
starts=filter_point - timedelta(minutes=5),
ends=filter_point - timedelta(minutes=5) + file.length,
starts_at=filter_point - timedelta(minutes=5),
ends_at=filter_point - timedelta(minutes=5) + file.length,
cue_out=file.cue_out,
instance=show,
file=file,
@ -156,12 +156,13 @@ class TestScheduleViewSet(APITestCase):
)
range_end = (filter_point + timedelta(minutes=1)).isoformat(timespec="seconds")
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)
result = response.json()
# The previous_item should be filtered out and not returned
self.assertEqual(len(result), 1)
self.assertEqual(
dateparse.parse_datetime(result[0]["starts"]), schedule_item.starts
dateparse.parse_datetime(result[0]["starts_at"]), schedule_item.starts_at
)

View File

@ -1,42 +1,33 @@
from django.db.models import F
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
from django.db import models
from django_filters import rest_framework as filters
from rest_framework import viewsets
from ..._constants import FILTER_NUMERICAL_LOOKUPS
from ..models import Schedule
from ..serializers import ScheduleSerializer
@extend_schema_view(
list=extend_schema(
parameters=[
OpenApiParameter(
name="is_valid",
description="Filter on valid instances",
required=False,
type=bool,
),
]
)
)
class ScheduleFilter(filters.FilterSet):
starts = filters.DateTimeFromToRangeFilter(field_name="starts_at")
ends = filters.DateTimeFromToRangeFilter(field_name="ends_at")
position_status = filters.NumberFilter()
broadcasted = filters.NumberFilter()
overbooked = filters.BooleanFilter(method="overbooked_filter")
# 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):
queryset = Schedule.objects.all()
serializer_class = ScheduleSerializer
filterset_fields = {
"starts": FILTER_NUMERICAL_LOOKUPS,
"ends": FILTER_NUMERICAL_LOOKUPS,
"playout_status": FILTER_NUMERICAL_LOOKUPS,
"broadcasted": FILTER_NUMERICAL_LOOKUPS,
}
filterset_class = ScheduleFilter
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"))

View File

@ -2540,134 +2540,33 @@ paths:
schema:
type: integer
- in: query
name: broadcasted__gt
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
name: ends_after
schema:
type: string
format: date-time
- in: query
name: ends__gt
name: ends_before
schema:
type: string
format: date-time
- in: query
name: ends__gte
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
name: overbooked
schema:
type: boolean
description: Filter on valid instances
- in: query
name: playout_status
name: position_status
schema:
type: integer
- in: query
name: playout_status__gt
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
name: starts_after
schema:
type: string
format: date-time
- in: query
name: starts__gt
name: starts_before
schema:
type: string
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:
- schedule
security:
@ -6553,13 +6452,19 @@ components:
id:
type: integer
readOnly: true
starts:
starts_at:
type: string
format: date-time
ends:
ends_at:
type: string
format: date-time
readOnly: true
instance:
type: string
format: uri
instance_id:
type: integer
readOnly: true
file:
type: string
format: uri
@ -6574,7 +6479,7 @@ components:
stream_id:
type: integer
readOnly: true
clip_length:
length:
type: string
nullable: true
fade_in:
@ -6590,27 +6495,25 @@ components:
cue_out:
type: string
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:
type: integer
maximum: 2147483647
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:
type: object
properties:
@ -7381,6 +7284,13 @@ components:
- item_url
- podcast
- published_at
PositionStatusEnum:
enum:
- -1
- 0
- 1
- 2
type: integer
Preference:
type: object
properties:
@ -7433,13 +7343,19 @@ components:
id:
type: integer
readOnly: true
starts:
starts_at:
type: string
format: date-time
ends:
ends_at:
type: string
format: date-time
readOnly: true
instance:
type: string
format: uri
instance_id:
type: integer
readOnly: true
file:
type: string
format: uri
@ -7454,7 +7370,7 @@ components:
stream_id:
type: integer
readOnly: true
clip_length:
length:
type: string
nullable: true
fade_in:
@ -7470,40 +7386,38 @@ components:
cue_out:
type: string
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:
type: integer
maximum: 2147483647
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:
- broadcasted
- cue_in
- cue_out
- ends
- ends_at
- file_id
- id
- instance
- instance_id
- item_url
- playout_status
- overbooked
- position
- starts
- starts_at
- stream_id
ServiceRegister:
type: object

View File

@ -27,16 +27,17 @@ def get_schedule(api_client: ApiClient):
schedule = api_client.services.schedule_url(
params={
"ends__range": (f"{current_time_str}Z,{end_time_str}Z"),
"is_valid": True,
"playout_status__gt": 0,
"ends_after": f"{current_time_str}Z",
"ends_before": f"{end_time_str}Z",
"overbooked": False,
"position_status__gt": 0,
}
)
events = {}
for item in schedule:
item["starts"] = isoparse(item["starts"])
item["ends"] = isoparse(item["ends"])
item["starts_at"] = isoparse(item["starts_at"])
item["ends_at"] = isoparse(item["ends_at"])
show_instance = api_client.services.show_instance_url(id=item["instance_id"])
show = api_client.services.show_url(id=show_instance["show_id"])
@ -62,8 +63,8 @@ def generate_file_events(
"""
events = {}
schedule_start_event_key = datetime_to_event_key(schedule["starts"])
schedule_end_event_key = datetime_to_event_key(schedule["ends"])
schedule_start_event_key = datetime_to_event_key(schedule["starts_at"])
schedule_end_event_key = datetime_to_event_key(schedule["ends_at"])
events[schedule_start_event_key] = {
"type": EventKind.FILE,
@ -102,15 +103,15 @@ def generate_webstream_events(
"""
events = {}
schedule_start_event_key = datetime_to_event_key(schedule["starts"])
schedule_end_event_key = datetime_to_event_key(schedule["ends"])
schedule_start_event_key = datetime_to_event_key(schedule["starts_at"])
schedule_end_event_key = datetime_to_event_key(schedule["ends_at"])
events[schedule_start_event_key] = {
"type": EventKind.STREAM_BUFFER_START,
"independent_event": True,
"row_id": schedule["id"],
"start": datetime_to_event_key(schedule["starts"] - timedelta(seconds=5)),
"end": 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_at"] - timedelta(seconds=5)),
"uri": webstream["url"],
"id": webstream["id"],
}
@ -127,7 +128,8 @@ def generate_webstream_events(
"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] = {
"type": EventKind.STREAM_BUFFER_END,
"independent_event": True,

View File

@ -3,92 +3,50 @@ from libretime_playout.schedule import get_schedule
class ApiClientServicesMock:
@staticmethod
def schedule_url(_post_data=None, params=None, **kwargs):
def schedule_url(*args, **kwargs):
return [
{
"item_url": "http://192.168.10.100:8081/api/v2/schedule/17/",
"id": 17,
"starts": "2022-03-04T15:30:00Z",
"ends": "2022-03-04T15:33:50.674340Z",
"starts_at": "2022-03-04T15:30:00Z",
"ends_at": "2022-03-04T15:33:50.674340Z",
"file": "http://192.168.10.100:8081/api/v2/files/1/",
"file_id": 1,
"stream": None,
"clip_length": "00:03:50.674340",
"fade_in": "00:00:00.500000",
"fade_out": "00:00:00.500000",
"cue_in": "00:00:01.310660",
"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,
"playout_status": 1,
"broadcasted": 0,
"position": 0,
},
{
"item_url": "http://192.168.10.100:8081/api/v2/schedule/18/",
"id": 18,
"starts": "2022-03-04T15:33:50.674340Z",
"ends": "2022-03-04T16:03:50.674340Z",
"starts_at": "2022-03-04T15:33:50.674340Z",
"ends_at": "2022-03-04T16:03:50.674340Z",
"file": None,
"stream": "http://192.168.10.100:8081/api/v2/webstreams/1/",
"stream_id": 1,
"clip_length": "00:30:00",
"fade_in": "00:00:00.500000",
"fade_out": "00:00:00.500000",
"cue_in": "00:00: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,
"playout_status": 1,
"broadcasted": 0,
"position": 1,
},
]
@staticmethod
def show_instance_url(_post_data=None, params=None, **kwargs):
def show_instance_url(*args, **kwargs):
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,
"instance": None,
"file": None,
}
@staticmethod
def show_url(_post_data=None, params=None, **kwargs):
def show_url(*args, **kwargs):
return {
"item_url": "http://192.168.10.100:8081/api/v2/shows/3/",
"id": 3,
"name": "Test",
"url": "",
"genre": "",
"description": "",
"color": "",
"background_color": "",
"linked": False,
"is_linkable": True,
"image_path": "",
"has_autoplaylist": False,
"autoplaylist_repeat": False,
"autoplaylist": None,
}
@staticmethod
def file_url(_post_data=None, params=None, **kwargs):
def file_url(*args, **kwargs):
return {
"id": 1,
"url": None,
@ -100,19 +58,11 @@ class ApiClientServicesMock:
}
@staticmethod
def webstream_url(_post_data=None, params=None, **kwargs):
def webstream_url(*args, **kwargs):
return {
"item_url": "http://192.168.10.100:8081/api/v2/webstreams/1/",
"id": 1,
"name": "Test",
"description": "",
"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",
}