Add openapi spec for API v2 (#1388)
This commit is contained in:
parent
9f1e41e6fa
commit
1274b2d849
|
@ -15,8 +15,8 @@ restarting:
|
|||
|
||||
Connections to the API are proxied through the Apache web server by default.
|
||||
Endpoint exploration and documentation is available from
|
||||
`http://example.com/api/v2/`, where `example.com` is the URL for the LibreTime
|
||||
instance.
|
||||
`http://example.com/api/v2/schema/swagger-ui/`, where `example.com` is the URL
|
||||
for the LibreTime instance.
|
||||
|
||||
### Development
|
||||
|
||||
|
|
|
@ -138,18 +138,21 @@ class ScheduleSerializer(serializers.HyperlinkedModelSerializer):
|
|||
"id",
|
||||
"starts",
|
||||
"ends",
|
||||
"file",
|
||||
"file_id",
|
||||
"stream",
|
||||
"stream_id",
|
||||
"clip_length",
|
||||
"fade_in",
|
||||
"fade_out",
|
||||
"cue_in",
|
||||
"cue_out",
|
||||
"media_item_played",
|
||||
"file",
|
||||
"file_id",
|
||||
"stream",
|
||||
"stream_id",
|
||||
"instance",
|
||||
"instance_id",
|
||||
"playout_status",
|
||||
"broadcasted",
|
||||
"position",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -38,7 +38,8 @@ INSTALLED_APPS = [
|
|||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"rest_framework",
|
||||
"url_filter",
|
||||
"django_filters",
|
||||
"drf_spectacular",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -114,8 +115,9 @@ REST_FRAMEWORK = {
|
|||
"libretimeapi.permissions.IsSystemTokenOrUser",
|
||||
],
|
||||
"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",
|
||||
}
|
||||
|
||||
|
|
|
@ -164,3 +164,56 @@ class TestScheduleViewSet(APITestCase):
|
|||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(dateparse.parse_datetime(result[0]["ends"]), scheduleItem.ends)
|
||||
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
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.urls import include, path
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
from rest_framework import routers
|
||||
|
||||
from .views import *
|
||||
|
@ -46,6 +47,12 @@ router.register("track-types", TrackTypeViewSet)
|
|||
|
||||
urlpatterns = [
|
||||
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-auth/", include("rest_framework.urls", namespace="rest_framework")),
|
||||
]
|
||||
|
|
|
@ -4,7 +4,8 @@ from django.conf import settings
|
|||
from django.db.models import F
|
||||
from django.http import FileResponse
|
||||
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.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
|
@ -12,6 +13,15 @@ from rest_framework.response import Response
|
|||
from .permissions import IsAdminOrOwnUser
|
||||
from .serializers import *
|
||||
|
||||
FILTER_NUMERICAL_LOOKUPS = [
|
||||
"exact",
|
||||
"gt",
|
||||
"lt",
|
||||
"gte",
|
||||
"lte",
|
||||
"range",
|
||||
]
|
||||
|
||||
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
queryset = get_user_model().objects.all()
|
||||
|
@ -139,10 +149,27 @@ class PreferenceViewSet(viewsets.ModelViewSet):
|
|||
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):
|
||||
queryset = Schedule.objects.all()
|
||||
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"
|
||||
|
||||
def get_queryset(self):
|
||||
|
|
|
@ -25,7 +25,8 @@ setup(
|
|||
"coreapi",
|
||||
"django~=3.0",
|
||||
"djangorestframework",
|
||||
"django-url-filter",
|
||||
"django-filter",
|
||||
"drf-spectacular",
|
||||
"markdown",
|
||||
"model_bakery",
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue