From 6773f0f6d5556310b9dc40fe0720caa041134087 Mon Sep 17 00:00:00 2001 From: Konrad Mohrfeldt <konrad.mohrfeldt@farbdev.org> Date: Fri, 12 Jul 2024 01:45:14 +0200 Subject: [PATCH] refactor: remove old program/playout endpoints --- program/filters.py | 11 --- program/serializers.py | 51 ------------ program/services.py | 170 +--------------------------------------- program/views.py | 171 +---------------------------------------- steering/urls.py | 9 --- 5 files changed, 6 insertions(+), 406 deletions(-) diff --git a/program/filters.py b/program/filters.py index e54eac15..55ec829e 100644 --- a/program/filters.py +++ b/program/filters.py @@ -280,14 +280,3 @@ class ActiveFilterSet(StaticFilterHelpTextMixin, filters.FilterSet): fields = [ "is_active", ] - - -class VirtualTimeslotFilterSet(filters.FilterSet): - include_virtual = filters.BooleanFilter( - field_name="is_virtual", - help_text="Include virtual timeslot entries (default: false).", - ) - - class Meta: - model = models.TimeSlot - fields = ["include_virtual"] diff --git a/program/serializers.py b/program/serializers.py index 015b01b7..9f052dd0 100644 --- a/program/serializers.py +++ b/program/serializers.py @@ -54,9 +54,6 @@ from program.models import ( from program.typing import ( Logo, MicroProgram, - NestedEpisode, - NestedSchedule, - NestedShow, ProgramFallback, RadioCBASettings, RadioImageRequirementsSettings, @@ -1286,51 +1283,3 @@ class RadioSettingsSerializer(serializers.ModelSerializer): logo=logo, website=obj.station_website, ) - - -# done this way to get the schema annotations for datetime right -class NestedTimeslotSerializer(serializers.Serializer): - end = serializers.DateTimeField() - id = serializers.IntegerField(allow_null=True) - is_virtual = serializers.BooleanField() - memo = serializers.CharField() - playlist_id = serializers.IntegerField(allow_null=True) - repetition_of_id = serializers.IntegerField(allow_null=True) - start = serializers.DateTimeField() - - -class PlayoutEntrySerializer(serializers.Serializer): - episode = serializers.SerializerMethodField() - schedule = serializers.SerializerMethodField(allow_null=True) - show = serializers.SerializerMethodField() - timeslot = NestedTimeslotSerializer() - - @staticmethod - def get_episode(obj) -> NestedEpisode: - pass - - @staticmethod - def get_schedule(obj) -> NestedSchedule: - pass - - @staticmethod - def get_show(obj) -> NestedShow: - pass - - -class ProgramEntrySerializer(serializers.Serializer): - episode = serializers.SerializerMethodField() - show = serializers.SerializerMethodField() - timeslot = NestedTimeslotSerializer() - - @staticmethod - def get_episode(obj) -> NestedEpisode: - pass - - @staticmethod - def get_schedule(obj) -> NestedSchedule: - pass - - @staticmethod - def get_show(obj) -> NestedShow: - pass diff --git a/program/services.py b/program/services.py index fd1e6406..7af891b9 100644 --- a/program/services.py +++ b/program/services.py @@ -19,38 +19,19 @@ import copy from datetime import datetime, time, timedelta -from itertools import pairwise from dateutil.relativedelta import relativedelta from dateutil.rrule import rrule -from rest_framework.exceptions import NotFound, ValidationError +from rest_framework.exceptions import ValidationError from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q, QuerySet from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from program.models import ( - Note, - RadioSettings, - RRule, - Schedule, - ScheduleConflictError, - Show, - TimeSlot, -) +from program.models import Note, RRule, Schedule, ScheduleConflictError, Show, TimeSlot from program.serializers import ScheduleSerializer, TimeSlotSerializer -from program.typing import ( - Conflicts, - DayScheduleEntry, - NestedEpisode, - NestedSchedule, - NestedShow, - NestedTimeslot, - ScheduleCreateUpdateData, - ScheduleData, - TimerangeEntry, -) +from program.typing import Conflicts, ScheduleCreateUpdateData, ScheduleData from program.utils import parse_date, parse_datetime, parse_time @@ -706,148 +687,3 @@ def generate_conflicts(timeslots: list[TimeSlot]) -> Conflicts: conflicts["playlists"] = {} return conflicts - - -def make_day_schedule_entry(*, timerange_entry: TimerangeEntry) -> DayScheduleEntry: - """returns a day schedule entry for the given timerange entry.""" - - episode = timerange_entry["episode"] - timeslot = timerange_entry["timeslot"] - show = timerange_entry["show"] - - return DayScheduleEntry( - episode=NestedEpisode( - id=episode.get("id"), - title=episode["title"], - ), - timeslot=NestedTimeslot( - end=timeslot["end"], - id=timeslot.get("id"), - is_virtual=bool(timeslot["is_virtual"]), - memo=timeslot.get("memo"), - playlist_id=timeslot.get("playlist_id"), - repetition_of_id=timeslot.get("repetition_of_id"), - start=timeslot["start"], - ), - show=NestedShow( - default_playlist_id=show["default_playlist_id"], - id=show["id"], - name=show["name"], - ), - ) - - -def make_timerange_entry(*, timeslot: TimeSlot) -> TimerangeEntry: - """returns a timerange entry for the given timeslot.""" - - episode = timeslot.note - schedule = timeslot.schedule - show = timeslot.schedule.show - - return TimerangeEntry( - episode=NestedEpisode( - id=episode.id, - title=episode.title, - ), - schedule=NestedSchedule( - default_playlist_id=schedule.default_playlist_id, - id=schedule.id, - ), - show=NestedShow( - default_playlist_id=show.default_playlist_id, - id=show.id, - name=show.name, - ), - timeslot=NestedTimeslot( - end=timeslot.end.strftime("%Y-%m-%dT%H:%M:%S"), - id=timeslot.id, - is_virtual=False, - memo=timeslot.memo, - playlist_id=timeslot.playlist_id, - repetition_of_id=timeslot.repetition_of.id if timeslot.repetition_of_id else None, - start=timeslot.start.strftime("%Y-%m-%dT%H:%M:%S"), - ), - ) - - -def make_virtual_timerange_entry(*, gap_start: datetime, gap_end: datetime) -> TimerangeEntry: - """returns a timerange entry to fill the gap between `gap_start` and `gap_end`.""" - - if radio_settings := RadioSettings.objects.first(): - fallback_show = radio_settings.fallback_show - return TimerangeEntry( - episode=NestedEpisode( - id=None, - title=radio_settings.pools[radio_settings.fallback_default_pool], - ), - schedule=None, - show=NestedShow( - default_playlist_id=fallback_show.default_playlist_id if fallback_show else None, - id=fallback_show.id if fallback_show else None, - name=fallback_show.name if fallback_show else "", - ), - timeslot=NestedTimeslot( - end=gap_end.strftime("%Y-%m-%dT%H:%M:%S"), - id=None, - is_virtual=True, - memo="", - playlist_id=None, - repetition_of_id=None, - start=gap_start.strftime("%Y-%m-%dT%H:%M:%S"), - ), - ) - else: - raise NotFound( - detail=_("Radio settings with fallbacks not found."), code="radio_settings-not_found" - ) - - -def get_timerange_timeslot_entries( - timerange_start: datetime, timerange_end: datetime, include_virtual: bool = False -) -> list[TimerangeEntry]: - """Gets list of timerange entries between the given `timerange_start` and `timerange_end`. - - Include virtual timerange entries if requested.""" - - timeslots = TimeSlot.objects.filter( - # start before `timerange_start` and end after `timerange_start` - Q(start__lt=timerange_start, end__gt=timerange_start) - # start after/at `timerange_start`, end before/at `timerange_end` - | Q(start__gte=timerange_start, end__lte=timerange_end) - # start before `timerange_end`, end after/at `timerange_end` - | Q(start__lt=timerange_end, end__gte=timerange_end) - ).select_related("schedule") - - if not include_virtual: - return [make_timerange_entry(timeslot=timeslot) for timeslot in timeslots] - - if not timeslots: - return [] - - timeslot_entries = [] - # gap before the first timeslot - first_timeslot = timeslots.first() - if first_timeslot.start > timerange_start: - timeslot_entries.append( - make_virtual_timerange_entry(gap_start=timerange_start, gap_end=first_timeslot.start) - ) - - for index, (current, upcoming) in enumerate(pairwise(timeslots)): - timeslot_entries.append(make_timerange_entry(timeslot=current)) - - # gap between the timeslots - if current.end != upcoming.start: - timeslot_entries.append( - make_virtual_timerange_entry(gap_start=current.end, gap_end=upcoming.start) - ) - else: - timeslot_entries.append(make_timerange_entry(timeslot=first_timeslot)) - - # gap after the last timeslot - last_timeslot = timeslots.last() - if last_timeslot.end < timerange_end: - timeslot_entries.append( - make_virtual_timerange_entry(gap_start=last_timeslot.end, gap_end=timerange_end) - ) - - return timeslot_entries diff --git a/program/views.py b/program/views.py index 6b447cec..d4f37165 100644 --- a/program/views.py +++ b/program/views.py @@ -19,11 +19,10 @@ # import logging -from datetime import date, datetime, time, timedelta +from datetime import date, datetime from textwrap import dedent from django_filters.rest_framework import DjangoFilterBackend -from djangorestframework_camel_case.util import camelize from drf_spectacular.utils import ( OpenApiExample, OpenApiParameter, @@ -75,9 +74,7 @@ from program.serializers import ( LinkTypeSerializer, MusicFocusSerializer, NoteSerializer, - PlayoutEntrySerializer, ProfileSerializer, - ProgramEntrySerializer, RadioSettingsSerializer, RRuleSerializer, ScheduleConflictResponseSerializer, @@ -91,174 +88,12 @@ from program.serializers import ( TypeSerializer, UserSerializer, ) -from program.services import ( - get_timerange_timeslot_entries, - make_day_schedule_entry, - resolve_conflicts, -) -from program.utils import get_values, parse_date +from program.services import resolve_conflicts +from program.utils import get_values logger = logging.getLogger(__name__) -@extend_schema_view( - list=extend_schema( - summary="List schedule for a specific date.", - description=( - "Returns a list of the schedule for a specific date." - "Expects parameters `year` (int), `month` (int), and `day` (int) as url components." - "e.g. /program/2024/01/31/" - ), - examples=[ - OpenApiExample( - "Full entry", - response_only=True, - value={ - "episode": {"id": 2, "title": ""}, - "show": {"defaultPlaylistId": None, "id": 2, "name": "EINS"}, - "timeslot": { - "end": "2024-07-03T16:30:00", - "id": 2, - "isVirtual": False, - "memo": "", - "playlistId": None, - "repetitionOfId": None, - "start": "2024-07-03T14:00:00", - }, - }, - ), - OpenApiExample( - "Virtual entry", - response_only=True, - value={ - "episode": {"id": None, "title": "Station Fallback Pool"}, - "show": {"defaultPlaylistId": None, "id": 1, "name": "Musikpool"}, - "timeslot": { - "end": "2024-07-04T00:00:00", - "id": None, - "isVirtual": True, - "memo": "", - "playlistId": None, - "repetitionOfId": None, - "start": "2024-07-03T22:00:00", - }, - }, - ), - ], - ), -) -class APIProgramViewSet( - mixins.ListModelMixin, - viewsets.GenericViewSet, -): - filterset_class = filters.VirtualTimeslotFilterSet - queryset = TimeSlot.objects.all() - serializer_class = ProgramEntrySerializer - - def list(self, 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: - start = timezone.make_aware(datetime.combine(timezone.now(), time(0, 0))) - else: - start = timezone.make_aware(datetime.combine(date(year, month, day), time(0, 0))) - - end = start + timedelta(hours=24) - - include_virtual = request.GET.get("include_virtual") == "true" - - schedule = [ - make_day_schedule_entry(timerange_entry=timeslot_entry) - for timeslot_entry in get_timerange_timeslot_entries(start, end, include_virtual) - ] - - return JsonResponse(camelize(schedule), safe=False) - - -@extend_schema_view( - list=extend_schema( - summary="List scheduled playout.", - description=( - "Returns a list of the scheduled playout. " - "The schedule will include virtual timeslots to fill unscheduled gaps if requested." - ), - # TODO: move this into the serializers - examples=[ - OpenApiExample( - "Full entry", - response_only=True, - value={ - "episode": {"id": 2, "title": ""}, - "schedule": {"defaultPlaylistId": None, "id": 1}, - "show": {"defaultPlaylistId": None, "id": 2, "name": "EINS"}, - "timeslot": { - "end": "2024-07-03T16:30:00", - "id": 2, - "isVirtual": False, - "memo": "", - "playlistId": None, - "repetitionOfId": None, - "start": "2024-07-03T14:00:00", - }, - }, - ), - OpenApiExample( - "Virtual entry", - response_only=True, - value={ - "episode": {"id": None, "title": "Station Fallback Pool"}, - "schedule": None, - "show": {"defaultPlaylistId": None, "id": 1, "name": "Musikpool"}, - "timeslot": { - "end": "2024-07-10T00:00:00", - "id": None, - "isVirtual": True, - "start": "2024-07-09T22:00:00", - }, - }, - ), - ], - ) -) -class APIPlayoutViewSet( - mixins.ListModelMixin, - viewsets.GenericViewSet, -): - filterset_class = filters.VirtualTimeslotFilterSet - queryset = TimeSlot.objects.all() - serializer_class = PlayoutEntrySerializer - - def list(self, request, *args, **kwargs): - """ - Return a JSON representation of the scheduled playout. - Called by - - engine (playout) to retrieve timeslots within a given timerange - - internal calendar to retrieve all timeslots for a week - """ - - # datetime.now and datetime.combine return timezone naive datetime objects - if request.GET.get("start") is None: - schedule_start = timezone.make_aware(datetime.combine(datetime.now(), time(0, 0))) - else: - schedule_start = timezone.make_aware( - datetime.combine(parse_date(request.GET.get("start")), time(0, 0)) - ) - - if request.GET.get("end") is None: - schedule_end = schedule_start + timedelta(days=7) - else: - schedule_end = timezone.make_aware( - datetime.combine( - parse_date(request.GET.get("end")) + timedelta(days=1), time(0, 0) - ) - ) - - include_virtual = request.GET.get("include_virtual") == "true" - - playout = get_timerange_timeslot_entries(schedule_start, schedule_end, include_virtual) - - return JsonResponse(camelize(playout), safe=False) - - @extend_schema_view( create=extend_schema(summary="Create a new user."), retrieve=extend_schema( diff --git a/steering/urls.py b/steering/urls.py index e807f164..46cca2d4 100644 --- a/steering/urls.py +++ b/steering/urls.py @@ -34,9 +34,7 @@ from program.views import ( APILinkTypeViewSet, APIMusicFocusViewSet, APINoteViewSet, - APIPlayoutViewSet, APIProfileViewSet, - APIProgramViewSet, APIRadioSettingsViewSet, APIRRuleViewSet, APIScheduleViewSet, @@ -67,17 +65,10 @@ router.register(r"link-types", APILinkTypeViewSet, basename="link-type") router.register(r"rrules", APIRRuleViewSet, basename="rrule") router.register(r"images", APIImageViewSet, basename="image") router.register(r"settings", APIRadioSettingsViewSet, basename="settings") -router.register(r"playout", APIPlayoutViewSet, basename="playout") -router.register(r"program/week", APIPlayoutViewSet, basename="program/week") urlpatterns = [ path("openid/", include("oidc_provider.urls", namespace="oidc_provider")), path("api/v1/", include(router.urls)), - path( - "api/v1/program/<int:year>/<int:month>/<int:day>/", - APIProgramViewSet.as_view({"get": "list"}), - name="program", - ), path("api/v1/schema/", SpectacularAPIView.as_view(), name="schema"), path( "api/v1/schema/swagger-ui/", -- GitLab