1
0
Fork 0

Add JSON-LD post API endpoint for Transmitter suggestions

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
spacecruft
Alfredos-Panagiotis Damkalis 2021-05-20 14:33:43 +03:00
parent 4141e275cc
commit 804b69e442
4 changed files with 215 additions and 3 deletions

18
db/api/parsers.py 100644
View File

@ -0,0 +1,18 @@
"""SatNOGS DB django rest framework API custom parsers"""
from pyld import jsonld
from rest_framework.parsers import JSONParser
from db.base.structured_data import get_structured_data
class JSONLDParser(JSONParser): # pylint: disable=R0903
""" Parser for JSONLD. """
media_type = 'application/ld+json'
def parse(self, stream, media_type=None, parser_context=None):
""" Render `data` into JSONLD, returning a bytestring. """
raw_data = super().parse(stream, media_type, parser_context)
structured_data = get_structured_data(parser_context['view'].basename, [])
data = jsonld.frame(raw_data, structured_data.frame, {'omitGraph': False})
return data

View File

@ -8,7 +8,7 @@ from drf_spectacular.utils import OpenApiExample, extend_schema_field, extend_sc
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, TransmitterEntry
@extend_schema_serializer( @extend_schema_serializer(
@ -181,6 +181,18 @@ class SatelliteSerializer(serializers.ModelSerializer):
] ]
class TransmitterEntrySerializer(serializers.ModelSerializer):
"""SatNOGS DB TransmitterEntry API Serializer"""
class Meta:
model = TransmitterEntry
fields = (
'uuid', 'description', 'status', 'type', 'uplink_low', 'uplink_high', 'uplink_drift',
'downlink_low', 'downlink_high', 'downlink_drift', 'downlink_mode', 'uplink_mode',
'invert', 'baud', 'satellite', 'citation', 'service', 'coordination',
'coordination_url', 'created_by'
)
@extend_schema_serializer( @extend_schema_serializer(
examples=[ examples=[
OpenApiExample( OpenApiExample(

View File

@ -15,6 +15,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from db.api import filters, pagination, serializers from db.api import filters, pagination, serializers
from db.api.parsers import JSONLDParser
from db.api.perms import SafeMethodsWithPermission from db.api.perms import SafeMethodsWithPermission
from db.api.renderers import BrowserableJSONLDRenderer, JSONLDRenderer from db.api.renderers import BrowserableJSONLDRenderer, JSONLDRenderer
from db.base.helpers import gridsquare from db.base.helpers import gridsquare
@ -152,14 +153,17 @@ class SatelliteViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
], ],
), ),
) )
class TransmitterViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901 class TransmitterViewSet( # pylint: disable=R0901
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
viewsets.GenericViewSet):
""" """
Read-only view into the Transmitter entities in the SatNOGS DB database. 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 = [
JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer
] ]
parser_classes = [JSONLDParser]
queryset = Transmitter.objects.filter( queryset = Transmitter.objects.filter(
satellite__associated_satellite__isnull=True, satellite__satellite_entry__approved=True satellite__associated_satellite__isnull=True, satellite__satellite_entry__approved=True
) )
@ -167,6 +171,170 @@ class TransmitterViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R090
filterset_class = filters.TransmitterViewFilter filterset_class = filters.TransmitterViewFilter
lookup_field = 'uuid' lookup_field = 'uuid'
def create(self, request, *args, **kwargs): # noqa: C901; pylint: disable=R0911,R0912,R0915
"""
Creates a transmitter suggestion.
"""
transmitters_data = []
for transmitter_entry in request.data['@graph']:
if 'transmitter' not in transmitter_entry:
data = 'Transmitter Entry without "transmitter" key'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if not transmitter_entry['transmitter']:
data = 'One or more of the required fields are missing.\n Required fields: \
description, status, citation, service, satellite'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
transmitter = transmitter_entry['transmitter']
transmitter_data = {}
if "@id" not in transmitter:
data = 'Missing "@id" for one or more entries'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if 'uuid' in transmitter:
if isinstance(transmitter['uuid'], list):
data = 'Multiple values for "http://schema.org/identifier" or multiple \
entries with the same "@id" and different \
"http://schema.org/identifier" values'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
transmitter_uuid = transmitter['uuid']
if Transmitter.objects.filter(uuid=transmitter_uuid).exists():
transmitter_data['uuid'] = transmitter_uuid
transmitter_data['description'] = transmitter['description']
transmitter_data['status'] = transmitter['status']
transmitter_data['citation'] = transmitter['citation']
transmitter_data['service'] = transmitter['service']
transmitter_data['coordination'] = ''
transmitter_data['coordination_url'] = ''
transmitter_data['created_by'] = request.user.pk
try:
if transmitter['satellite']:
if isinstance(transmitter['satellite'], list):
data = 'Multiple values for "https://schema.space/metasat/satellite" \
or multiple entries with the same "@id" and different \
"https://schema.space/metasat/satellite" values'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
transmitter_data['satellite'] = Satellite.objects.get(
satellite_entry__norad_cat_id=transmitter['satellite']['norad_cat_id']
).pk
else:
data = 'Missing NORAD ID value for Satellite'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
except Satellite.DoesNotExist:
data = 'Unknown NORAD ID: {}'.format(transmitter['satellite']['norad_cat_id'])
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if 'baud' in transmitter:
transmitter_data['baud'] = transmitter['baud']
if 'invert' in transmitter:
transmitter_data['invert'] = transmitter['invert']
if 'uplink' not in transmitter and 'downlink' not in transmitter:
data = 'Missing "https://schema.space/metasat/uplink" or \
"https://schema.space/metasat/downlink"'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if 'uplink' in transmitter:
if isinstance(transmitter['uplink'], list):
data = 'Multiple values for "https://schema.space/metasat/uplink" or multiple \
entries with the same "@id" and different \
"https://schema.space/metasat/uplink" values'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if 'frequency' not in transmitter['uplink']:
data = 'Missing "https://schema.space/metasat/frequency" from \
"https://schema.space/metasat/uplink" value'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if isinstance(transmitter['uplink']['frequency'], int):
transmitter_data['type'] = 'Transceiver'
transmitter_data['uplink_low'] = transmitter['uplink']['frequency']
else:
transmitter_data['type'] = 'Transponder'
if 'minimum' not in transmitter['uplink'][
'frequency'] or 'maximum' not in transmitter['uplink']['frequency']:
data = 'Missing "https://schema.org/minimum" or \
"https://schema.org/maximum" from \
"https://schema.space/metasat/frequency" value of \
"https://schema.space/metasat/uplink"'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
transmitter_data['uplink_low'] = transmitter['uplink']['frequency']['minimum']
transmitter_data['uplink_high'] = transmitter['uplink']['frequency']['maximum']
if 'mode' in transmitter['uplink']:
try:
transmitter_data['uplink_mode'] = Mode.objects.get(
name=transmitter['uplink']['mode']
).pk
except Mode.DoesNotExist:
data = 'Unknown Mode: {}'.format(transmitter['uplink']['mode'])
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if 'drift' in transmitter['uplink']:
transmitter_data['uplink_drift'] = transmitter['uplink']['drift']
else:
transmitter_data['type'] = 'Transmitter'
if 'downlink' in transmitter:
if isinstance(transmitter['downlink'], list):
data = 'Multiple values for "https://schema.space/metasat/downlink" or \
multiple entries with the same "@id" and different \
"https://schema.space/metasat/downlink" values'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if 'frequency' not in transmitter['downlink']:
data = 'Missing "https://schema.space/metasat/frequency" from \
"https://schema.space/metasat/downlink" value'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if isinstance(transmitter['downlink']['frequency'],
int) and not transmitter_data['type'] == 'Transponder':
transmitter_data['downlink_low'] = transmitter['downlink']['frequency']
elif transmitter_data['type'] == 'Transponder':
if 'minimum' not in transmitter['downlink'][
'frequency'] or 'maximum' not in transmitter['downlink']['frequency']:
data = 'Missing "https://schema.org/minimum" or \
"https://schema.org/maximum" from \
"https://schema.space/metasat/frequency" value of \
"https://schema.space/metasat/downlink"'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
transmitter_data['downlink_low'] = transmitter['downlink']['frequency'][
'minimum']
transmitter_data['downlink_high'] = transmitter['downlink']['frequency'][
'maximum']
else:
data = 'Expected integer for "https://schema.space/metasat/frequency" value \
of "https://schema.space/metasat/downlink"'
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if 'mode' in transmitter['downlink']:
try:
transmitter_data['downlink_mode'] = Mode.objects.get(
name=transmitter['downlink']['mode']
).pk
except Mode.DoesNotExist:
data = 'Unknown Mode: {}'.format(transmitter['downlink']['mode'])
return Response(data, status=status.HTTP_400_BAD_REQUEST)
if 'drift' in transmitter['downlink']:
transmitter_data['downlink_drift'] = transmitter['downlink']['drift']
transmitters_data.append(transmitter_data)
serializer = serializers.TransmitterEntrySerializer(
data=transmitters_data, many=True, allow_empty=True
)
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_201_CREATED)
class LatestTleSetViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901 class LatestTleSetViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901
""" """

View File

@ -131,6 +131,20 @@ class TransmitterStructuredData(StructuredData):
"citation": "schema:citation", "citation": "schema:citation",
"service": "service" "service": "service"
} }
self.frame = {
"@context": self.context,
'transmitter': {
"@requireAll": True,
"description": {},
"status": {},
"citation": {},
"service": {},
"satellite": {
"@requireAll": True,
"norad_cat_id": {}
},
}
}
structured_data = [] structured_data = []
transmitter_id_domain = Site.objects.get_current().domain + '/transmitter/' transmitter_id_domain = Site.objects.get_current().domain + '/transmitter/'
satellite_id_domain = Site.objects.get_current().domain + '/satellite/' satellite_id_domain = Site.objects.get_current().domain + '/satellite/'