Add openapi spec for API v2 (#1388)

This commit is contained in:
Kyle Robbertze 2021-10-16 18:34:03 +00:00 committed by GitHub
parent 9f1e41e6fa
commit 1274b2d849
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 104 additions and 11 deletions

View File

@ -15,8 +15,8 @@ restarting:
Connections to the API are proxied through the Apache web server by default. Connections to the API are proxied through the Apache web server by default.
Endpoint exploration and documentation is available from Endpoint exploration and documentation is available from
`http://example.com/api/v2/`, where `example.com` is the URL for the LibreTime `http://example.com/api/v2/schema/swagger-ui/`, where `example.com` is the URL
instance. for the LibreTime instance.
### Development ### Development

View File

@ -138,18 +138,21 @@ class ScheduleSerializer(serializers.HyperlinkedModelSerializer):
"id", "id",
"starts", "starts",
"ends", "ends",
"file",
"file_id",
"stream",
"stream_id",
"clip_length", "clip_length",
"fade_in", "fade_in",
"fade_out", "fade_out",
"cue_in", "cue_in",
"cue_out", "cue_out",
"media_item_played", "media_item_played",
"file",
"file_id",
"stream",
"stream_id",
"instance", "instance",
"instance_id", "instance_id",
"playout_status",
"broadcasted",
"position",
] ]

View File

@ -38,7 +38,8 @@ INSTALLED_APPS = [
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"rest_framework", "rest_framework",
"url_filter", "django_filters",
"drf_spectacular",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -114,8 +115,9 @@ REST_FRAMEWORK = {
"libretimeapi.permissions.IsSystemTokenOrUser", "libretimeapi.permissions.IsSystemTokenOrUser",
], ],
"DEFAULT_FILTER_BACKENDS": [ "DEFAULT_FILTER_BACKENDS": [
"url_filter.integrations.drf.DjangoFilterBackend", "django_filters.rest_framework.DjangoFilterBackend",
], ],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"URL_FIELD_NAME": "item_url", "URL_FIELD_NAME": "item_url",
} }

View File

@ -164,3 +164,56 @@ class TestScheduleViewSet(APITestCase):
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(dateparse.parse_datetime(result[0]["ends"]), scheduleItem.ends) self.assertEqual(dateparse.parse_datetime(result[0]["ends"]), scheduleItem.ends)
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), f.cueout) self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), f.cueout)
def test_schedule_item_range(self):
music_dir = baker.make(
"libretimeapi.MusicDir",
directory=os.path.join(os.path.dirname(__file__), "resources"),
)
f = baker.make(
"libretimeapi.File",
directory=music_dir,
mime="audio/mp3",
filepath="song.mp3",
length=timedelta(seconds=40.86),
cuein=timedelta(seconds=0),
cueout=timedelta(seconds=40.8131),
)
filter_point = datetime.now(tz=timezone.utc)
show = baker.make(
"libretimeapi.ShowInstance",
starts=filter_point - timedelta(minutes=5),
ends=filter_point + timedelta(minutes=5),
)
schedule_item = baker.make(
"libretimeapi.Schedule",
starts=filter_point,
ends=filter_point + f.length,
cue_out=f.cueout,
instance=show,
file=f,
)
previous_item = baker.make(
"libretimeapi.Schedule",
starts=filter_point - timedelta(minutes=5),
ends=filter_point - timedelta(minutes=5) + f.length,
cue_out=f.cueout,
instance=show,
file=f,
)
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(self.token))
range_start = (filter_point - timedelta(minutes=1)).isoformat(
timespec="seconds"
)
range_end = (filter_point + timedelta(minutes=1)).isoformat(timespec="seconds")
response = self.client.get(
self.path, {"starts__range": "{},{}".format(range_start, 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
)

View File

@ -1,4 +1,5 @@
from django.urls import include, path from django.urls import include, path
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from rest_framework import routers from rest_framework import routers
from .views import * from .views import *
@ -46,6 +47,12 @@ router.register("track-types", TrackTypeViewSet)
urlpatterns = [ urlpatterns = [
path("api/v2/", include(router.urls)), path("api/v2/", include(router.urls)),
path("api/v2/schema/", SpectacularAPIView.as_view(), name="schema"),
path(
"api/v2/schema/swagger-ui/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
path("api/v2/version/", version), path("api/v2/version/", version),
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
] ]

View File

@ -4,7 +4,8 @@ from django.conf import settings
from django.db.models import F from django.db.models import F
from django.http import FileResponse from django.http import FileResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import status, viewsets from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
from rest_framework import fields, status, viewsets
from rest_framework.decorators import action, api_view, permission_classes from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.response import Response from rest_framework.response import Response
@ -12,6 +13,15 @@ from rest_framework.response import Response
from .permissions import IsAdminOrOwnUser from .permissions import IsAdminOrOwnUser
from .serializers import * from .serializers import *
FILTER_NUMERICAL_LOOKUPS = [
"exact",
"gt",
"lt",
"gte",
"lte",
"range",
]
class UserViewSet(viewsets.ModelViewSet): class UserViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all() queryset = get_user_model().objects.all()
@ -139,10 +149,27 @@ class PreferenceViewSet(viewsets.ModelViewSet):
model_permission_name = "preference" model_permission_name = "preference"
@extend_schema_view(
list=extend_schema(
parameters=[
OpenApiParameter(
name="is_valid",
description="Filter on valid instances",
required=False,
type=bool,
),
]
)
)
class ScheduleViewSet(viewsets.ModelViewSet): class ScheduleViewSet(viewsets.ModelViewSet):
queryset = Schedule.objects.all() queryset = Schedule.objects.all()
serializer_class = ScheduleSerializer serializer_class = ScheduleSerializer
filter_fields = ("starts", "ends", "playout_status", "broadcasted") filter_fields = {
"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): def get_queryset(self):

View File

@ -25,7 +25,8 @@ setup(
"coreapi", "coreapi",
"django~=3.0", "django~=3.0",
"djangorestframework", "djangorestframework",
"django-url-filter", "django-filter",
"drf-spectacular",
"markdown", "markdown",
"model_bakery", "model_bakery",
], ],