From 625e5263e7d59a62995cee22c2bd6c105f2dde9a Mon Sep 17 00:00:00 2001 From: Kroenenn Date: Sun, 26 Apr 2026 13:53:53 -0600 Subject: [PATCH] feat: wire drf-spectacular for auto-generated OpenAPI schema --- backend/api/serializers.py | 2 ++ backend/api/urls.py | 3 +-- backend/api/views.py | 39 ++++++++++++++++++++++--------------- backend/databus/settings.py | 7 +++++++ 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 9ea59e5..c870097 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -200,6 +200,7 @@ class GeoStopSerializer(GeoFeatureModelSerializer): class Meta: model = Stop geo_field = "stop_point" + id_field = None fields = "__all__" @@ -242,6 +243,7 @@ class GeoShapeSerializer(GeoFeatureModelSerializer): class Meta: model = GeoShape geo_field = "geometry" + id_field = None fields = "__all__" diff --git a/backend/api/urls.py b/backend/api/urls.py index e4234e0..906ed8c 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -1,6 +1,5 @@ from django.urls import include, path from rest_framework import routers -from rest_framework.authtoken.views import obtain_auth_token from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView from . import views @@ -42,6 +41,6 @@ path("which-shapes/", views.WhichShapesView.as_view(), name="which_shapes"), path("find-trips/", views.FindTripsView.as_view(), name="find_trips"), path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), - path("docs/schema/", views.get_schema, name="schema"), + path("docs/schema/", SpectacularAPIView.as_view(), name="schema"), path("docs/", views.RedocView.as_view(url_name="schema"), name="api_docs"), ] diff --git a/backend/api/views.py b/backend/api/views.py index 02a5a56..7d5a223 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -1,14 +1,12 @@ -from django.conf import settings -from django.http import FileResponse from django.contrib.auth import authenticate -from rest_framework import viewsets +from rest_framework import viewsets, serializers as drf_serializers from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.authentication import TokenAuthentication from rest_framework.authtoken.models import Token -from django.views.decorators.csrf import csrf_exempt from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.views import SpectacularRedocView +from drf_spectacular.utils import extend_schema, inline_serializer from django.views.decorators.clickjacking import xframe_options_exempt from django.utils.decorators import method_decorator @@ -19,13 +17,6 @@ from datetime import datetime, timedelta -def get_schema(request): - file_path = settings.BASE_DIR / "api" / "realtime.yml" - return FileResponse( - open(file_path, "rb"), as_attachment=True, filename="realtime.yml" - ) - - @method_decorator(xframe_options_exempt, name="dispatch") class RedocView(SpectacularRedocView): pass @@ -102,20 +93,36 @@ class OperatorViewSet(viewsets.ModelViewSet): authentication_classes = [TokenAuthentication] +_run_create_response = inline_serializer( + name="RunCreateResponse", + fields={"id": drf_serializers.IntegerField()}, +) + + class RunViewSet(viewsets.ModelViewSet): queryset = Run.objects.all() serializer_class = RunSerializer authentication_classes = [TokenAuthentication] + @extend_schema( + summary="Start a run", + description="Registers a new run (an instance of a GTFS trip). " + "Triggers register_run, which updates the transit system state in Redis.", + responses={201: _run_create_response}, + ) def create(self, request): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) - return Response( - { - "id": serializer.instance.id, - } - ) + return Response({"id": serializer.instance.id}, status=201) + + @extend_schema( + summary="End a run", + description="Updates run_status to end the run. " + "Triggers end_run, which flushes state from Redis and initiates run lifecycle management.", + ) + def partial_update(self, request, *args, **kwargs): + return super().partial_update(request, *args, **kwargs) class PositionViewSet(viewsets.ModelViewSet): diff --git a/backend/databus/settings.py b/backend/databus/settings.py index 94815cf..af61363 100644 --- a/backend/databus/settings.py +++ b/backend/databus/settings.py @@ -161,6 +161,13 @@ SPECTACULAR_SETTINGS = { "TITLE": "Databús API | bUCR", + "DESCRIPTION": ( + "REST API for the Databús transit data system. " + "Manages runs, vehicle telemetry, GTFS Schedule data, and real-time feed publication." + ), + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": False, + "COMPONENT_SPLIT_REQUEST": True, } # Channels settings