1
0
Fork 0

Add artifact model and API endpoint for new waterfall artifacts

Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
spacecruft
Pierros Papadeas 2020-05-20 19:30:10 +03:00
parent 4891efd75e
commit d8aef58025
10 changed files with 157 additions and 24 deletions

View File

@ -6,7 +6,7 @@ import django_filters
from django_filters import rest_framework as filters
from django_filters.rest_framework import FilterSet
from db.base.models import DemodData, Mode, Satellite, Transmitter
from db.base.models import Artifact, DemodData, Mode, Satellite, Transmitter
class TransmitterViewFilter(FilterSet):
@ -57,3 +57,12 @@ class TelemetryViewFilter(FilterSet):
class Meta:
model = DemodData
fields = ['satellite', 'app_source', 'observer', 'transmitter']
class ArtifactViewFilter(FilterSet):
"""SatNOGS DB Artifact API View Filter"""
class Meta:
model = Artifact
fields = [
'network_obs_id',
]

View File

@ -3,10 +3,11 @@
from __future__ import absolute_import, division, print_function, \
unicode_literals
import h5py
from rest_framework import serializers
from db.base.models import TRANSMITTER_STATUS, DemodData, Mode, Satellite, \
Telemetry, Transmitter
from db.base.models import TRANSMITTER_STATUS, Artifact, DemodData, Mode, \
Satellite, Telemetry, Transmitter
class ModeSerializer(serializers.ModelSerializer):
@ -125,3 +126,33 @@ class SidsSerializer(serializers.ModelSerializer):
class Meta:
model = DemodData
fields = ('satellite', 'payload_frame', 'station', 'lat', 'lng', 'timestamp', 'app_source')
class ArtifactSerializer(serializers.ModelSerializer):
"""SatNOGS DB Artifacts API Serializer"""
class Meta:
model = Artifact
fields = ('id', 'network_obs_id', 'artifact_file')
class NewArtifactSerializer(serializers.ModelSerializer):
"""SatNOGS Network New Artifact API Serializer"""
def validate(self, attrs):
"""Validates data of incoming artifact"""
try:
with h5py.File(self.initial_data['artifact_file'], 'r') as h5_file:
if 'artifact_version' not in h5_file.attrs:
raise serializers.ValidationError(
'Not a valid SatNOGS Artifact.', code='invalid'
)
except OSError as error:
raise serializers.ValidationError(
'Not a valid HDF5 file: {}'.format(error), code='invalid'
)
return attrs
class Meta:
model = Artifact
fields = ('artifact_file', )

View File

@ -8,6 +8,7 @@ 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)

View File

@ -4,12 +4,15 @@ from __future__ import absolute_import, division, print_function, \
from django.core.files.base import ContentFile
from rest_framework import mixins, status, viewsets
from rest_framework.parsers import FileUploadParser, FormParser
from rest_framework.parsers import FileUploadParser, FormParser, \
MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from db.api import filters, pagination, serializers
from db.api.perms import SafeMethodsWithPermission
from db.base.models import DemodData, Mode, Satellite, Transmitter
from db.base.models import Artifact, DemodData, Mode, Satellite, Transmitter
from db.base.tasks import update_satellite
@ -89,3 +92,36 @@ class TelemetryView( # pylint: disable=R0901
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(status=status.HTTP_201_CREATED, headers=headers)
class ArtifactView( # pylint: disable=R0901
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
viewsets.GenericViewSet):
"""SatNOGS DB Artifact API view class"""
queryset = Artifact.objects.all()
filter_class = filters.ArtifactViewFilter
permission_classes = [IsAuthenticated]
parser_classes = (FormParser, MultiPartParser)
pagination_class = pagination.LinkedHeaderPageNumberPagination
def get_serializer_class(self):
"""Returns the right serializer depending on http method that is used"""
if self.action == 'create':
return serializers.NewArtifactSerializer
return serializers.ArtifactSerializer
def create(self, request, *args, **kwargs):
"""Creates artifact"""
serializer = self.get_serializer(data=request.data)
try:
if serializer.is_valid():
data = serializer.save()
http_response = {}
http_response['id'] = data.id
response = Response(http_response, status=status.HTTP_200_OK)
else:
data = serializer.errors
response = Response(data, status=status.HTTP_400_BAD_REQUEST)
except (ValidationError, ValueError, OSError) as error:
response = Response(str(error), status=status.HTTP_400_BAD_REQUEST)
return response

View File

@ -11,8 +11,9 @@ from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.urls import reverse
from db.base.models import DemodData, ExportedFrameset, Mode, Satellite, \
Telemetry, Transmitter, TransmitterEntry, TransmitterSuggestion
from db.base.models import Artifact, DemodData, ExportedFrameset, Mode, \
Satellite, Telemetry, Transmitter, TransmitterEntry, \
TransmitterSuggestion
from db.base.tasks import check_celery, decode_all_data
@ -243,3 +244,9 @@ class ExportedFramesetAdmin(admin.ModelAdmin):
list_display = ('id', 'created', 'user', 'satellite', 'exported_file', 'start', 'end')
search_fields = ('user', 'satellite__norad_cat_id')
list_filter = ('satellite', 'user')
@admin.register(Artifact)
class ArtifactAdmin(admin.ModelAdmin):
"""Defines Artifact view in django admin UI"""
list_display = ('id', 'network_obs_id', 'artifact_file')

View File

@ -0,0 +1,21 @@
# Generated by Django 2.2.12 on 2020-05-20 16:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0017_exported_frameset'),
]
operations = [
migrations.CreateModel(
name='Artifact',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('artifact_file', models.FileField(blank=True, null=True, upload_to='artifacts/')),
('network_obs_id', models.BigIntegerField(blank=True, null=True)),
],
),
]

View File

@ -6,6 +6,7 @@ import logging
from os import path
from uuid import uuid4
import h5py
from django.conf import settings
from django.contrib.auth.models import User
from django.core.validators import MaxValueValidator, MinValueValidator
@ -67,6 +68,18 @@ def _set_is_decoded(sender, instance, **kwargs): # pylint: disable=W0613
instance.is_decoded = instance.payload_decoded != ''
def _extract_network_obs_id(sender, instance, created, **kwargs): # pylint: disable=W0613
post_save.disconnect(_extract_network_obs_id, sender=Artifact)
try:
with h5py.File(instance.artifact_file, 'r') as h5_file:
instance.network_obs_id = h5_file.attrs["observation_id"]
except OSError as error:
LOGGER.warning(error)
instance.save()
post_save.connect(_extract_network_obs_id, sender=Artifact)
@python_2_unicode_compatible
class Mode(models.Model):
"""A satellite transmitter RF mode. For example: FM"""
@ -377,3 +390,18 @@ class ExportedFrameset(models.Model):
exported_file = models.FileField(upload_to=_name_exported_frames, blank=True, null=True)
start = models.DateTimeField(blank=True, null=True)
end = models.DateTimeField(blank=True, null=True)
@python_2_unicode_compatible
class Artifact(models.Model):
"""Model for observation artifacts."""
artifact_file = models.FileField(upload_to='artifacts/', blank=True, null=True)
network_obs_id = models.BigIntegerField(blank=True, null=True)
def __str__(self):
return 'artifact-{0}'.format(self.id)
post_save.connect(_extract_network_obs_id, sender=Artifact)

View File

@ -7,8 +7,8 @@
apipkg==1.5
appdirs==1.4.3
attrs==19.3.0
click==7.1.1
coverage==5.0.4
click==7.1.2
coverage==5.1
distlib==0.3.0
docopt==0.6.1
docopts==0.6.1
@ -22,7 +22,7 @@ packaging==20.3
pluggy==0.13.1
pur==5.2.2
py==1.8.1
pyparsing==2.4.6
pyparsing==2.4.7
pytest==5.4.1
pytest-cov==2.7.1
pytest-django==3.5.1
@ -30,6 +30,6 @@ pytest-forked==1.0.2
pytest-xdist==1.29.0
text-unidecode==1.3
toml==0.10.0
tox==3.14.5
virtualenv==20.0.13
tox==3.14.6
virtualenv==20.0.20
wcwidth==0.1.9

View File

@ -6,15 +6,15 @@
amqp==2.5.2
billiard==3.6.3.0
celery==4.3.0
certifi==2019.11.28
certifi==2020.4.5.1
cffi==1.14.0
chardet==3.0.4
cryptography==2.8
cryptography==2.9.2
defusedxml==0.6.0
dj-database-url==0.5.0
Django==2.2.11
Django==2.2.12
django-allauth==0.40.0
django-appconf==1.0.3
django-appconf==1.0.4
django-avatar==4.1.0
django-compressor==2.3
django-crispy-forms==1.7.2
@ -27,11 +27,11 @@ django-shortuuidfield==0.1.3
djangorestframework==3.10.3
dnspython==1.16.0
enum34==1.1.10
eventlet==0.25.1
eventlet==0.25.2
greenlet==0.4.15
gunicorn==19.9.0
h5py==2.10.0
idna==2.9
importlib-metadata==1.5.0
influxdb==5.2.3
kaitaistruct==0.8
kombu==4.6.8
@ -39,19 +39,20 @@ Logbook==1.5.3
Markdown==3.1.1
monotonic==1.5
mysqlclient==1.4.6
numpy==1.18.4
oauthlib==3.1.0
Pillow==7.0.0
Pillow==7.1.2
pycparser==2.20
PyJWT==1.7.1
python-dateutil==2.8.1
python-decouple==3.1
python-dotenv==0.10.5
python3-openid==3.1.0
pytz==2019.3
pytz==2020.1
PyYAML==5.1.2
ratelimiter==1.2.0.post0
rcssmin==1.0.6
redis==3.4.1
redis==3.5.0
Represent==1.6.0
requests==2.23.0
requests-oauthlib==1.3.0
@ -64,12 +65,10 @@ shortuuid==1.0.1
simplejson==3.16.0
six==1.14.0
social-auth-app-django==3.1.0
social-auth-core==3.3.0
social-auth-core==3.3.3
spacetrack==0.13.6
sqlparse==0.3.1
Unidecode==1.1.1
Unipath==1.1
uritemplate==3.0.1
urllib3==1.24.3
vine==1.3.0
zipp==3.1.0

View File

@ -63,6 +63,7 @@ install_requires =
simplejson~=3.16.0
uritemplate~=3.0.0
PyYAML~=5.1.0
h5py~=2.10.0
# Debugging
django-debug-toolbar~=2.0.0
# Conflict workarounds