mirror of
https://github.com/bugsink/bugsink.git
synced 2025-12-20 03:59:53 -06:00
Add Swagger using drf-spectacular
See #146 DRF 3.16 and Django 5.2 are not in drf-spectacular's published list of supported but here's some sources that give reason to believe they are supported _in practice_: * https://github.com/tfranzel/drf-spectacular/issues/1417 * https://github.com/tfranzel/drf-spectacular/issues/1414
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from django.db.backends.signals import connection_created
|
||||
from django.contrib.auth.management.commands.createsuperuser import Command as CreateSuperUserCommand
|
||||
from drf_spectacular.extensions import OpenApiAuthenticationExtension
|
||||
|
||||
|
||||
def set_pragmas(sender, connection, **kwargs):
|
||||
@@ -40,3 +41,16 @@ def _get_input_message(self, field, default=None):
|
||||
|
||||
unpatched_get_input_message = CreateSuperUserCommand._get_input_message
|
||||
CreateSuperUserCommand._get_input_message = _get_input_message
|
||||
|
||||
|
||||
class BearerTokenAuthenticationExtension(OpenApiAuthenticationExtension):
|
||||
# Will be auto-discovered b/c in __init__.py and subclass of OpenApiAuthenticationExtension
|
||||
target_class = 'bugsink.authentication.BearerTokenAuthentication'
|
||||
name = 'BearerAuth'
|
||||
|
||||
def get_security_definition(self, auto_schema):
|
||||
return {
|
||||
'type': 'http',
|
||||
'scheme': 'bearer',
|
||||
'bearerFormat': 'token',
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ INSTALLED_APPS = [
|
||||
'tailwind', # As currently set up, this is also needed in production (templatetags)
|
||||
'admin_auto_filters',
|
||||
'rest_framework',
|
||||
'drf_spectacular',
|
||||
'drf_spectacular_sidecar', # this brings the swagger-ui
|
||||
]
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
@@ -86,6 +88,22 @@ REST_FRAMEWORK = {
|
||||
"DEFAULT_PARSER_CLASSES": [
|
||||
"rest_framework.parsers.JSONParser",
|
||||
],
|
||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||
}
|
||||
|
||||
SPECTACULAR_SETTINGS = {
|
||||
'TITLE': 'Bugsink',
|
||||
'DESCRIPTION': 'Bugsink API Documentation',
|
||||
'VERSION': '1.0.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False, # keep the docs clean and not document the docs endpoint itself.
|
||||
|
||||
"SECURITY": [
|
||||
{"bearerAuth": []}
|
||||
],
|
||||
"ENUM_NAME_OVERRIDES": {
|
||||
"TeamVisibilityEnum": ["joinable", "discoverable", "hidden"],
|
||||
"ProjectVisibilityEnum": ["joinable", "discoverable", "team_members"],
|
||||
},
|
||||
}
|
||||
|
||||
BUGSINK_APPS = [
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.contrib.auth import views as auth_views
|
||||
from django.views.generic import RedirectView, TemplateView
|
||||
|
||||
from rest_framework import routers
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
|
||||
from alerts.views import debug_email as debug_alerts_email
|
||||
from users.views import debug_email as debug_users_email
|
||||
@@ -60,6 +61,8 @@ urlpatterns = [
|
||||
path("users/", include("users.urls")),
|
||||
|
||||
path("api/canonical/0/", include((api_router.urls, "api"), namespace="api")),
|
||||
path("api/canonical/0/schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path("api/canonical/0/schema/swagger-ui/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
|
||||
|
||||
# these are sentry-cli endpoint for uploading; they're unrelated to e.g. the ingestion API.
|
||||
# the /api/0/ is just a hard prefix (for the ingest API, that position indicates the project id, but here it's just
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
|
||||
|
||||
from bugsink.utils import assert_
|
||||
from bugsink.api_pagination import AscDescCursorPagination
|
||||
@@ -38,6 +39,28 @@ class EventViewSet(AtomicRequestMixin, viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
return queryset.filter(issue=query_params["issue"])
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="issue",
|
||||
type=OpenApiTypes.UUID,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=True,
|
||||
description="Filter events by issue UUID (required).",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="order",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=False,
|
||||
enum=["asc", "desc"],
|
||||
description="Sort order of digest_order (default: desc).",
|
||||
),
|
||||
]
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def get_object(self):
|
||||
"""
|
||||
DRF's get_object(), but we intentionally bypass filter_queryset for detail routes to keep PK lookups
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.shortcuts import get_object_or_404
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.pagination import CursorPagination
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
|
||||
|
||||
from bugsink.api_mixins import AtomicRequestMixin
|
||||
|
||||
@@ -69,6 +70,36 @@ class IssueViewSet(AtomicRequestMixin, viewsets.ReadOnlyModelViewSet):
|
||||
def get_queryset(self):
|
||||
return self.queryset
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="project",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=True,
|
||||
description="Filter issues by project id (required).",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="sort",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=False,
|
||||
enum=["digest_order", "last_seen"],
|
||||
description="Sort mode (default: digest_order).",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="order",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=False,
|
||||
enum=["asc", "desc"],
|
||||
description="Sort order (default: asc).",
|
||||
),
|
||||
]
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
if self.action != "list":
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import viewsets
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
|
||||
|
||||
from bugsink.api_pagination import AscDescCursorPagination
|
||||
from bugsink.api_mixins import ExpandViewSetMixin, AtomicRequestMixin
|
||||
@@ -33,6 +34,20 @@ class ProjectViewSet(AtomicRequestMixin, ExpandViewSetMixin, viewsets.ModelViewS
|
||||
http_method_names = ["get", "post", "patch", "head", "options"]
|
||||
pagination_class = ProjectPagination
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="team",
|
||||
type=OpenApiTypes.UUID,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=False,
|
||||
description="Optional filter by team UUID.",
|
||||
),
|
||||
]
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
if self.action != "list":
|
||||
return queryset
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
|
||||
|
||||
from bugsink.api_pagination import AscDescCursorPagination
|
||||
from bugsink.api_mixins import AtomicRequestMixin
|
||||
@@ -28,6 +29,20 @@ class ReleaseViewSet(AtomicRequestMixin, viewsets.ModelViewSet):
|
||||
http_method_names = ["get", "post", "head", "options"]
|
||||
pagination_class = ReleasePagination
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="project",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=True,
|
||||
description="Filter releases by project id (required).",
|
||||
),
|
||||
]
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
if self.action != "list":
|
||||
|
||||
@@ -17,3 +17,4 @@ fastjsonschema==2.21.*
|
||||
verbose_csrf_middleware==1.0.*
|
||||
ecma426>=0.2.0
|
||||
djangorestframework==3.16.*
|
||||
drf-spectacular[sidecar]
|
||||
|
||||
Reference in New Issue
Block a user