Skip to content
Snippets Groups Projects
Commit 20908280 authored by Ernesto Rico Schmidt's avatar Ernesto Rico Schmidt
Browse files

Generate virtual timeslots for unscheduled areas

- add `includeVirtual` as GET parameter for `json_playout()`
- refactor `json_playout` to be cleaner

Closes: #120
parent 2d2b0245
No related branches found
No related tags found
No related merge requests found
Pipeline #2910 passed
......@@ -21,6 +21,7 @@
import json
import logging
from datetime import date, datetime, time, timedelta
from itertools import pairwise
from textwrap import dedent
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view
......@@ -77,6 +78,54 @@ from program.utils import (
logger = logging.getLogger(__name__)
def timeslot_entry(*, timeslot: TimeSlot) -> dict:
"""return a timeslot entry as a dict"""
show = timeslot.show
schedule = timeslot.schedule
playlist_id = timeslot.playlist_id
title = show_name = f"{show.name} {_('REP')}" if schedule.is_repetition else show.name
return {
"id": timeslot.id,
"start": timeslot.start.strftime("%Y-%m-%dT%H:%M:%S"),
"end": timeslot.end.strftime("%Y-%m-%dT%H:%M:%S"),
"title": title,
"schedule_id": schedule.id,
"is_repetition": timeslot.is_repetition,
"playlist_id": playlist_id,
"schedule_default_playlist_id": schedule.default_playlist_id,
"show_default_playlist_id": show.default_playlist_id,
"show_id": show.id,
"show_name": show_name,
"show_hosts": ", ".join(show.hosts.values_list("name", flat=True)),
# `Show.type` is a foreign key that can be null
"show_type": show.type.name if show.type_id else "",
"show_categories": ", ".join(show.category.values_list("name", flat=True)),
"show_topics": ", ".join(show.topic.values_list("name", flat=True)),
# TODO: replace `show_musicfocus` with `show_music_focus` when engine is updated
"show_musicfocus": ", ".join(show.music_focus.values_list("name", flat=True)),
"show_languages": ", ".join(show.language.values_list("name", flat=True)),
# TODO: replace `show_fundingcategory` with `show_funding_category` when engine is
# updated
# `Show.funding_category` is a foreign key can be null
"show_fundingcategory": show.funding_category.name if show.funding_category_id else "",
"memo": timeslot.memo,
"className": "danger" if playlist_id is None or playlist_id == 0 else "default",
}
def gap_entry(*, gap_start: datetime, gap_end: datetime) -> dict:
"""return a virtual timeslot to fill the gap in between `gap_start` and `gap_end` as a dict"""
return {
"start": gap_start.strftime("%Y-%m-%dT%H:%M:%S"),
"end": gap_end.strftime("%Y-%m-%dT%H:%M:%S"),
"virtual": True,
}
def json_day_schedule(request, year=None, month=None, day=None):
# datetime.combine returns a timezone naive datetime object
if year is None and month is None and day is None:
......@@ -111,87 +160,65 @@ def json_day_schedule(request, year=None, month=None, day=None):
def json_playout(request):
"""
Called by
- engine (playout) to retrieve timeslots within a given timerange
Expects GET variables 'start' (date) and 'end' (date).
If start not given, it will be today
Return a JSON representation of the scheduled playout.
Expects GET parameters `start` (date), `end` (date), and `includeVirtual` (boolean).
- `start` is today by default.
- `end` is one week after the start date by default.
- `includeVirtual` is false by default.
The schedule will include virtual timeslots to fill unscheduled gaps if requested.
- internal calendar to retrieve all timeslots for a week
Expects GET variable 'start' (date), otherwise start will be today
If end not given, it returns all timeslots of the next 7 days
Called by
- engine (playout) to retrieve timeslots within a given timerange
- internal calendar to retrieve all timeslots for a week
"""
# datetime.combine returns a timezone naive datetime object
if request.GET.get("start") is None:
start = timezone.make_aware(datetime.combine(timezone.now(), time(0, 0)))
schedule_start = timezone.make_aware(datetime.combine(timezone.now(), time(0, 0)))
else:
start = timezone.make_aware(
schedule_start = timezone.make_aware(
datetime.combine(parse_date(request.GET.get("start")), time(0, 0))
)
if request.GET.get("end") is None:
end = start + timedelta(days=7)
schedule_end = schedule_start + timedelta(days=7)
else:
end = timezone.make_aware(
datetime.combine(parse_date(request.GET.get("end")), time(23, 59))
schedule_end = timezone.make_aware(
datetime.combine(parse_date(request.GET.get("end")) + timedelta(days=1), time(0, 0)),
timezone=timezone.get_current_timezone(),
)
include_virtual = request.GET.get("includeVirtual") == "true"
timeslots = (
TimeSlot.objects.get_timerange_timeslots(start, end)
TimeSlot.objects.get_timerange_timeslots(schedule_start, schedule_end)
.select_related("schedule")
.select_related("show")
)
schedule = []
for ts in timeslots:
is_repetition = " " + _("REP") if ts.schedule.is_repetition is True else ""
hosts = ", ".join(ts.show.hosts.values_list("name", flat=True))
categories = ", ".join(ts.show.category.values_list("name", flat=True))
topics = ", ".join(ts.show.topic.values_list("name", flat=True))
music_focus = ", ".join(ts.show.music_focus.values_list("name", flat=True))
languages = ", ".join(ts.show.language.values_list("name", flat=True))
funding_category = (
FundingCategory.objects.get(pk=ts.show.funding_category_id)
if ts.show.funding_category_id
else None
)
first_timeslot = timeslots.first()
type_ = Type.objects.get(pk=ts.show.type_id)
if include_virtual and first_timeslot.start > schedule_start:
schedule.append(gap_entry(gap_start=schedule_start, gap_end=first_timeslot.start))
classname = "default"
for current, upcoming in pairwise(timeslots):
schedule.append(timeslot_entry(timeslot=current))
if ts.playlist_id is None or ts.playlist_id == 0:
classname = "danger"
if include_virtual and current.end != upcoming.start:
schedule.append(gap_entry(gap_start=current.end, gap_end=upcoming.start))
entry = {
"id": ts.id,
"start": ts.start.strftime("%Y-%m-%dT%H:%M:%S"),
"end": ts.end.strftime("%Y-%m-%dT%H:%M:%S"),
"title": ts.show.name + is_repetition, # For JS Calendar
"schedule_id": ts.schedule.id,
"is_repetition": ts.is_repetition,
"playlist_id": ts.playlist_id,
"schedule_default_playlist_id": ts.schedule.default_playlist_id,
"show_default_playlist_id": ts.show.default_playlist_id,
"show_id": ts.show.id,
"show_name": ts.show.name + is_repetition,
"show_hosts": hosts,
"show_type": type_.name,
"show_categories": categories,
"show_topics": topics,
# TODO: replace `show_musicfocus` with `show_music_focus` when engine is updated
"show_musicfocus": music_focus,
"show_languages": languages,
# TODO: replace `show_fundingcategory` with `show_funding_category` when engine is
# updated
"show_fundingcategory": funding_category.name,
"memo": ts.memo,
"className": classname,
}
last_timeslot = timeslots.last()
schedule.append(entry)
# we need to append the last timeslot to the schedule to complete it
schedule.append(timeslot_entry(timeslot=last_timeslot))
if include_virtual and last_timeslot.end < schedule_end:
schedule.append(gap_entry(gap_start=last_timeslot.end, gap_end=schedule_end))
return HttpResponse(
json.dumps(schedule, ensure_ascii=False).encode("utf8"),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment