1
0
Fork 0

Epic API (doc) changes for SatNOGS DB

I've decided to change things up in API schema and doc generation.

Work is not quite complete but its enough for testing in dev and feedback.

Major changes:
* Renaming of api.view classes to match ViewSet inheritance (minor annoyance)
* Introduce drf-spectacular for schema generation and doc UI via swagger-ui
* lots of doc changes for the API to provide a good experience with the above.

New schema generation should work seamlessly in gitlab ci, as well as via /api/schema dynamically.

The new swagger ui view is available via /api/schema/docs/

Signed-off-by: Corey Shields <cshields@gmail.com>
spacecruft
Corey Shields 2021-01-09 20:39:09 -05:00
parent bec7469dc2
commit 5e03f7c759
13 changed files with 458 additions and 94 deletions

View File

@ -26,11 +26,10 @@ schema:
script:
- pip install --no-cache-dir --no-deps -r "requirements.txt" --force-reinstall .
- >-
./manage.py generateschema
--title "SatNOGS DB"
--description "SatNOGS DB is a transmitter suggestions and crowd-sourcing app."
--generator_class "db.api.generators.SchemaGenerator"
> satnogs-db-api-client/api-schema.yml
./manage.py spectacular
--file satnogs-db-api-client/api-schema.yml
--validate
--fail-on-warn
artifacts:
expire_in: 1 week
when: always

View File

@ -6,3 +6,4 @@ satnogs-db-api-client
build
docs
versioneer.py
db/settings.py

View File

@ -1,46 +0,0 @@
"""
NOTE this is a patch to add missing functionality from DRF's openapi implementation
and should be revisited periodically as new functionality is implemented upstream.
refer to https://github.com/encode/django-rest-framework/pull/7516
"""
from rest_framework.schemas.openapi import SchemaGenerator as OpenAPISchemaGenerator
class SchemaGenerator(OpenAPISchemaGenerator):
"""
Returns an extended schema that includes some missing fields from the
upstream OpenAPI implementation
"""
def get_schema(self, request=None, public=False):
schema = super().get_schema(request, public)
schema['info']['version'] = '1.0'
# temporarily add servers until the following is fixed
# https://github.com/encode/django-rest-framework/issues/7631
schema['servers'] = [
{
'url': 'http://localhost:8000',
'description': 'local dev'
}, {
'url': 'https://db.satnogs.org',
'description': 'production'
}
]
# temporarily add securitySchemes until implemented upstream
if 'securitySchemes' not in schema['components']:
schema['components']['securitySchemes'] = {
'ApiKeyAuth': {
'type': 'apiKey',
'in': 'header',
'name': 'Authorization'
}
}
# temporarily add default security object at top-level
if 'security' not in schema:
schema['security'] = [{'ApiKeyAuth': []}]
return schema

View File

@ -2,12 +2,31 @@
# pylint: disable=R0201
import h5py
from django.utils.datastructures import MultiValueDictKeyError
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiExample, extend_schema_field, extend_schema_serializer
from rest_framework import serializers
from db.base.models import TRANSMITTER_STATUS, Artifact, DemodData, LatestTleSet, Mode, \
Satellite, Telemetry, Transmitter
@extend_schema_serializer(
examples=[
OpenApiExample(
'Mode Example 1',
summary='Example: list all modes',
description='This is a truncated example response for listing all RF Mode entries',
value=[
{
'id': 49,
'name': 'AFSK'
},
],
response_only=True, # signal that example only applies to responses
),
]
)
class ModeSerializer(serializers.ModelSerializer):
"""SatNOGS DB Mode API Serializer"""
class Meta:
@ -22,6 +41,32 @@ class SatTelemetrySerializer(serializers.ModelSerializer):
fields = ['decoder']
@extend_schema_serializer(
examples=[
OpenApiExample(
'Satellite Example 1',
summary='Example: retrieving ISS',
description='This is an example response for retrieving the ISS entry, NORAD ID 25544',
value={
'norad_cat_id': 25544,
'name': 'ISS',
'names': 'ZARYA',
'image': 'https://db-satnogs.freetls.fastly.net/media/satellites/ISS.jpg',
'status': 'alive',
'decayed': None,
'launched': '1998-11-20T00:00:00Z',
'deployed': '1998-11-20T00:00:00Z',
'website': 'https://www.nasa.gov/mission_pages/station/main/index.html',
'operator': 'None',
'countries': 'RU,US',
'telemetries': [{
'decoder': 'iss'
}]
},
response_only=True, # signal that example only applies to responses
),
]
)
class SatelliteSerializer(serializers.ModelSerializer):
"""SatNOGS DB Satellite API Serializer"""
@ -36,15 +81,50 @@ class SatelliteSerializer(serializers.ModelSerializer):
'website', 'operator', 'countries', 'telemetries'
)
@extend_schema_field(OpenApiTypes.STR)
def get_operator(self, obj):
"""Returns operator text"""
return str(obj.operator)
@extend_schema_field(OpenApiTypes.STR)
def get_countries(self, obj):
"""Returns countires"""
return ','.join(map(str, obj.countries))
@extend_schema_serializer(
examples=[
OpenApiExample(
'Transmitter Example 1',
summary='Example: Transmitter API response',
value={
'uuid': 'eozSf5mKyzNxoascs8V4bV',
'description': 'Mode V/U FM - Voice Repeater',
'alive': True,
'type': 'Transceiver',
'uplink_low': 145990000,
'uplink_high': None,
'uplink_drift': None,
'downlink_low': 437800000,
'downlink_high': None,
'downlink_drift': None,
'mode': 'FM',
'mode_id': 1,
'uplink_mode': 'FM',
'invert': False,
'baud': None,
'norad_cat_id': 25544,
'status': 'active',
'updated': '2020-09-03T13:14:41.552071Z',
'citation': 'https://www.ariss.org/press-releases/september-2-2020',
'service': 'Amateur',
'coordination': '',
'coordination_url': ''
},
response_only=True, # signal that example only applies to responses
),
]
)
class TransmitterSerializer(serializers.ModelSerializer):
"""SatNOGS DB Transmitter API Serializer"""
norad_cat_id = serializers.SerializerMethodField()
@ -64,10 +144,12 @@ class TransmitterSerializer(serializers.ModelSerializer):
)
# Keeping alive field for compatibility issues
@extend_schema_field(OpenApiTypes.BOOL)
def get_alive(self, obj):
"""Returns transmitter status"""
return obj.status == TRANSMITTER_STATUS[0]
@extend_schema_field(OpenApiTypes.INT)
def get_mode_id(self, obj):
"""Returns downlink mode id"""
try:
@ -75,6 +157,7 @@ class TransmitterSerializer(serializers.ModelSerializer):
except AttributeError: # rare chance that this happens in prod
return None
@extend_schema_field(OpenApiTypes.INT)
def get_mode(self, obj):
"""Returns downlink mode name"""
try:
@ -82,6 +165,7 @@ class TransmitterSerializer(serializers.ModelSerializer):
except AttributeError:
return None
@extend_schema_field(OpenApiTypes.INT)
def get_uplink_mode(self, obj):
"""Returns uplink mode name"""
try:
@ -89,6 +173,7 @@ class TransmitterSerializer(serializers.ModelSerializer):
except AttributeError:
return None
@extend_schema_field(OpenApiTypes.INT64)
def get_norad_cat_id(self, obj):
"""Returns Satellite NORAD ID"""
try:
@ -97,6 +182,23 @@ class TransmitterSerializer(serializers.ModelSerializer):
return None
@extend_schema_serializer(
examples=[
OpenApiExample(
'TLE Example 1',
summary='Example: TLE API response',
value={
'tle0': '0 ISS (ZARYA)',
'tle1': '1 25544U 98067A 21009.90234038 .00001675 00000-0 38183-4 0 9997',
'tle2': '2 25544 51.6464 45.6388 0000512 205.3232 213.2158 15.49275327264062',
'tle_source': 'undisclosed',
'norad_cat_id': 25544,
'updated': '2021-01-09T22:46:37.781923+0000'
},
response_only=True, # signal that example only applies to responses
),
]
)
class LatestTleSetSerializer(serializers.ModelSerializer):
"""SatNOGS DB LatestTleSet API Serializer"""
@ -111,31 +213,65 @@ class LatestTleSetSerializer(serializers.ModelSerializer):
model = LatestTleSet
fields = ('tle0', 'tle1', 'tle2', 'tle_source', 'norad_cat_id', 'updated')
@extend_schema_field(OpenApiTypes.INT64)
def get_norad_cat_id(self, obj):
"""Returns Satellite NORAD ID"""
return obj.satellite.norad_cat_id
@extend_schema_field(OpenApiTypes.STR)
def get_tle0(self, obj):
"""Returns TLE line 0"""
return obj.tle0
@extend_schema_field(OpenApiTypes.STR)
def get_tle1(self, obj):
"""Returns TLE line 1"""
return obj.tle1
@extend_schema_field(OpenApiTypes.STR)
def get_tle2(self, obj):
"""Returns TLE line 2"""
return obj.tle2
@extend_schema_field(OpenApiTypes.STR)
def get_tle_source(self, obj):
"""Returns TLE source"""
return obj.tle_source
@extend_schema_field(OpenApiTypes.DATETIME)
def get_updated(self, obj):
"""Returns TLE updated datetime"""
return obj.updated.strftime('%Y-%m-%dT%H:%M:%S.%f%z')
@extend_schema_serializer(
exclude_fields=('app_source', 'observer', 'timestamp'),
examples=[
OpenApiExample(
'Telemetry Example 1',
summary='Example: retrieving a single Telemetry frame',
description='This is an example response for retrieving a single data frame',
value={
'norad_cat_id': 40379,
'transmitter': None,
'app_source': 'network',
'schema': None,
'decoded': 'influxdb',
'frame':
'968870A6A0A66086A240404040E103F0ABCD0000004203F500B475E215EA5FA0040C000B00090001\
0025008E55EE7B64650100000000AE4D07005D660F007673340000C522370067076507FD0C6000270\
0FE0CC50E0D00AD0E0B069007BD0E0E00650D21001400FE0C910054007007690D8700FC0CBA00E407\
43001C0F140077077807D7078E00120F240068076D07DA0A74003D0F2500830780077A0AC401490F9\
60070077207FDFC9F079507950700C03B0015009AFF6900C8FFE0FFA700EBFF3A00F200F3FF02016D\
0A590A0D0AE3099B0C830CB50DA70D9D06CC0043009401B8338B334C20001000000000009F0200000\
3000000FF723D00BEFFFFFFFF2E89B0151C00',
'observer': 'KB9JHU-EM69uf',
'timestamp': '2021-01-05T22:28:09Z'
},
response_only=True, # signal that example only applies to responses
),
]
)
class TelemetrySerializer(serializers.ModelSerializer):
"""SatNOGS DB Telemetry API Serializer"""
norad_cat_id = serializers.SerializerMethodField()
@ -151,10 +287,12 @@ class TelemetrySerializer(serializers.ModelSerializer):
'timestamp'
)
@extend_schema_field(OpenApiTypes.INT64)
def get_norad_cat_id(self, obj):
"""Returns Satellite NORAD ID for this Transmitter"""
return obj.satellite.norad_cat_id
@extend_schema_field(OpenApiTypes.UUID)
def get_transmitter(self, obj):
"""Returns Transmitter UUID"""
try:
@ -162,6 +300,8 @@ class TelemetrySerializer(serializers.ModelSerializer):
except AttributeError:
return ''
# deprecated, needs pulled out - cshields
@extend_schema_field(OpenApiTypes.STR)
def get_schema(self, obj):
"""Returns Transmitter telemetry schema"""
try:
@ -169,10 +309,12 @@ class TelemetrySerializer(serializers.ModelSerializer):
except AttributeError:
return ''
@extend_schema_field(OpenApiTypes.STR)
def get_decoded(self, obj):
"""Returns the payload_decoded field"""
return obj.payload_decoded
@extend_schema_field(OpenApiTypes.STR)
def get_frame(self, obj):
"""Returns the payload frame"""
return obj.display_frame()
@ -188,6 +330,26 @@ class SidsSerializer(serializers.ModelSerializer):
)
@extend_schema_serializer(
examples=[
OpenApiExample(
'View Artifact Example 1',
summary='Example: retrieving a specific artifact',
description='This is an example response when requesting a specific artifact \
previously uploaded to DB',
value={
'id':
1337,
'network_obs_id':
3376466,
'artifact_file':
'http://db-dev.satnogs.org/media/artifacts/bba35b2d-76cc-4a8f-9b8a-4a2ecb09c6df.h5'
},
status_codes=['200'],
response_only=True, # signal that example only applies to responses
),
]
)
class ArtifactSerializer(serializers.ModelSerializer):
"""SatNOGS DB Artifacts API Serializer"""
class Meta:
@ -195,6 +357,21 @@ class ArtifactSerializer(serializers.ModelSerializer):
fields = ('id', 'network_obs_id', 'artifact_file')
@extend_schema_serializer(
examples=[
OpenApiExample(
'New Artifact Example 1',
summary='Example: uploading artifact',
description='This is an example response after successfully uploading an artifact \
file. The ID of the artifact is returned',
value={
'id': 1337,
},
status_codes=['200', '201'],
response_only=True, # signal that example only applies to responses
),
]
)
class NewArtifactSerializer(serializers.ModelSerializer):
"""SatNOGS Network New Artifact API Serializer"""
def validate(self, attrs):
@ -206,7 +383,7 @@ class NewArtifactSerializer(serializers.ModelSerializer):
raise serializers.ValidationError(
'Not a valid SatNOGS Artifact.', code='invalid'
)
except OSError as error:
except (OSError, MultiValueDictKeyError) as error:
raise serializers.ValidationError(
'Not a valid HDF5 file: {}'.format(error), code='invalid'
)

View File

@ -5,11 +5,11 @@ from db.api import views
ROUTER = routers.DefaultRouter()
ROUTER.register(r'artifacts', views.ArtifactView)
ROUTER.register(r'modes', views.ModeView)
ROUTER.register(r'satellites', views.SatelliteView)
ROUTER.register(r'transmitters', views.TransmitterView)
ROUTER.register(r'telemetry', views.TelemetryView)
ROUTER.register(r'tle', views.LatestTleSetView)
ROUTER.register(r'artifacts', views.ArtifactViewSet)
ROUTER.register(r'modes', views.ModeViewSet)
ROUTER.register(r'satellites', views.SatelliteViewSet)
ROUTER.register(r'transmitters', views.TransmitterViewSet)
ROUTER.register(r'telemetry', views.TelemetryViewSet)
ROUTER.register(r'tle', views.LatestTleSetViewSet)
API_URLPATTERNS = ROUTER.urls

View File

@ -1,6 +1,8 @@
"""SatNOGS DB API django rest framework Views"""
from django.core.files.base import ContentFile
from django.db.models import F
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
from rest_framework import mixins, status, viewsets
from rest_framework.parsers import FileUploadParser, FormParser, MultiPartParser
from rest_framework.permissions import IsAuthenticated
@ -16,8 +18,21 @@ from db.base.models import Artifact, DemodData, LatestTleSet, Mode, Satellite, T
from db.base.tasks import update_satellite
class ModeView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
"""View into the transmitter modulation modes in the SatNOGS DB database"""
@extend_schema_view(
retrieve=extend_schema(
description='Retrieve a single RF Mode from SatNOGS DB based on its ID',
),
list=extend_schema(description='Retrieve a complete list of RF Modes from SatNOGS DB', )
)
class ModeViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
"""
Read-only view into the transmitter modulation modes (RF Modes) currently tracked
in the SatNOGS DB database
For more details on individual RF mode types please [see our wiki][moderef].
[moderef]: https://wiki.satnogs.org/Category:RF_Modes
"""
renderer_classes = [
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
]
@ -25,8 +40,47 @@ class ModeView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
serializer_class = serializers.ModeSerializer
class SatelliteView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
"""View into the Satellite entities in the SatNOGS DB database"""
@extend_schema_view(
list=extend_schema(
description='Retrieve a full or filtered list of satellites in SatNOGS DB',
parameters=[
# drf-spectacular does not currently recognize the in_orbit filter as a
# bool, forcing it here. See drf-spectacular#234
OpenApiParameter(
name='in_orbit',
description='Filter by satellites currently in orbit (True) or those that have \
decayed (False)',
required=False,
type=bool
),
OpenApiParameter(
name='norad_cat_id',
description='Select a satellite by its NORAD-assigned identifier'
),
OpenApiParameter(
name='status',
description='Filter satellites by their operational status',
required=False,
type=bool
),
],
),
retrieve=extend_schema(
description='Retrieve details on a single satellite in SatNOGS DB',
parameters=[
OpenApiParameter(
'norad_cat_id',
OpenApiTypes.INT64,
OpenApiParameter.PATH,
description='Select a satellite by its NORAD-assigned identifier'
),
],
)
)
class SatelliteViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
"""
Read-only view into the Satellite entities in the SatNOGS DB database
"""
renderer_classes = [
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
]
@ -36,9 +90,9 @@ class SatelliteView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
lookup_field = 'norad_cat_id'
class TransmitterView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
class TransmitterViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
"""
View into the Transmitter entities in the SatNOGS DB database.
Read-only view into the Transmitter entities in the SatNOGS DB database.
Transmitters are inclusive of Transceivers and Transponders
"""
renderer_classes = [
@ -50,9 +104,10 @@ class TransmitterView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
lookup_field = 'uuid'
class LatestTleSetView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
class LatestTleSetViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
"""
View into the most recent two-line elements (TLE) in the SatNOGS DB database
Read-only view into the most recent two-line elements (TLE) in the SatNOGS DB
database
"""
renderer_classes = [JSONRenderer, BrowsableAPIRenderer]
queryset = LatestTleSet.objects.all().select_related('satellite').exclude(
@ -84,7 +139,29 @@ class LatestTleSetView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
return self.queryset
class TelemetryView( # pylint: disable=R0901
@extend_schema_view(
list=extend_schema(
parameters=[
OpenApiParameter(
name='app_source',
description='The submission source for the telemetry frames: manual (a manual \
upload/entry), network (SatNOGS Network observations), or sids \
(legacy API submission)',
),
OpenApiParameter(
name='observer',
description='(string) name of the observer (submitter) to retrieve telemetry data \
from'
),
OpenApiParameter(
name='satellite',
description='NORAD ID of a satellite to filter telemetry data for'
),
OpenApiParameter(name='transmitter', description='Not currently in use'),
],
),
)
class TelemetryViewSet( # pylint: disable=R0901
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
viewsets.GenericViewSet):
"""
@ -102,11 +179,12 @@ class TelemetryView( # pylint: disable=R0901
parser_classes = (FormParser, FileUploadParser)
pagination_class = pagination.LinkedHeaderPageNumberPagination
@extend_schema(
responses={'201': None}, # None
)
def create(self, request, *args, **kwargs):
"""
Creates an frame of telemetry data from a satellite observation. See
https://www.pe0sat.vgnet.nl/download/Hidden/Dombrovski-SIDS-Simple-Downlink-Share-Convention.pdf
for a description of the original protocol.
Creates an frame of telemetry data from a satellite observation.
"""
data = {}
@ -156,11 +234,34 @@ class TelemetryView( # pylint: disable=R0901
return Response(status=status.HTTP_201_CREATED, headers=headers)
class ArtifactView( # pylint: disable=R0901
@extend_schema_view(
list=extend_schema(
parameters=[
OpenApiParameter(
'network_obs_id',
OpenApiTypes.INT64,
required=False,
description='Given a SatNOGS Network observation ID, this will return any \
artifacts files associated with the observation.'
),
],
),
retrieve=extend_schema(
parameters=[
OpenApiParameter(
'id',
OpenApiTypes.URI,
OpenApiParameter.PATH,
description='The ID for the requested artifact entry in DB'
),
],
),
)
class ArtifactViewSet( # pylint: disable=R0901
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
viewsets.GenericViewSet):
"""
Artifacts are objects collected in relation to a satellite observation.
Artifacts are file-formatted objects collected from a satellite observation.
"""
queryset = Artifact.objects.all()
filterset_class = filters.ArtifactViewFilter
@ -176,8 +277,10 @@ class ArtifactView( # pylint: disable=R0901
def create(self, request, *args, **kwargs):
"""
Creates observation artifact
Creates observation artifact from an [HDF5 formatted file][hdf5ref]
* Requires session or key authentication to create an artifact
[hdf5ref]: https://en.wikipedia.org/wiki/Hierarchical_Data_Format
"""
serializer = self.get_serializer(data=request.data)
try:

View File

@ -35,6 +35,7 @@ THIRD_PARTY_APPS = (
'bootstrap_modal_forms',
'rest_framework',
'rest_framework.authtoken',
'drf_spectacular',
'django_countries',
'django_filters',
'fontawesome_5',
@ -278,7 +279,127 @@ REST_FRAMEWORK = {
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication'
],
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend', )
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend', ),
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
# path prefix is used for tagging the discovered operations.
# use '/api/v[0-9]' for tagging apis like '/api/v1/albums' with ['albums']
'SCHEMA_PATH_PREFIX': r'/api',
'DEFAULT_GENERATOR_CLASS': 'drf_spectacular.generators.SchemaGenerator',
# Configuration for serving the schema with SpectacularAPIView
'SERVE_URLCONF': None,
# complete public schema or a subset based on the requesting user
'SERVE_PUBLIC': True,
# is the
'SERVE_INCLUDE_SCHEMA': True,
'SERVE_PERMISSIONS': ['rest_framework.permissions.AllowAny'],
# available SwaggerUI configuration parameters
# https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
'SWAGGER_UI_SETTINGS': {
'deepLinking': True,
'persistAuthorization': True,
'displayOperationId': True,
},
# available SwaggerUI versions: https://github.com/swagger-api/swagger-ui/releases
'SWAGGER_UI_DIST': STATIC_URL + 'lib/swagger-ui-dist',
'SWAGGER_UI_FAVICON_HREF': STATIC_URL + 'favicon.ico',
'TITLE': 'SatNOGS DB',
'DESCRIPTION': 'SatNOGS DB is a crowdsourced database of details about orbital \
satellites and data collected from them.',
'TOS': None,
# Optional: MAY contain "name", "url", "email"
'CONTACT': {
'name': 'SatNOGS Developer Chat',
'url': 'https://riot.im/app/#/room/#satnogs-dev:matrix.org'
},
# Optional: MUST contain "name", MAY contain URL
'LICENSE': {
'name': 'AGPL 3.0',
'url': 'https://www.gnu.org/licenses/agpl-3.0.html'
},
'VERSION': '1.1',
# Optional list of servers.
# Each entry MUST contain "url", MAY contain "description", "variables"
'SERVERS': [
{
'url': 'https://db-dev.satnogs.org',
'description': 'Development server'
},
{
'url': 'https://db.satnogs.org',
'description': 'Production server'
}
],
# Postprocessing functions that run at the end of schema generation.
# must satisfy interface result = hook(generator, request, public, result)
'POSTPROCESSING_HOOKS': [
'drf_spectacular.hooks.postprocess_schema_enums'
],
# Function that returns a mocked request for view processing. For CLI usage
# original_request will be None.
# interface: request = build_mock_request(method, path, view, original_request, **kwargs)
'GET_MOCK_REQUEST': 'drf_spectacular.plumbing.build_mock_request',
# Tags defined in the global scope
'TAGS': [
{
'name': 'artifacts',
'description': 'IN DEVELOPMENT (BETA): Artifacts are file-formatted objects \
collected from a satellite observation.'
},
{
'name': 'modes',
'description': 'Radio Frequency modulation modes (RF Modes) currently \
tracked in the SatNOGS DB database',
'externalDocs': {
'description': 'RF Modes in SatNOGS Wiki',
'url': 'https://wiki.satnogs.org/Category:RF_Modes',
}
},
{
'name': 'satellites',
'description': 'Human-made orbital objects, typically with radio frequency \
transmitters and/or reveivers'
},
{
'name': 'telemetry',
'description': 'Telemetry objects in the SatNOGS DB database are frames of \
data collected from downlinked observations.'
},
{
'name': 'tle',
'description': 'The most recent two-line elements (TLE) in the SatNOGS DB database',
'externalDocs': {
'description': 'TLE Wikipedia doc',
'url': 'https://en.wikipedia.org/wiki/Two-line_element_set',
}
},
{
'name': 'transmitters',
'description': 'Radio Frequency (RF) transmitter entities in the SatNOGS DB \
database. Transmitters in this case are inclusive of Transceivers \
and Transponders'
},
],
# Optional: MUST contain 'url', may contain "description"
'EXTERNAL_DOCS': {
'url': 'https://wiki.satnogs.org',
'description': 'SatNOGS Wiki'
},
'COMPONENT_SPLIT_REQUEST': True
}
# Security
@ -397,3 +518,6 @@ if ENVIRONMENT == 'dev':
for backend in TEMPLATES:
del backend['OPTIONS']['loaders']
backend['APP_DIRS'] = True
# for h5 artifact uploads
DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880

View File

@ -5,9 +5,8 @@ from django.conf.urls import include
from django.contrib import admin
from django.urls import path
from django.views.static import serve
from rest_framework.schemas import get_schema_view
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerSplitView
from db.api.generators import SchemaGenerator
from db.api.urls import API_URLPATTERNS
from db.base.urls import BASE_URLPATTERNS
@ -22,16 +21,10 @@ urlpatterns = [
path('api/', include(API_URLPATTERNS)),
# API Schema
path(
'api-schema',
get_schema_view(
title='SatNOGS DB',
description='SatNOGS DB is a transmitter suggestions and crowd-sourcing app.',
version='1.0',
generator_class=SchemaGenerator
),
name='api-schema'
),
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
# Swagger UI view of our schema. Note the use of SpectacularSwaggerSplitView
# is to avoid CSP issues without having to open up unsafe-inline.
path('api/schema/docs/', SpectacularSwaggerSplitView.as_view(url_name='schema'), name='docs'),
# Admin
path('admin/', admin.site.urls),

View File

@ -23,4 +23,4 @@ This Python client is available in `PyPI <https://pypi.org/project/satnogs-db-ap
API Reference
-------------
|api_reference_url|_ contains a full reference of the API.
`Our live schema docs <https://db.satnogs.org/api/schema/docs/>`_ contain a full interactive reference of the API.

5
package-lock.json generated
View File

@ -6909,6 +6909,11 @@
"pdfkit": ">=0.8.1"
}
},
"swagger-ui-dist": {
"version": "3.39.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.39.0.tgz",
"integrity": "sha512-mNCdhxMvYH0E96ebDX5LL3Yj8zMqC/HFAN5YDjwYxuetEewZ6onBrBBSJsWcl6vCxbEbtS2qBiy9OtBY+YyndQ=="
},
"sweetalert2": {
"version": "9.17.2",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-9.17.2.tgz",

View File

@ -20,7 +20,8 @@
"d3": "^6.3.0",
"flot": "^4.2.1",
"gpredict.js": "github:kerel-fs/gpredict.js",
"mapbox-gl": "^2.0.0"
"mapbox-gl": "^2.0.0",
"swagger-ui-dist": "^3.39.0"
},
"assets": [
"admin-lte/**/*",
@ -28,6 +29,8 @@
"flot/dist/**/*",
"gpredict.js/dist/gpredict.min.js",
"mapbox-gl/dist/mapbox-gl.css",
"mapbox-gl/dist/mapbox-gl.js"
"mapbox-gl/dist/mapbox-gl.js",
"swagger-ui-dist/swagger-ui.css",
"swagger-ui-dist/swagger-ui-bundle.js"
]
}

View File

@ -15,7 +15,7 @@ chardet==3.0.4
cryptography==3.3.1
defusedxml==0.7.0rc1
dj-database-url==0.5.0
Django==3.1.4
Django==3.1.5
django-allauth==0.44.0
django-appconf==1.0.4
django-avatar==5.0.0
@ -33,6 +33,7 @@ django-shortuuidfield==0.1.3
django-widget-tweaks==1.4.8
djangorestframework==3.12.2
dnspython==1.16.0
drf-spectacular==0.12.0
ecdsa==0.14.1
enum34==1.1.10
eventlet==0.29.1
@ -42,21 +43,24 @@ gunicorn==19.9.0
h5py==3.1.0
idna==2.10
importlib-metadata==1.7.0
inflection==0.5.1
influxdb==5.3.1
jsonschema==3.2.0
kaitaistruct==0.9
kombu==4.6.11
Logbook==1.5.3
lxml==4.6.2
Markdown==3.3.3
msgpack==1.0.2
mysqlclient==2.0.2
numpy==1.19.4
mysqlclient==2.0.3
numpy==1.19.5
oauthlib==3.1.0
Pillow==8.0.1
Pillow==8.1.0
pyasn1==0.4.8
pycparser==2.20
PyJWT==2.0.0
PyLD==2.0.3
pyrsistent==0.17.3
python-dateutil==2.8.1
python-decouple==3.3
python-dotenv==0.15.0
@ -81,7 +85,7 @@ simplejson==3.17.2
six==1.15.0
social-auth-app-django==4.0.0
social-auth-core==3.3.3
spacetrack==0.15.0
spacetrack==0.16.0
sqlparse==0.4.1
Unipath==1.1
uritemplate==3.0.1

View File

@ -53,6 +53,7 @@ install_requires =
django_compressor~=2.4.0
# API
djangorestframework~=3.12.0
drf-spectacular~=0.12.0
Markdown~=3.3.0
django-filter~=2.4.0
# Astronomy