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
parent
bec7469dc2
commit
5e03f7c759
|
@ -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
|
||||
|
|
|
@ -6,3 +6,4 @@ satnogs-db-api-client
|
|||
build
|
||||
docs
|
||||
versioneer.py
|
||||
db/settings.py
|
||||
|
|
|
@ -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
|
|
@ -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'
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
133
db/api/views.py
133
db/api/views.py
|
@ -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:
|
||||
|
|
126
db/settings.py
126
db/settings.py
|
@ -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
|
||||
|
|
17
db/urls.py
17
db/urls.py
|
@ -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),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue