Skip to content
Snippets Groups Projects
views.py 30.23 KiB
#
# steering, Programme/schedule management for AURA
#
# Copyright (C) 2011-2017, 2020, Ernesto Rico Schmidt
# Copyright (C) 2017-2019, Ingo Leindecker
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import json
import logging
from datetime import date, datetime, time

from rest_framework import mixins, permissions, status, viewsets
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.response import Response

from django.contrib.auth.models import User
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.translation import gettext as _
from program import filters
from program.models import (
    Category,
    FundingCategory,
    Host,
    Language,
    MusicFocus,
    Note,
    Schedule,
    Show,
    TimeSlot,
    Topic,
    Type,
)
from program.serializers import (
    CategorySerializer,
    FundingCategorySerializer,
    HostSerializer,
    LanguageSerializer,
    MusicFocusSerializer,
    NoteSerializer,
    ScheduleSerializer,
    ShowSerializer,
    TimeSlotSerializer,
    TopicSerializer,
    TypeSerializer,
    UserSerializer,
)
from program.utils import get_pk_and_slug, get_values, parse_date

logger = logging.getLogger(__name__)


def json_day_schedule(request, year=None, month=None, day=None):
    if year is None and month is None and day is None:
        today = timezone.make_aware(datetime.combine(timezone.now(), time(0, 0)))
    else:
        today = timezone.make_aware(
            datetime.combine(date(year, month, day), time(0, 0))
        )

    timeslots = (
        TimeSlot.objects.get_24h_timeslots(today)
        .select_related("schedule")
        .select_related("show")
    )
    schedule = []
    for ts in timeslots:
        entry = {
            "start": ts.start.strftime("%Y-%m-%d_%H:%M:%S"),
            "end": ts.end.strftime("%Y-%m-%d_%H:%M:%S"),
            "title": ts.show.name,
            "id": ts.show.id,
        }

        schedule.append(entry)

    return HttpResponse(
        json.dumps(schedule, ensure_ascii=False).encode("utf8"),
        content_type="application/json; charset=utf-8",
    )


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

       - 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
    """

    if request.GET.get("start") is None:
        start = timezone.make_aware(datetime.combine(timezone.now(), time(0, 0)))
    else:
        start = timezone.make_aware(
            datetime.combine(parse_date(request.GET.get("start")), time(0, 0))
        )

    if request.GET.get("end") is None:
        # If no end was given, return the next week
        timeslots = (
            TimeSlot.objects.get_7d_timeslots(start)
            .select_related("schedule")
            .select_related("show")
        )
    else:
        # Otherwise return the given timerange
        end = timezone.make_aware(
            datetime.combine(parse_date(request.GET.get("end")), time(23, 59))
        )
        timeslots = (
            TimeSlot.objects.get_timerange_timeslots(start, 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
        )

        type_ = Type.objects.get(pk=ts.show.type_id)

        classname = "default"

        if ts.playlist_id is None or ts.playlist_id == 0:
            classname = "danger"

        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,
        }

        schedule.append(entry)

    return HttpResponse(
        json.dumps(schedule, ensure_ascii=False).encode("utf8"),
        content_type="application/json; charset=utf-8",
    )


class APIUserViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    mixins.UpdateModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet,
):
    """
    /users returns oneself. Superusers see all users. Only superusers may create a user (GET, POST)
    /users/{pk} retrieves or updates a single user. Non-superusers may only update certain fields
     (GET, PUT)

    Superusers may access and update all users.
    """

    permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]
    serializer_class = UserSerializer
    queryset = User.objects.none()

    def get_queryset(self):
        """Constrain access to oneself except for superusers"""
        if self.request.user.is_superuser:
            return User.objects.all()

        return User.objects.filter(pk=self.request.user.id)

    def retrieve(self, request, *args, **kwargs):
        """Returns a single user"""
        pk = get_values(self.kwargs, "pk")

        # Common users only see themselves
        if not request.user.is_superuser and pk != request.user.id:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        user = get_object_or_404(User, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)

    def create(self, request, *args, **kwargs):
        """
        Create a User
        Only superusers may create a user
        """

        if not request.user.is_superuser:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        serializer = UserSerializer(data=request.data)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def update(self, request, *args, **kwargs):
        pk = get_values(self.kwargs, "pk")

        serializer = UserSerializer(data=request.data)
        # Common users may only edit themselves
        if not request.user.is_superuser and pk != request.user.id:
            return Response(
                serializer.initial_data, status=status.HTTP_401_UNAUTHORIZED
            )

        user = get_object_or_404(User, pk=pk)
        serializer = UserSerializer(
            user, data=request.data, context={"user": request.user}
        )

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class APIShowViewSet(viewsets.ModelViewSet):
    """
    Returns a list of available shows.
    Only superusers may add and delete shows.
    """

    queryset = Show.objects.all()
    serializer_class = ShowSerializer
    permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]
    pagination_class = LimitOffsetPagination
    filterset_class = filters.ShowFilterSet

    def create(self, request, *args, **kwargs):
        """
        Create a show.

        Only superusers may create a show.
        """

        if not request.user.is_superuser:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        serializer = ShowSerializer(data=request.data)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def retrieve(self, request, *args, **kwargs):
        """Returns a single show"""

        pk, slug = get_pk_and_slug(self.kwargs)

        show = (
            get_object_or_404(Show, pk=pk)
            if pk
            else get_object_or_404(Show, slug=slug)
            if slug
            else None
        )

        serializer = ShowSerializer(show)

        return Response(serializer.data)

    def update(self, request, *args, **kwargs):
        """
        Update a show.

        Common users may only update shows they own.
        """

        pk = get_values(self.kwargs, "pk")

        if not request.user.is_superuser and pk not in request.user.shows.values_list(
            "id", flat=True
        ):
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        show = get_object_or_404(Show, pk=pk)
        serializer = ShowSerializer(
            show, data=request.data, context={"user": request.user}
        )

        if serializer.is_valid():
            # Common users mustn't edit the show's name
            if not request.user.is_superuser:
                serializer.validated_data["name"] = show.name
            serializer.save()
            return Response(serializer.data)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, *args, **kwargs):
        """
        Delete a show.

        Only superusers may delete shows.
        """

        if not request.user.is_superuser:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        pk = get_values(self.kwargs, "pk")

        Show.objects.get(pk=pk).delete()

        return Response(status=status.HTTP_204_NO_CONTENT)


class APIScheduleViewSet(viewsets.ModelViewSet):
    """
    /schedules/ returns all schedules (GET)
    /schedules/{pk} returns the given schedule (GET)
    /shows/{show_pk}/schedules returns schedules of the show (GET, POST)
    /shows/{show_pk}/schedules/{pk} returns schedules by its ID (GET, PUT, DELETE)

    Only superusers may create and update schedules
    """

    queryset = Schedule.objects.none()
    serializer_class = ScheduleSerializer
    permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]

    def get_queryset(self):
        show_pk = get_values(self.kwargs, "show_pk")

        if show_pk:
            return Schedule.objects.filter(show=show_pk)

        return Schedule.objects.all()

    def retrieve(self, request, *args, **kwargs):
        pk, show_pk = get_values(self.kwargs, "pk", "show_pk")

        schedule = (
            get_object_or_404(Schedule, pk=pk, show=show_pk)
            if show_pk
            else get_object_or_404(Schedule, pk=pk)
        )

        serializer = ScheduleSerializer(schedule)

        return Response(serializer.data)

    def create(self, request, *args, **kwargs):
        """
        Create a schedule, generate timeslots, test for collisions and resolve them including notes

        Only superusers may add schedules
        TODO: Perhaps directly insert into database if no conflicts found
        """

        if not request.user.is_superuser:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        pk, show_pk = get_values(self.kwargs, "pk", "show_pk")

        # Only allow creating when calling /shows/{show_pk}/schedules/ and with ehe `schedule` JSON
        # object
        if show_pk is None or "schedule" not in request.data:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        # First create submit -> return projected timeslots and collisions
        if "solutions" not in request.data:
            return Response(
                Schedule.make_conflicts(request.data["schedule"], pk, show_pk),
                status=status.HTTP_409_CONFLICT,
            )

        # Otherwise try to resolve
        resolution = Schedule.resolve_conflicts(request.data, pk, show_pk)

        # If resolution went well
        if "projected" not in resolution:
            return Response(resolution, status=status.HTTP_201_CREATED)

        # Otherwise return conflicts
        return Response(resolution, status=status.HTTP_409_CONFLICT)

    def update(self, request, *args, **kwargs):
        """
        Update a schedule, generate timeslots, test for collisions and resolve them including notes

        Only superusers may update schedules
        """

        if not request.user.is_superuser:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        pk, show_pk = get_values(self.kwargs, "pk", "show_pk")

        # Only allow updating when calling /shows/{show_pk}/schedules/{pk}/ and with the `schedule`
        # JSON object
        if show_pk is None or "schedule" not in request.data:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        # If default playlist id or repetition are given, just update
        if default_playlist_id := request.data.get("schedule").get(
            "default_playlist_id"
        ):
            schedule = get_object_or_404(Schedule, pk=pk, show=show_pk)
            schedule.default_playlist_id = int(default_playlist_id)
            schedule.save()

            serializer = ScheduleSerializer(schedule)
            return Response(serializer.data)

        if is_repetition := request.data.get("schedule").get("is_repetition"):
            schedule = get_object_or_404(Schedule, pk=pk, show=show_pk)
            schedule.is_repetition = bool(is_repetition)
            schedule.save()

            serializer = ScheduleSerializer(schedule)
            return Response(serializer.data)

        # First update submit -> return projected timeslots and collisions
        if "solutions" not in request.data:
            return Response(
                Schedule.make_conflicts(request.data["schedule"], pk, show_pk),
                status=status.HTTP_409_CONFLICT,
            )

        # Otherwise try to resolve
        resolution = Schedule.resolve_conflicts(request.data, pk, show_pk)

        # If resolution went well
        if "projected" not in resolution:
            return Response(resolution)

        # Otherwise return conflicts
        return Response(resolution, status=status.HTTP_409_CONFLICT)

    def destroy(self, request, *args, **kwargs):
        """
        Delete a schedule
        Only superusers may delete schedules
        """

        if not request.user.is_superuser:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        pk, show_pk = get_values(self.kwargs, "pk", "show_pk")

        # Only allow deleting when calling /shows/{show_pk}/schedules/{pk}
        if show_pk is None:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        Schedule.objects.get(pk=pk).delete()

        return Response(status=status.HTTP_204_NO_CONTENT)


# TODO: Create is currently not implemented because timeslots are supposed to be inserted
#       by creating or updating a schedule.
#       There might be a use case for adding a single timeslot without any conflicts though.
class APITimeSlotViewSet(
    mixins.RetrieveModelMixin,
    mixins.UpdateModelMixin,
    mixins.DestroyModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet,
):
    """
    Returns a list of timeslots.

    By default, only timeslots ranging from now + 60 days will be displayed.
    You may override this default overriding start and/or end parameter.

    Timeslots may only be added by creating/updating a schedule.
    """

    permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]
    serializer_class = TimeSlotSerializer
    pagination_class = LimitOffsetPagination
    queryset = TimeSlot.objects.all().order_by("-start")
    filterset_class = filters.TimeSlotFilterSet

    def get_queryset(self):
        queryset = super().get_queryset()

        # subroute filters
        show_pk, schedule_pk = get_values(self.kwargs, "show_pk", "schedule_pk")
        if show_pk:
            queryset = queryset.filter(show=show_pk)
        if schedule_pk:
            queryset = queryset.filter(schedule=schedule_pk)

        return queryset

    def retrieve(self, request, *args, **kwargs):
        pk, show_pk = get_values(self.kwargs, "pk", "show_pk")

        if show_pk:
            timeslot = get_object_or_404(TimeSlot, pk=pk, show=show_pk)
        else:
            timeslot = get_object_or_404(TimeSlot, pk=pk)

        serializer = TimeSlotSerializer(timeslot)
        return Response(serializer.data)

    def update(self, request, *args, **kwargs):
        """Link a playlist_id to a timeslot"""

        pk, show_pk, schedule_pk = get_values(
            self.kwargs, "pk", "show_pk", "schedule_pk"
        )

        if (
            not request.user.is_superuser
            and show_pk not in request.user.shows.values_lis("id", flat=True)
        ):
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        # Update is only allowed when calling /shows/1/schedules/1/timeslots/1 and if user owns the
        # show
        if schedule_pk is None or show_pk is None:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        timeslot = get_object_or_404(
            TimeSlot, pk=pk, schedule=schedule_pk, show=show_pk
        )

        serializer = TimeSlotSerializer(timeslot, data=request.data)
        if serializer.is_valid():
            serializer.save()

            # Return the next repetition
            # We do this because the Dashboard needs to update the repetition timeslot as well
            # but with another playlist containing the recording instead of the original playlist
            ts = TimeSlot.objects.filter(show=show_pk, start__gt=timeslot.start)[0]
            if ts.is_repetition:
                serializer = TimeSlotSerializer(ts)
                return Response(serializer.data)

            # ...or nothing if there isn't one
            return Response(status=status.HTTP_200_OK)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, *args, **kwargs):
        """
        Deletes a timeslot.

        Only superusers may delete timeslots.
        """

        if not request.user.is_superuser:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        pk, show_pk = get_values(self.kwargs, "pk", "show_pk")

        # Only allow when calling endpoint starting with /shows/1/...
        if show_pk is None:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        TimeSlot.objects.get(pk=pk).delete()

        return Response(status=status.HTTP_204_NO_CONTENT)


class APINoteViewSet(viewsets.ModelViewSet):
    """
    /notes/ returns all notes (GET)
    /notes/{pk} returns a single note (if owned) (GET)
    /notes/?ids={...} returns given notes (if owned) (GET)
    /notes/?host={host} returns notes assigned to a given host (GET)
    /notes/?owner={owner} returns notes editable by a given user (GET)
    /notes/?user={user} returns notes created by a given user (GET)
    /shows/{show_pk}/notes returns all notes of a show (GET)
    /shows/{show_pk}/notes/{pk} returns a note by its ID (GET)
    /shows/{show_pk}/timeslots/{timeslot_pk}/note/ returns a note of the timeslot (GET)
    /shows/{show_pk}/timeslots/{timeslot_pk}/note/{pk} returns a note by its ID (GET)
    /shows/{show_pk}/schedules/{schedule_pk}/timeslots/{timeslot_pk}/note returns a note to the
     timeslot (GET, POST).
    /shows/{show_pk}/schedules/{schedule_pk}/timeslots/{timeslot_pk}/note/{pk} returns a note by
     its ID (GET, PUT, DELETE)

    Superusers may access and update all notes
    """

    queryset = Note.objects.none()
    serializer_class = NoteSerializer
    permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]
    pagination_class = LimitOffsetPagination

    def get_queryset(self):
        timeslot_pk, show_pk = get_values(self.kwargs, "timeslot_pk", "show_pk")

        # Endpoints

        #
        #     /shows/1/schedules/1/timeslots/1/note
        #     /shows/1/timeslots/1/note
        #
        #     Return a note to the timeslot
        #
        if show_pk and timeslot_pk:
            notes = Note.objects.filter(show=show_pk, timeslot=timeslot_pk)

        #
        #     /shows/1/notes
        #
        #     Returns notes to the show
        #
        elif show_pk and timeslot_pk is None:
            notes = Note.objects.filter(show=show_pk)

        #
        #     /notes
        #
        #     Returns all notes
        #
        else:
            notes = Note.objects.all()

        # Filters

        if ids := self.request.query_params.get("ids"):
            # Filter notes by their IDs
            note_ids = list(map(int, ids.split(",")))
            notes = notes.filter(id__in=note_ids)

        if host := self.request.query_params.get("host"):
            # Filter notes by host
            notes = notes.filter(host=int(host))

        if owner := self.request.query_params.get("owner"):
            # Filter notes by show owner: all notes the user may edit
            shows = Show.objects.filter(owners=int(owner))
            notes = notes.filter(show__in=shows)

        if user := self.request.query_params.get("user"):
            # Filter notes by their creator
            notes = notes.filter(user=int(user))

        return notes

    def create(self, request, *args, **kwargs):
        """Create a note"""

        show_pk, schedule_pk, timeslot_pk = get_values(
            self.kwargs, "show_pk", "schedule_pk", "timelost_pk"
        )

        if (
            not request.user.is_superuser
            and show_pk not in request.user.shows.values_list("id", flat=True)
        ):
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        # Only create a note if show_id, timeslot_id and schedule_id is given
        if show_pk is None or schedule_pk is None or timeslot_pk is None:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        serializer = NoteSerializer(
            data=request.data, context={"user_id": request.user.id}
        )

        if serializer.is_valid():
            hosts = Host.objects.filter(
                shows__in=request.user.shows.values_list("id", flat=True)
            )
            if not request.user.is_superuser and request.data["host"] not in hosts:
                serializer.validated_data["host"] = None

            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def retrieve(self, request, *args, **kwargs):
        """
        Returns a single note

        Called by:
        /notes/1
        /shows/1/notes/1
        /shows/1/timeslots/1/note/1
        /shows/1/schedules/1/timeslots/1/note/1
        """
        pk, show_pk, schedule_pk, timeslot_pk = get_values(
            self.kwargs, "pk", "show_pk", "schedule_pk", "timeslot_pk"
        )

        #
        #      /shows/1/notes/1
        #
        #      Returns a note to a show
        #
        if show_pk and timeslot_pk is None and schedule_pk is None:
            note = get_object_or_404(Note, pk=pk, show=show_pk)

        #
        #     /shows/1/timeslots/1/note/1
        #     /shows/1/schedules/1/timeslots/1/note/1
        #
        #     Return a note to a timeslot
        #
        elif show_pk and timeslot_pk:
            note = get_object_or_404(Note, pk=pk, show=show_pk, timeslot=timeslot_pk)

        #
        #     /notes/1
        #
        #     Returns the given note
        #
        else:
            note = get_object_or_404(Note, pk=pk)

        serializer = NoteSerializer(note)
        return Response(serializer.data)

    def update(self, request, *args, **kwargs):
        pk, show_pk, schedule_pk, timeslot_pk = get_values(
            self.kwargs, "pk", "show_pk", "schedule_pk", "timeslot_pk"
        )

        if (
            not request.user.is_superuser
            and show_pk not in request.user.shows.values_list("id", flat=True)
        ):
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        # Allow PUT only when calling
        #  /shows/{show_pk}/schedules/{schedule_pk}/timeslots/{timeslot_pk}/note/{pk}
        if show_pk is None or schedule_pk is None or timeslot_pk is None:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        note = get_object_or_404(Note, pk=pk, timeslot=timeslot_pk, show=show_pk)

        serializer = NoteSerializer(note, data=request.data)

        if serializer.is_valid():
            hosts = Host.objects.filter(
                shows__in=request.user.shows.values_list("id", flat=True)
            )
            # Don't assign a host the user mustn't edit. Reassign the original value instead
            if not request.user.is_superuser and int(request.data["host"]) not in hosts:
                serializer.validated_data["host"] = Host.objects.filter(
                    pk=note.host_id
                )[0]

            serializer.save()
            return Response(serializer.data)

        return Response(status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, *args, **kwargs):
        pk, show_pk, schedule_pk, timeslot_pk = get_values(
            self.kwargs, "pk", "show_pk", "schedule_pk", "timeslot_pk"
        )

        if (
            not request.user.is_superuser
            and show_pk not in request.user.shows.values_list("id", flat=True)
        ):
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        if pk is None or show_pk is None or schedule_pk is None or timeslot_pk is None:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        Note.objects.get(pk=pk).delete()

        return Response(status=status.HTTP_204_NO_CONTENT)


class ActiveInactiveViewSet(viewsets.ModelViewSet):
    permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]

    def get_queryset(self: viewsets.ModelViewSet):
        """Filters"""

        if self.request.query_params.get("active") == "true":
            return self.queryset.model.objects.filter(is_active=True)

        if self.request.query_params.get("active") == "false":
            return self.queryset.model.objects.filter(is_active=False)

        return self.queryset.model.objects.all()


class APICategoryViewSet(ActiveInactiveViewSet):
    """
    /categories/ returns all categories (GET, POST)
    /categories/?active=true returns all active categories (GET)
    /categories/?active=false returns all inactive categories (GET)
    /categories/{pk} Returns a category by its ID (GET, PUT, DELETE)
    """

    queryset = Category.objects.all()
    serializer_class = CategorySerializer


class APITypeViewSet(ActiveInactiveViewSet):
    """
    /types/ returns all types (GET, POST)
    /types/?active=true returns all active types (GET)
    /types/?active=false returns all inactive types (GET)
    /types/{pk} returns a type by its ID (GET, PUT, DELETE)
    """

    queryset = Type.objects.all()
    serializer_class = TypeSerializer


class APITopicViewSet(ActiveInactiveViewSet):
    """
    /topics/: Returns all topics (GET, POST)
    /topics/?active=true Returns all active topics (GET)
    /topics/?active=false Returns all inactive topics (GET)
    /topics/{pk}: Returns a topic by its ID (GET, PUT, DELETE)
    """

    queryset = Topic.objects.all()
    serializer_class = TopicSerializer


class APIMusicFocusViewSet(ActiveInactiveViewSet):
    """
    /musicfocus/ returns all music focuses (GET, POST)
    /musicfocus/?active=true: returns all active music focuses (GET)
    /musicfocus/?active=false: returns all inactive music focuses (GET)
    /musicfocus/{pk}: returns a music focus by its ID (GET, PUT, DELETE)
    """

    queryset = MusicFocus.objects.all()
    serializer_class = MusicFocusSerializer


class APIFundingCategoryViewSet(ActiveInactiveViewSet):
    """
    /fundingcategories/: returns all funding categories (GET, POST)
    /fundingcategories/?active=true returns all active funding categories (GET)
    /fundingcategories/?active=false returns all inactive funding categories (GET)
    /fundingcategories/{pk} returns a funding category by its ID (GET, PUT, DELETE)
    """

    queryset = FundingCategory.objects.all()
    serializer_class = FundingCategorySerializer


class APILanguageViewSet(ActiveInactiveViewSet):
    """
    /languages/ returns all languages (GET, POST)
    /languages/?active=true returns all active languages (GET)
    /languages/?active=false returns all inactive languages (GET)
    /languages/{pk} returns a language by its ID (GET, PUT, DELETE)
    """

    queryset = Language.objects.all()
    serializer_class = LanguageSerializer


class APIHostViewSet(ActiveInactiveViewSet):
    """
    /hosts/ returns all hosts (GET, POST)
    /hosts/?active=true returns all active hosts (GET)
    /hosts/?active=false returns all inactive hosts (GET)
    /hosts/{pk} returns a host by its ID (GET, PUT, DELETE)
    """

    queryset = Host.objects.all()
    serializer_class = HostSerializer
    pagination_class = LimitOffsetPagination