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:
|
script:
|
||||||
- pip install --no-cache-dir --no-deps -r "requirements.txt" --force-reinstall .
|
- pip install --no-cache-dir --no-deps -r "requirements.txt" --force-reinstall .
|
||||||
- >-
|
- >-
|
||||||
./manage.py generateschema
|
./manage.py spectacular
|
||||||
--title "SatNOGS DB"
|
--file satnogs-db-api-client/api-schema.yml
|
||||||
--description "SatNOGS DB is a transmitter suggestions and crowd-sourcing app."
|
--validate
|
||||||
--generator_class "db.api.generators.SchemaGenerator"
|
--fail-on-warn
|
||||||
> satnogs-db-api-client/api-schema.yml
|
|
||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
when: always
|
when: always
|
||||||
|
|
|
@ -6,3 +6,4 @@ satnogs-db-api-client
|
||||||
build
|
build
|
||||||
docs
|
docs
|
||||||
versioneer.py
|
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
|
# pylint: disable=R0201
|
||||||
|
|
||||||
import h5py
|
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 rest_framework import serializers
|
||||||
|
|
||||||
from db.base.models import TRANSMITTER_STATUS, Artifact, DemodData, LatestTleSet, Mode, \
|
from db.base.models import TRANSMITTER_STATUS, Artifact, DemodData, LatestTleSet, Mode, \
|
||||||
Satellite, Telemetry, Transmitter
|
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):
|
class ModeSerializer(serializers.ModelSerializer):
|
||||||
"""SatNOGS DB Mode API Serializer"""
|
"""SatNOGS DB Mode API Serializer"""
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -22,6 +41,32 @@ class SatTelemetrySerializer(serializers.ModelSerializer):
|
||||||
fields = ['decoder']
|
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):
|
class SatelliteSerializer(serializers.ModelSerializer):
|
||||||
"""SatNOGS DB Satellite API Serializer"""
|
"""SatNOGS DB Satellite API Serializer"""
|
||||||
|
|
||||||
|
@ -36,15 +81,50 @@ class SatelliteSerializer(serializers.ModelSerializer):
|
||||||
'website', 'operator', 'countries', 'telemetries'
|
'website', 'operator', 'countries', 'telemetries'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_operator(self, obj):
|
def get_operator(self, obj):
|
||||||
"""Returns operator text"""
|
"""Returns operator text"""
|
||||||
return str(obj.operator)
|
return str(obj.operator)
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_countries(self, obj):
|
def get_countries(self, obj):
|
||||||
"""Returns countires"""
|
"""Returns countires"""
|
||||||
return ','.join(map(str, obj.countries))
|
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):
|
class TransmitterSerializer(serializers.ModelSerializer):
|
||||||
"""SatNOGS DB Transmitter API Serializer"""
|
"""SatNOGS DB Transmitter API Serializer"""
|
||||||
norad_cat_id = serializers.SerializerMethodField()
|
norad_cat_id = serializers.SerializerMethodField()
|
||||||
|
@ -64,10 +144,12 @@ class TransmitterSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Keeping alive field for compatibility issues
|
# Keeping alive field for compatibility issues
|
||||||
|
@extend_schema_field(OpenApiTypes.BOOL)
|
||||||
def get_alive(self, obj):
|
def get_alive(self, obj):
|
||||||
"""Returns transmitter status"""
|
"""Returns transmitter status"""
|
||||||
return obj.status == TRANSMITTER_STATUS[0]
|
return obj.status == TRANSMITTER_STATUS[0]
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_mode_id(self, obj):
|
def get_mode_id(self, obj):
|
||||||
"""Returns downlink mode id"""
|
"""Returns downlink mode id"""
|
||||||
try:
|
try:
|
||||||
|
@ -75,6 +157,7 @@ class TransmitterSerializer(serializers.ModelSerializer):
|
||||||
except AttributeError: # rare chance that this happens in prod
|
except AttributeError: # rare chance that this happens in prod
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_mode(self, obj):
|
def get_mode(self, obj):
|
||||||
"""Returns downlink mode name"""
|
"""Returns downlink mode name"""
|
||||||
try:
|
try:
|
||||||
|
@ -82,6 +165,7 @@ class TransmitterSerializer(serializers.ModelSerializer):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT)
|
||||||
def get_uplink_mode(self, obj):
|
def get_uplink_mode(self, obj):
|
||||||
"""Returns uplink mode name"""
|
"""Returns uplink mode name"""
|
||||||
try:
|
try:
|
||||||
|
@ -89,6 +173,7 @@ class TransmitterSerializer(serializers.ModelSerializer):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT64)
|
||||||
def get_norad_cat_id(self, obj):
|
def get_norad_cat_id(self, obj):
|
||||||
"""Returns Satellite NORAD ID"""
|
"""Returns Satellite NORAD ID"""
|
||||||
try:
|
try:
|
||||||
|
@ -97,6 +182,23 @@ class TransmitterSerializer(serializers.ModelSerializer):
|
||||||
return None
|
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):
|
class LatestTleSetSerializer(serializers.ModelSerializer):
|
||||||
"""SatNOGS DB LatestTleSet API Serializer"""
|
"""SatNOGS DB LatestTleSet API Serializer"""
|
||||||
|
|
||||||
|
@ -111,31 +213,65 @@ class LatestTleSetSerializer(serializers.ModelSerializer):
|
||||||
model = LatestTleSet
|
model = LatestTleSet
|
||||||
fields = ('tle0', 'tle1', 'tle2', 'tle_source', 'norad_cat_id', 'updated')
|
fields = ('tle0', 'tle1', 'tle2', 'tle_source', 'norad_cat_id', 'updated')
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT64)
|
||||||
def get_norad_cat_id(self, obj):
|
def get_norad_cat_id(self, obj):
|
||||||
"""Returns Satellite NORAD ID"""
|
"""Returns Satellite NORAD ID"""
|
||||||
return obj.satellite.norad_cat_id
|
return obj.satellite.norad_cat_id
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_tle0(self, obj):
|
def get_tle0(self, obj):
|
||||||
"""Returns TLE line 0"""
|
"""Returns TLE line 0"""
|
||||||
return obj.tle0
|
return obj.tle0
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_tle1(self, obj):
|
def get_tle1(self, obj):
|
||||||
"""Returns TLE line 1"""
|
"""Returns TLE line 1"""
|
||||||
return obj.tle1
|
return obj.tle1
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_tle2(self, obj):
|
def get_tle2(self, obj):
|
||||||
"""Returns TLE line 2"""
|
"""Returns TLE line 2"""
|
||||||
return obj.tle2
|
return obj.tle2
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_tle_source(self, obj):
|
def get_tle_source(self, obj):
|
||||||
"""Returns TLE source"""
|
"""Returns TLE source"""
|
||||||
return obj.tle_source
|
return obj.tle_source
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.DATETIME)
|
||||||
def get_updated(self, obj):
|
def get_updated(self, obj):
|
||||||
"""Returns TLE updated datetime"""
|
"""Returns TLE updated datetime"""
|
||||||
return obj.updated.strftime('%Y-%m-%dT%H:%M:%S.%f%z')
|
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):
|
class TelemetrySerializer(serializers.ModelSerializer):
|
||||||
"""SatNOGS DB Telemetry API Serializer"""
|
"""SatNOGS DB Telemetry API Serializer"""
|
||||||
norad_cat_id = serializers.SerializerMethodField()
|
norad_cat_id = serializers.SerializerMethodField()
|
||||||
|
@ -151,10 +287,12 @@ class TelemetrySerializer(serializers.ModelSerializer):
|
||||||
'timestamp'
|
'timestamp'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.INT64)
|
||||||
def get_norad_cat_id(self, obj):
|
def get_norad_cat_id(self, obj):
|
||||||
"""Returns Satellite NORAD ID for this Transmitter"""
|
"""Returns Satellite NORAD ID for this Transmitter"""
|
||||||
return obj.satellite.norad_cat_id
|
return obj.satellite.norad_cat_id
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.UUID)
|
||||||
def get_transmitter(self, obj):
|
def get_transmitter(self, obj):
|
||||||
"""Returns Transmitter UUID"""
|
"""Returns Transmitter UUID"""
|
||||||
try:
|
try:
|
||||||
|
@ -162,6 +300,8 @@ class TelemetrySerializer(serializers.ModelSerializer):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
# deprecated, needs pulled out - cshields
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_schema(self, obj):
|
def get_schema(self, obj):
|
||||||
"""Returns Transmitter telemetry schema"""
|
"""Returns Transmitter telemetry schema"""
|
||||||
try:
|
try:
|
||||||
|
@ -169,10 +309,12 @@ class TelemetrySerializer(serializers.ModelSerializer):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_decoded(self, obj):
|
def get_decoded(self, obj):
|
||||||
"""Returns the payload_decoded field"""
|
"""Returns the payload_decoded field"""
|
||||||
return obj.payload_decoded
|
return obj.payload_decoded
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_frame(self, obj):
|
def get_frame(self, obj):
|
||||||
"""Returns the payload frame"""
|
"""Returns the payload frame"""
|
||||||
return obj.display_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):
|
class ArtifactSerializer(serializers.ModelSerializer):
|
||||||
"""SatNOGS DB Artifacts API Serializer"""
|
"""SatNOGS DB Artifacts API Serializer"""
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -195,6 +357,21 @@ class ArtifactSerializer(serializers.ModelSerializer):
|
||||||
fields = ('id', 'network_obs_id', 'artifact_file')
|
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):
|
class NewArtifactSerializer(serializers.ModelSerializer):
|
||||||
"""SatNOGS Network New Artifact API Serializer"""
|
"""SatNOGS Network New Artifact API Serializer"""
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
@ -206,7 +383,7 @@ class NewArtifactSerializer(serializers.ModelSerializer):
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
'Not a valid SatNOGS Artifact.', code='invalid'
|
'Not a valid SatNOGS Artifact.', code='invalid'
|
||||||
)
|
)
|
||||||
except OSError as error:
|
except (OSError, MultiValueDictKeyError) as error:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
'Not a valid HDF5 file: {}'.format(error), code='invalid'
|
'Not a valid HDF5 file: {}'.format(error), code='invalid'
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,11 +5,11 @@ from db.api import views
|
||||||
|
|
||||||
ROUTER = routers.DefaultRouter()
|
ROUTER = routers.DefaultRouter()
|
||||||
|
|
||||||
ROUTER.register(r'artifacts', views.ArtifactView)
|
ROUTER.register(r'artifacts', views.ArtifactViewSet)
|
||||||
ROUTER.register(r'modes', views.ModeView)
|
ROUTER.register(r'modes', views.ModeViewSet)
|
||||||
ROUTER.register(r'satellites', views.SatelliteView)
|
ROUTER.register(r'satellites', views.SatelliteViewSet)
|
||||||
ROUTER.register(r'transmitters', views.TransmitterView)
|
ROUTER.register(r'transmitters', views.TransmitterViewSet)
|
||||||
ROUTER.register(r'telemetry', views.TelemetryView)
|
ROUTER.register(r'telemetry', views.TelemetryViewSet)
|
||||||
ROUTER.register(r'tle', views.LatestTleSetView)
|
ROUTER.register(r'tle', views.LatestTleSetViewSet)
|
||||||
|
|
||||||
API_URLPATTERNS = ROUTER.urls
|
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"""
|
"""SatNOGS DB API django rest framework Views"""
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.db.models import F
|
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 import mixins, status, viewsets
|
||||||
from rest_framework.parsers import FileUploadParser, FormParser, MultiPartParser
|
from rest_framework.parsers import FileUploadParser, FormParser, MultiPartParser
|
||||||
from rest_framework.permissions import IsAuthenticated
|
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
|
from db.base.tasks import update_satellite
|
||||||
|
|
||||||
|
|
||||||
class ModeView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
|
@extend_schema_view(
|
||||||
"""View into the transmitter modulation modes in the SatNOGS DB database"""
|
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 = [
|
renderer_classes = [
|
||||||
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
|
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
|
||||||
]
|
]
|
||||||
|
@ -25,8 +40,47 @@ class ModeView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
|
||||||
serializer_class = serializers.ModeSerializer
|
serializer_class = serializers.ModeSerializer
|
||||||
|
|
||||||
|
|
||||||
class SatelliteView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
|
@extend_schema_view(
|
||||||
"""View into the Satellite entities in the SatNOGS DB database"""
|
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 = [
|
renderer_classes = [
|
||||||
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
|
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
|
||||||
]
|
]
|
||||||
|
@ -36,9 +90,9 @@ class SatelliteView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
|
||||||
lookup_field = 'norad_cat_id'
|
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
|
Transmitters are inclusive of Transceivers and Transponders
|
||||||
"""
|
"""
|
||||||
renderer_classes = [
|
renderer_classes = [
|
||||||
|
@ -50,9 +104,10 @@ class TransmitterView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
|
||||||
lookup_field = 'uuid'
|
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]
|
renderer_classes = [JSONRenderer, BrowsableAPIRenderer]
|
||||||
queryset = LatestTleSet.objects.all().select_related('satellite').exclude(
|
queryset = LatestTleSet.objects.all().select_related('satellite').exclude(
|
||||||
|
@ -84,7 +139,29 @@ class LatestTleSetView(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
|
||||||
return self.queryset
|
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,
|
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
|
||||||
viewsets.GenericViewSet):
|
viewsets.GenericViewSet):
|
||||||
"""
|
"""
|
||||||
|
@ -102,11 +179,12 @@ class TelemetryView( # pylint: disable=R0901
|
||||||
parser_classes = (FormParser, FileUploadParser)
|
parser_classes = (FormParser, FileUploadParser)
|
||||||
pagination_class = pagination.LinkedHeaderPageNumberPagination
|
pagination_class = pagination.LinkedHeaderPageNumberPagination
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
responses={'201': None}, # None
|
||||||
|
)
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Creates an frame of telemetry data from a satellite observation. See
|
Creates an frame of telemetry data from a satellite observation.
|
||||||
https://www.pe0sat.vgnet.nl/download/Hidden/Dombrovski-SIDS-Simple-Downlink-Share-Convention.pdf
|
|
||||||
for a description of the original protocol.
|
|
||||||
"""
|
"""
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
|
@ -156,11 +234,34 @@ class TelemetryView( # pylint: disable=R0901
|
||||||
return Response(status=status.HTTP_201_CREATED, headers=headers)
|
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,
|
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
|
||||||
viewsets.GenericViewSet):
|
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()
|
queryset = Artifact.objects.all()
|
||||||
filterset_class = filters.ArtifactViewFilter
|
filterset_class = filters.ArtifactViewFilter
|
||||||
|
@ -176,8 +277,10 @@ class ArtifactView( # pylint: disable=R0901
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
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
|
* 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)
|
serializer = self.get_serializer(data=request.data)
|
||||||
try:
|
try:
|
||||||
|
|
126
db/settings.py
126
db/settings.py
|
@ -35,6 +35,7 @@ THIRD_PARTY_APPS = (
|
||||||
'bootstrap_modal_forms',
|
'bootstrap_modal_forms',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
|
'drf_spectacular',
|
||||||
'django_countries',
|
'django_countries',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
'fontawesome_5',
|
'fontawesome_5',
|
||||||
|
@ -278,7 +279,127 @@ REST_FRAMEWORK = {
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
'rest_framework.authentication.SessionAuthentication'
|
'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
|
# Security
|
||||||
|
@ -397,3 +518,6 @@ if ENVIRONMENT == 'dev':
|
||||||
for backend in TEMPLATES:
|
for backend in TEMPLATES:
|
||||||
del backend['OPTIONS']['loaders']
|
del backend['OPTIONS']['loaders']
|
||||||
backend['APP_DIRS'] = True
|
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.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.views.static import serve
|
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.api.urls import API_URLPATTERNS
|
||||||
from db.base.urls import BASE_URLPATTERNS
|
from db.base.urls import BASE_URLPATTERNS
|
||||||
|
|
||||||
|
@ -22,16 +21,10 @@ urlpatterns = [
|
||||||
path('api/', include(API_URLPATTERNS)),
|
path('api/', include(API_URLPATTERNS)),
|
||||||
|
|
||||||
# API Schema
|
# API Schema
|
||||||
path(
|
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||||
'api-schema',
|
# Swagger UI view of our schema. Note the use of SpectacularSwaggerSplitView
|
||||||
get_schema_view(
|
# is to avoid CSP issues without having to open up unsafe-inline.
|
||||||
title='SatNOGS DB',
|
path('api/schema/docs/', SpectacularSwaggerSplitView.as_view(url_name='schema'), name='docs'),
|
||||||
description='SatNOGS DB is a transmitter suggestions and crowd-sourcing app.',
|
|
||||||
version='1.0',
|
|
||||||
generator_class=SchemaGenerator
|
|
||||||
),
|
|
||||||
name='api-schema'
|
|
||||||
),
|
|
||||||
|
|
||||||
# Admin
|
# Admin
|
||||||
path('admin/', admin.site.urls),
|
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
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|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"
|
"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": {
|
"sweetalert2": {
|
||||||
"version": "9.17.2",
|
"version": "9.17.2",
|
||||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-9.17.2.tgz",
|
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-9.17.2.tgz",
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
"d3": "^6.3.0",
|
"d3": "^6.3.0",
|
||||||
"flot": "^4.2.1",
|
"flot": "^4.2.1",
|
||||||
"gpredict.js": "github:kerel-fs/gpredict.js",
|
"gpredict.js": "github:kerel-fs/gpredict.js",
|
||||||
"mapbox-gl": "^2.0.0"
|
"mapbox-gl": "^2.0.0",
|
||||||
|
"swagger-ui-dist": "^3.39.0"
|
||||||
},
|
},
|
||||||
"assets": [
|
"assets": [
|
||||||
"admin-lte/**/*",
|
"admin-lte/**/*",
|
||||||
|
@ -28,6 +29,8 @@
|
||||||
"flot/dist/**/*",
|
"flot/dist/**/*",
|
||||||
"gpredict.js/dist/gpredict.min.js",
|
"gpredict.js/dist/gpredict.min.js",
|
||||||
"mapbox-gl/dist/mapbox-gl.css",
|
"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
|
cryptography==3.3.1
|
||||||
defusedxml==0.7.0rc1
|
defusedxml==0.7.0rc1
|
||||||
dj-database-url==0.5.0
|
dj-database-url==0.5.0
|
||||||
Django==3.1.4
|
Django==3.1.5
|
||||||
django-allauth==0.44.0
|
django-allauth==0.44.0
|
||||||
django-appconf==1.0.4
|
django-appconf==1.0.4
|
||||||
django-avatar==5.0.0
|
django-avatar==5.0.0
|
||||||
|
@ -33,6 +33,7 @@ django-shortuuidfield==0.1.3
|
||||||
django-widget-tweaks==1.4.8
|
django-widget-tweaks==1.4.8
|
||||||
djangorestframework==3.12.2
|
djangorestframework==3.12.2
|
||||||
dnspython==1.16.0
|
dnspython==1.16.0
|
||||||
|
drf-spectacular==0.12.0
|
||||||
ecdsa==0.14.1
|
ecdsa==0.14.1
|
||||||
enum34==1.1.10
|
enum34==1.1.10
|
||||||
eventlet==0.29.1
|
eventlet==0.29.1
|
||||||
|
@ -42,21 +43,24 @@ gunicorn==19.9.0
|
||||||
h5py==3.1.0
|
h5py==3.1.0
|
||||||
idna==2.10
|
idna==2.10
|
||||||
importlib-metadata==1.7.0
|
importlib-metadata==1.7.0
|
||||||
|
inflection==0.5.1
|
||||||
influxdb==5.3.1
|
influxdb==5.3.1
|
||||||
|
jsonschema==3.2.0
|
||||||
kaitaistruct==0.9
|
kaitaistruct==0.9
|
||||||
kombu==4.6.11
|
kombu==4.6.11
|
||||||
Logbook==1.5.3
|
Logbook==1.5.3
|
||||||
lxml==4.6.2
|
lxml==4.6.2
|
||||||
Markdown==3.3.3
|
Markdown==3.3.3
|
||||||
msgpack==1.0.2
|
msgpack==1.0.2
|
||||||
mysqlclient==2.0.2
|
mysqlclient==2.0.3
|
||||||
numpy==1.19.4
|
numpy==1.19.5
|
||||||
oauthlib==3.1.0
|
oauthlib==3.1.0
|
||||||
Pillow==8.0.1
|
Pillow==8.1.0
|
||||||
pyasn1==0.4.8
|
pyasn1==0.4.8
|
||||||
pycparser==2.20
|
pycparser==2.20
|
||||||
PyJWT==2.0.0
|
PyJWT==2.0.0
|
||||||
PyLD==2.0.3
|
PyLD==2.0.3
|
||||||
|
pyrsistent==0.17.3
|
||||||
python-dateutil==2.8.1
|
python-dateutil==2.8.1
|
||||||
python-decouple==3.3
|
python-decouple==3.3
|
||||||
python-dotenv==0.15.0
|
python-dotenv==0.15.0
|
||||||
|
@ -81,7 +85,7 @@ simplejson==3.17.2
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
social-auth-app-django==4.0.0
|
social-auth-app-django==4.0.0
|
||||||
social-auth-core==3.3.3
|
social-auth-core==3.3.3
|
||||||
spacetrack==0.15.0
|
spacetrack==0.16.0
|
||||||
sqlparse==0.4.1
|
sqlparse==0.4.1
|
||||||
Unipath==1.1
|
Unipath==1.1
|
||||||
uritemplate==3.0.1
|
uritemplate==3.0.1
|
||||||
|
|
Loading…
Reference in New Issue