Merge branch 'remove-data-model' into 'dev'
Refactor models to remove Data table See merge request !401environments/stage/deployments/6
commit
b6c0132576
|
@ -1,14 +1,14 @@
|
|||
import django_filters
|
||||
|
||||
from network.base.models import Data
|
||||
from network.base.models import Observation
|
||||
|
||||
|
||||
class DataViewFilter(django_filters.FilterSet):
|
||||
class ObservationViewFilter(django_filters.FilterSet):
|
||||
start = django_filters.IsoDateTimeFilter(name='start', lookup_expr='gte')
|
||||
end = django_filters.IsoDateTimeFilter(name='end', lookup_expr='lte')
|
||||
norad = django_filters.NumberFilter(name='observation__satellite__norad_cat_id',
|
||||
lookup_expr='iexact')
|
||||
|
||||
class Meta:
|
||||
model = Data
|
||||
model = Observation
|
||||
fields = ['ground_station']
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from network.base.models import Data, Station, DemodData
|
||||
from network.base.models import Observation, Station, DemodData
|
||||
|
||||
|
||||
class DemodDataSerializer(serializers.ModelSerializer):
|
||||
|
@ -9,7 +9,7 @@ class DemodDataSerializer(serializers.ModelSerializer):
|
|||
fields = ('payload_demod', )
|
||||
|
||||
|
||||
class DataSerializer(serializers.ModelSerializer):
|
||||
class ObservationSerializer(serializers.ModelSerializer):
|
||||
transmitter = serializers.SerializerMethodField()
|
||||
norad_cat_id = serializers.SerializerMethodField()
|
||||
station_name = serializers.SerializerMethodField()
|
||||
|
@ -18,8 +18,8 @@ class DataSerializer(serializers.ModelSerializer):
|
|||
demoddata = DemodDataSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Data
|
||||
fields = ('id', 'start', 'end', 'observation', 'ground_station', 'transmitter',
|
||||
model = Observation
|
||||
fields = ('id', 'start', 'end', 'ground_station', 'transmitter',
|
||||
'norad_cat_id', 'payload', 'waterfall', 'demoddata', 'station_name',
|
||||
'station_lat', 'station_lng')
|
||||
read_only_fields = ['id', 'start', 'end', 'observation', 'ground_station',
|
||||
|
@ -28,17 +28,17 @@ class DataSerializer(serializers.ModelSerializer):
|
|||
|
||||
def update(self, instance, validated_data):
|
||||
validated_data.pop('demoddata')
|
||||
super(DataSerializer, self).update(instance, validated_data)
|
||||
super(ObservationSerializer, self).update(instance, validated_data)
|
||||
return instance
|
||||
|
||||
def get_transmitter(self, obj):
|
||||
try:
|
||||
return obj.observation.transmitter.uuid
|
||||
return obj.transmitter.uuid
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
def get_norad_cat_id(self, obj):
|
||||
return obj.observation.satellite.norad_cat_id
|
||||
return obj.satellite.norad_cat_id
|
||||
|
||||
def get_station_name(self, obj):
|
||||
return obj.ground_station.name
|
||||
|
@ -59,28 +59,28 @@ class JobSerializer(serializers.ModelSerializer):
|
|||
transmitter = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Data
|
||||
model = Observation
|
||||
fields = ('id', 'start', 'end', 'ground_station', 'tle0', 'tle1', 'tle2',
|
||||
'frequency', 'mode', 'transmitter')
|
||||
|
||||
def get_frequency(self, obj):
|
||||
return obj.observation.transmitter.downlink_low
|
||||
return obj.transmitter.downlink_low
|
||||
|
||||
def get_transmitter(self, obj):
|
||||
return obj.observation.transmitter.uuid
|
||||
return obj.transmitter.uuid
|
||||
|
||||
def get_tle0(self, obj):
|
||||
return obj.observation.tle.tle0
|
||||
return obj.tle.tle0
|
||||
|
||||
def get_tle1(self, obj):
|
||||
return obj.observation.tle.tle1
|
||||
return obj.tle.tle1
|
||||
|
||||
def get_tle2(self, obj):
|
||||
return obj.observation.tle.tle2
|
||||
return obj.tle.tle2
|
||||
|
||||
def get_mode(self, obj):
|
||||
try:
|
||||
return obj.observation.transmitter.mode.name
|
||||
return obj.transmitter.mode.name
|
||||
except:
|
||||
return ''
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import pytest
|
|||
from django.test import TestCase
|
||||
|
||||
from network.base.tests import (
|
||||
DataFactory,
|
||||
ObservationFactory,
|
||||
SatelliteFactory,
|
||||
TransmitterFactory,
|
||||
StationFactory
|
||||
|
@ -16,7 +16,7 @@ class JobViewApiTest(TestCase):
|
|||
"""
|
||||
Tests the Job View API
|
||||
"""
|
||||
data = None
|
||||
observation = None
|
||||
satellites = []
|
||||
transmitters = []
|
||||
stations = []
|
||||
|
@ -28,7 +28,7 @@ class JobViewApiTest(TestCase):
|
|||
self.transmitters.append(TransmitterFactory())
|
||||
for x in xrange(1, 10):
|
||||
self.stations.append(StationFactory())
|
||||
self.data = DataFactory()
|
||||
self.observation = ObservationFactory()
|
||||
|
||||
def test_job_view_api(self):
|
||||
response = self.client.get('/api/jobs/')
|
||||
|
|
|
@ -5,7 +5,7 @@ from network.api import views
|
|||
router = routers.DefaultRouter()
|
||||
|
||||
router.register(r'jobs', views.JobView, base_name='jobs')
|
||||
router.register(r'data', views.DataView, base_name='data')
|
||||
router.register(r'data', views.ObservationView, base_name='data')
|
||||
router.register(r'settings', views.SettingsView, base_name='settings')
|
||||
|
||||
api_urlpatterns = router.urls
|
||||
|
|
|
@ -7,13 +7,13 @@ from rest_framework.response import Response
|
|||
|
||||
from network.api.perms import StationOwnerCanEditPermission
|
||||
from network.api import serializers, filters
|
||||
from network.base.models import Data, Station
|
||||
from network.base.models import Observation, Station
|
||||
|
||||
|
||||
class DataView(viewsets.ModelViewSet, mixins.UpdateModelMixin):
|
||||
queryset = Data.objects.all()
|
||||
serializer_class = serializers.DataSerializer
|
||||
filter_class = filters.DataViewFilter
|
||||
class ObservationView(viewsets.ModelViewSet, mixins.UpdateModelMixin):
|
||||
queryset = Observation.objects.all()
|
||||
serializer_class = serializers.ObservationSerializer
|
||||
filter_class = filters.ObservationViewFilter
|
||||
permission_classes = [
|
||||
StationOwnerCanEditPermission
|
||||
]
|
||||
|
@ -23,14 +23,14 @@ class DataView(viewsets.ModelViewSet, mixins.UpdateModelMixin):
|
|||
instance = self.get_object()
|
||||
instance.demoddata.create(payload_demod=request.data.get('demoddata'))
|
||||
|
||||
super(DataView, self).update(request, *args, **kwargs)
|
||||
super(ObservationView, self).update(request, *args, **kwargs)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class JobView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Data.objects.filter(payload='')
|
||||
queryset = Observation.objects.filter(payload='')
|
||||
serializer_class = serializers.JobSerializer
|
||||
filter_class = filters.DataViewFilter
|
||||
filter_class = filters.ObservationViewFilter
|
||||
filter_fields = ('ground_station')
|
||||
|
||||
def get_queryset(self):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from network.base.models import (Antenna, Satellite, Station, Transmitter,
|
||||
Observation, Data, Mode, Tle, Rig, DemodData)
|
||||
Observation, Mode, Tle, Rig, DemodData)
|
||||
|
||||
|
||||
@admin.register(Rig)
|
||||
|
@ -71,18 +71,3 @@ class ObservationAdmin(admin.ModelAdmin):
|
|||
|
||||
class DataDemodInline(admin.TabularInline):
|
||||
model = DemodData
|
||||
|
||||
|
||||
@admin.register(Data)
|
||||
class DataAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'start_date', 'end_date', 'observation', 'ground_station')
|
||||
readonly_fields = ('observation', 'ground_station')
|
||||
inlines = [
|
||||
DataDemodInline,
|
||||
]
|
||||
|
||||
def start_date(self, obj):
|
||||
return obj.start.strftime('%d.%m.%Y, %H:%M')
|
||||
|
||||
def end_date(self, obj):
|
||||
return obj.end.strftime('%d.%m.%Y, %H:%M')
|
||||
|
|
|
@ -2,8 +2,8 @@ from django.core.management.base import BaseCommand
|
|||
from django.core.management import call_command
|
||||
|
||||
from network.base.models import Antenna
|
||||
from network.base.tests import (generate_payload, generate_payload_name, DemodDataFactory,
|
||||
DataFactory, StationFactory)
|
||||
from network.base.tests import (generate_payload, generate_payload_name,
|
||||
DemodDataFactory, StationFactory, ObservationFactory)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -22,7 +22,7 @@ class Command(BaseCommand):
|
|||
self.stdout.write("Creating fixtures...")
|
||||
StationFactory.create_batch(10,
|
||||
antennas=(Antenna.objects.all().values_list('id', flat=True)))
|
||||
DataFactory.create_batch(20)
|
||||
ObservationFactory.create_batch(20)
|
||||
for _ in range(40):
|
||||
DemodDataFactory.create(payload_demod__data=generate_payload(),
|
||||
payload_demod__filename=generate_payload_name())
|
||||
|
|
|
@ -18,8 +18,8 @@ class Command(BaseCommand):
|
|||
try:
|
||||
sat = satellite(obj.norad_cat_id)
|
||||
except:
|
||||
self.stdout.write(('Satellite {} with Identifier {} does '
|
||||
'not exist').format(obj.name, obj.norad_cat_id))
|
||||
self.stdout.write(('{0} - {1}: TLE not found [error]')
|
||||
.format(obj.name, obj.norad_cat_id))
|
||||
continue
|
||||
|
||||
obj.name = sat.name()
|
||||
|
@ -30,13 +30,13 @@ class Command(BaseCommand):
|
|||
try:
|
||||
latest_tle = obj.latest_tle.tle1
|
||||
if latest_tle == tle[1]:
|
||||
self.stdout.write(('Satellite {} with Identifier {} '
|
||||
'found [defer]').format(obj.name, obj.norad_cat_id))
|
||||
self.stdout.write(('{0} - {1}: TLE already exists [defer]')
|
||||
.format(obj.name, obj.norad_cat_id))
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
|
||||
Tle.objects.create(tle0=tle[0], tle1=tle[1], tle2=tle[2], satellite=obj)
|
||||
|
||||
self.stdout.write(('Satellite {} with Identifier {} '
|
||||
'found [updated]').format(obj.name, obj.norad_cat_id))
|
||||
self.stdout.write(('{0} - {1}: new TLE found [updated]')
|
||||
.format(obj.name, obj.norad_cat_id))
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-09 21:03
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
def disable_db_checks(apps, schema_editor):
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
connection = connections[DEFAULT_DB_ALIAS]
|
||||
if 'mysql' in connection.settings_dict['ENGINE']:
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SET foreign_key_checks = 0')
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('base', '0021_auto_20170813_1258'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(disable_db_checks),
|
||||
|
||||
migrations.AddField(
|
||||
model_name='demoddata',
|
||||
name='observation',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='demoddata', to='base.Observation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='ground_station',
|
||||
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='base.Station'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='max_altitude',
|
||||
field=models.FloatField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='payload',
|
||||
field=models.FileField(blank=True, null=True, upload_to=b'data_payloads'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='rise_azimuth',
|
||||
field=models.FloatField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='set_azimuth',
|
||||
field=models.FloatField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='vetted_datetime',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='vetted_status',
|
||||
field=models.CharField(choices=[(b'unknown', b'Unknown'), (b'verified', b'Verified'), (b'data_not_verified', b'Has Data, Not Verified'), (b'no_data', b'No Data')], default=b'unknown', max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='vetted_user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='observations_vetted', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='waterfall',
|
||||
field=models.ImageField(blank=True, null=True, upload_to=b'data_waterfalls'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-09 13:26
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
def move_data(apps, schema_editor):
|
||||
Data = apps.get_model('base', 'Data')
|
||||
Observation = apps.get_model('base', 'Observation')
|
||||
DemodData = apps.get_model('base', 'DemodData')
|
||||
for observation in Observation.objects.all():
|
||||
obs = Observation.objects.filter(pk=observation.pk)
|
||||
data = Data.objects.filter(observation=observation)
|
||||
for counter, datum in enumerate(data):
|
||||
demod = DemodData.objects.filter(data=datum)
|
||||
obj = {
|
||||
'satellite': datum.observation.satellite,
|
||||
'transmitter': datum.observation.transmitter,
|
||||
'tle': datum.observation.tle,
|
||||
'author': datum.observation.author,
|
||||
'start': datum.start,
|
||||
'end': datum.end,
|
||||
'ground_station': datum.ground_station,
|
||||
'payload': datum.payload,
|
||||
'waterfall': datum.waterfall,
|
||||
'vetted_datetime': datum.vetted_datetime,
|
||||
'vetted_user': datum.vetted_user,
|
||||
'vetted_status': datum.vetted_status,
|
||||
'rise_azimuth': datum.rise_azimuth,
|
||||
'max_altitude': datum.max_altitude,
|
||||
'set_azimuth': datum.set_azimuth
|
||||
}
|
||||
# this observation becomes its first data object
|
||||
if not counter:
|
||||
obs.update(**obj)
|
||||
obs_new = obs.get()
|
||||
# new observation objects for all the next data objects
|
||||
else:
|
||||
obs_new = Observation.objects.create(**obj)
|
||||
demod.update(observation=obs_new)
|
||||
|
||||
|
||||
dependencies = [
|
||||
('base', '0022_auto_20170909_2103'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(move_data),
|
||||
]
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-09 21:07
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0023_auto_20170909_2103'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='demoddata',
|
||||
name='data',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-09 21:11
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0024_remove_demoddata_data'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='data',
|
||||
name='ground_station',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='data',
|
||||
name='observation',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='data',
|
||||
name='vetted_user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Data',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-09 21:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('base', '0025_auto_20170909_2111'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='observation',
|
||||
name='author',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observations', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='observation',
|
||||
name='ground_station',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observations', to='base.Station'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='observation',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observations', to='base.Satellite'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='observation',
|
||||
name='tle',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observations', to='base.Tle'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='observation',
|
||||
name='transmitter',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observations', to='base.Transmitter'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='observation',
|
||||
name='vetted_user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observations_vetted', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='station',
|
||||
name='owner',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ground_stations', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='station',
|
||||
name='rig',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ground_stations', to='base.Rig'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tle',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tles', to='base.Satellite'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transmitter',
|
||||
name='satellite',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transmitters', to='base.Satellite'),
|
||||
),
|
||||
]
|
|
@ -7,6 +7,7 @@ from django.conf import settings
|
|||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.dispatch import receiver
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
@ -37,6 +38,7 @@ SATELLITE_STATUS = ['alive', 'dead', 're-entered']
|
|||
|
||||
|
||||
class Rig(models.Model):
|
||||
"""Model for Rig types."""
|
||||
name = models.CharField(choices=zip(RIG_TYPES, RIG_TYPES), max_length=10)
|
||||
rictld_number = models.PositiveIntegerField(blank=True, null=True)
|
||||
|
||||
|
@ -67,7 +69,8 @@ class Antenna(models.Model):
|
|||
|
||||
class Station(models.Model):
|
||||
"""Model for SatNOGS ground stations."""
|
||||
owner = models.ForeignKey(User)
|
||||
owner = models.ForeignKey(User, related_name="ground_stations",
|
||||
on_delete=models.SET_NULL, null=True, blank=True)
|
||||
name = models.CharField(max_length=45)
|
||||
image = models.ImageField(upload_to='ground_stations', blank=True)
|
||||
alt = models.PositiveIntegerField(help_text='In meters above ground')
|
||||
|
@ -87,7 +90,8 @@ class Station(models.Model):
|
|||
last_seen = models.DateTimeField(null=True, blank=True)
|
||||
horizon = models.PositiveIntegerField(help_text='In degrees above 0', default=10)
|
||||
uuid = models.CharField(db_index=True, max_length=100, blank=True)
|
||||
rig = models.ForeignKey(Rig, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
rig = models.ForeignKey(Rig, related_name='ground_stations',
|
||||
on_delete=models.SET_NULL, null=True, blank=True)
|
||||
description = models.TextField(max_length=500, blank=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -115,8 +119,8 @@ class Station(models.Model):
|
|||
|
||||
@property
|
||||
def success_rate(self):
|
||||
observations = self.data_set.all().count()
|
||||
success = self.data_set.exclude(payload='').count()
|
||||
observations = self.observations.all().count()
|
||||
success = self.observations.exclude(payload='').count()
|
||||
if observations:
|
||||
return int(100 * (float(success) / float(observations)))
|
||||
else:
|
||||
|
@ -178,21 +182,21 @@ class Satellite(models.Model):
|
|||
|
||||
@property
|
||||
def data_count(self):
|
||||
return Data.objects.filter(observation__satellite=self).count()
|
||||
return Observation.objects.filter(satellite=self).count()
|
||||
|
||||
@property
|
||||
def verified_count(self):
|
||||
data = Data.objects.filter(observation__satellite=self)
|
||||
data = Observation.objects.filter(satellite=self)
|
||||
return data.filter(vetted_status='verified').count()
|
||||
|
||||
@property
|
||||
def empty_count(self):
|
||||
data = Data.objects.filter(observation__satellite=self)
|
||||
data = Observation.objects.filter(satellite=self)
|
||||
return data.filter(vetted_status='no_data').count()
|
||||
|
||||
@property
|
||||
def unknown_count(self):
|
||||
data = Data.objects.filter(observation__satellite=self)
|
||||
data = Observation.objects.filter(satellite=self)
|
||||
return data.filter(vetted_status='unknown').count()
|
||||
|
||||
@property
|
||||
|
@ -225,7 +229,8 @@ class Tle(models.Model):
|
|||
tle1 = models.CharField(max_length=200, blank=True)
|
||||
tle2 = models.CharField(max_length=200, blank=True)
|
||||
updated = models.DateTimeField(auto_now=True, blank=True)
|
||||
satellite = models.ForeignKey(Satellite, related_name='tles', null=True)
|
||||
satellite = models.ForeignKey(Satellite, related_name='tles',
|
||||
on_delete=models.CASCADE, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['tle0']
|
||||
|
@ -247,7 +252,8 @@ class Transmitter(models.Model):
|
|||
null=True, on_delete=models.SET_NULL)
|
||||
invert = models.BooleanField(default=False)
|
||||
baud = models.FloatField(validators=[MinValueValidator(0)], null=True, blank=True)
|
||||
satellite = models.ForeignKey(Satellite, related_name='transmitters', null=True)
|
||||
satellite = models.ForeignKey(Satellite, related_name='transmitters',
|
||||
on_delete=models.CASCADE, null=True, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.description
|
||||
|
@ -255,15 +261,28 @@ class Transmitter(models.Model):
|
|||
|
||||
class Observation(models.Model):
|
||||
"""Model for SatNOGS observations."""
|
||||
satellite = models.ForeignKey(Satellite)
|
||||
transmitter = models.ForeignKey(Transmitter, null=True, related_name='observations')
|
||||
tle = models.ForeignKey(Tle, null=True)
|
||||
author = models.ForeignKey(User)
|
||||
satellite = models.ForeignKey(Satellite, related_name='observations',
|
||||
on_delete=models.SET_NULL, null=True, blank=True)
|
||||
transmitter = models.ForeignKey(Transmitter, related_name='observations',
|
||||
on_delete=models.SET_NULL, null=True, blank=True)
|
||||
tle = models.ForeignKey(Tle, related_name='observations',
|
||||
on_delete=models.SET_NULL, null=True, blank=True)
|
||||
author = models.ForeignKey(User, related_name='observations',
|
||||
on_delete=models.SET_NULL, null=True, blank=True)
|
||||
start = models.DateTimeField()
|
||||
end = models.DateTimeField()
|
||||
|
||||
class Meta:
|
||||
ordering = ['-start', '-end']
|
||||
ground_station = models.ForeignKey(Station, related_name='observations',
|
||||
on_delete=models.SET_NULL, null=True, blank=True)
|
||||
payload = models.FileField(upload_to='data_payloads', blank=True, null=True)
|
||||
waterfall = models.ImageField(upload_to='data_waterfalls', blank=True, null=True)
|
||||
vetted_datetime = models.DateTimeField(null=True, blank=True)
|
||||
vetted_user = models.ForeignKey(User, related_name='observations_vetted',
|
||||
on_delete=models.SET_NULL, null=True, blank=True)
|
||||
vetted_status = models.CharField(choices=OBSERVATION_STATUSES,
|
||||
max_length=20, default='unknown')
|
||||
rise_azimuth = models.FloatField(blank=True, null=True)
|
||||
max_altitude = models.FloatField(blank=True, null=True)
|
||||
set_azimuth = models.FloatField(blank=True, null=True)
|
||||
|
||||
@property
|
||||
def is_past(self):
|
||||
|
@ -283,51 +302,6 @@ class Observation(models.Model):
|
|||
deletion = self.end + timedelta(minutes=int(settings.OBSERVATION_MIN_DELETION_RANGE))
|
||||
return deletion < now()
|
||||
|
||||
# observation has at least 1 payload submitted, no verification taken into account
|
||||
@property
|
||||
def has_submitted_data(self):
|
||||
return self.data_set.exclude(payload='').count()
|
||||
|
||||
# observaton has at least 1 payload that has been verified good
|
||||
@property
|
||||
def has_verified_data(self):
|
||||
return self.data_set.filter(vetted_status='verified').count()
|
||||
|
||||
# observation is vetted to be all bad data
|
||||
@property
|
||||
def has_no_data(self):
|
||||
return self.data_set.filter(
|
||||
vetted_status='no_data').count() == self.data_set.count()
|
||||
|
||||
# observation has at least 1 payload left unvetted
|
||||
@property
|
||||
def has_unvetted_data(self):
|
||||
return self.data_set.filter(vetted_status='unknown').count()
|
||||
|
||||
def __unicode__(self):
|
||||
return '{0}'.format(self.id)
|
||||
|
||||
|
||||
class Data(models.Model):
|
||||
"""Model for observation data."""
|
||||
start = models.DateTimeField()
|
||||
end = models.DateTimeField()
|
||||
observation = models.ForeignKey(Observation)
|
||||
ground_station = models.ForeignKey(Station)
|
||||
payload = models.FileField(upload_to='data_payloads', blank=True, null=True)
|
||||
waterfall = models.ImageField(upload_to='data_waterfalls', blank=True, null=True)
|
||||
vetted_datetime = models.DateTimeField(null=True, blank=True)
|
||||
vetted_user = models.ForeignKey(User, related_name="vetted_user_set", null=True, blank=True)
|
||||
vetted_status = models.CharField(choices=OBSERVATION_STATUSES,
|
||||
max_length=20, default='unknown')
|
||||
rise_azimuth = models.FloatField(blank=True, null=True)
|
||||
max_altitude = models.FloatField(blank=True, null=True)
|
||||
set_azimuth = models.FloatField(blank=True, null=True)
|
||||
|
||||
@property
|
||||
def is_past(self):
|
||||
return self.end < now()
|
||||
|
||||
# this payload has been vetted good/bad by someone
|
||||
@property
|
||||
def is_vetted(self):
|
||||
|
@ -357,9 +331,15 @@ class Data(models.Model):
|
|||
class Meta:
|
||||
ordering = ['-start', '-end']
|
||||
|
||||
def __unicode__(self):
|
||||
return str(self.id)
|
||||
|
||||
@receiver(models.signals.post_delete, sender=Data)
|
||||
def data_remove_files(sender, instance, **kwargs):
|
||||
def get_absolute_url(self):
|
||||
return reverse('base:observation_view', kwargs={'id': self.id})
|
||||
|
||||
|
||||
@receiver(models.signals.post_delete, sender=Observation)
|
||||
def observation_remove_files(sender, instance, **kwargs):
|
||||
if instance.payload:
|
||||
if os.path.isfile(instance.payload.path):
|
||||
os.remove(instance.payload.path)
|
||||
|
@ -369,7 +349,8 @@ def data_remove_files(sender, instance, **kwargs):
|
|||
|
||||
|
||||
class DemodData(models.Model):
|
||||
data = models.ForeignKey(Data, related_name='demoddata')
|
||||
observation = models.ForeignKey(Observation, related_name='demoddata',
|
||||
on_delete=models.CASCADE, blank=True, null=True)
|
||||
payload_demod = models.FileField(upload_to='data_payloads', blank=True, null=True)
|
||||
|
||||
def is_image(self):
|
||||
|
|
|
@ -14,7 +14,7 @@ from django.contrib.auth.models import Permission
|
|||
|
||||
from network.base.models import (ANTENNA_BANDS, ANTENNA_TYPES, RIG_TYPES, OBSERVATION_STATUSES,
|
||||
Rig, Mode, Antenna, Satellite, Tle, Station, Transmitter,
|
||||
Observation, Data, DemodData)
|
||||
Observation, DemodData)
|
||||
from network.users.tests import UserFactory
|
||||
|
||||
|
||||
|
@ -142,12 +142,20 @@ class TransmitterFactory(factory.django.DjangoModelFactory):
|
|||
class ObservationFactory(factory.django.DjangoModelFactory):
|
||||
"""Observation model factory."""
|
||||
satellite = factory.Iterator(get_valid_satellites())
|
||||
tle = factory.SubFactory(TleFactory)
|
||||
author = factory.SubFactory(UserFactory)
|
||||
start = fuzzy.FuzzyDateTime(now() - timedelta(days=3),
|
||||
now() + timedelta(days=3))
|
||||
end = factory.LazyAttribute(
|
||||
lambda x: x.start + timedelta(hours=random.randint(1, 8))
|
||||
)
|
||||
ground_station = factory.Iterator(Station.objects.all())
|
||||
payload = factory.django.FileField(filename='data.ogg')
|
||||
vetted_datetime = factory.LazyAttribute(
|
||||
lambda x: x.end + timedelta(hours=random.randint(1, 20))
|
||||
)
|
||||
vetted_user = factory.SubFactory(UserFactory)
|
||||
vetted_status = fuzzy.FuzzyChoice(choices=OBSERVATION_STATUSES)
|
||||
|
||||
@factory.lazy_attribute
|
||||
def transmitter(self):
|
||||
|
@ -157,27 +165,8 @@ class ObservationFactory(factory.django.DjangoModelFactory):
|
|||
model = Observation
|
||||
|
||||
|
||||
class DataFactory(factory.django.DjangoModelFactory):
|
||||
start = fuzzy.FuzzyDateTime(now() - timedelta(days=3),
|
||||
now() + timedelta(days=3))
|
||||
end = factory.LazyAttribute(
|
||||
lambda x: x.start + timedelta(minutes=random.randint(1, 20))
|
||||
)
|
||||
observation = factory.SubFactory(ObservationFactory)
|
||||
ground_station = factory.Iterator(Station.objects.all())
|
||||
payload = factory.django.FileField(filename='data.ogg')
|
||||
vetted_datetime = factory.LazyAttribute(
|
||||
lambda x: x.end + timedelta(hours=random.randint(1, 20))
|
||||
)
|
||||
vetted_user = factory.SubFactory(UserFactory)
|
||||
vetted_status = fuzzy.FuzzyChoice(choices=OBSERVATION_STATUS_IDS)
|
||||
|
||||
class Meta:
|
||||
model = Data
|
||||
|
||||
|
||||
class DemodDataFactory(factory.django.DjangoModelFactory):
|
||||
data = factory.Iterator(Data.objects.all())
|
||||
observation = factory.Iterator(Observation.objects.all())
|
||||
payload_demod = factory.django.FileField()
|
||||
|
||||
class Meta:
|
||||
|
@ -255,6 +244,7 @@ class ObservationsListViewTest(TestCase):
|
|||
observations = []
|
||||
satellites = []
|
||||
transmitters = []
|
||||
stations = []
|
||||
|
||||
def setUp(self):
|
||||
# Clear the data and create some new random data
|
||||
|
@ -269,9 +259,11 @@ class ObservationsListViewTest(TestCase):
|
|||
for x in xrange(1, 10):
|
||||
self.satellites.append(SatelliteFactory())
|
||||
for x in xrange(1, 10):
|
||||
self.transmitters.append(TransmitterFactory(satellite=self.satellites[0]))
|
||||
self.transmitters.append(TransmitterFactory())
|
||||
for x in xrange(1, 10):
|
||||
self.observations.append(ObservationFactory(satellite=self.satellites[0]))
|
||||
self.stations.append(StationFactory())
|
||||
for x in xrange(1, 20):
|
||||
self.observations.append(ObservationFactory())
|
||||
|
||||
def test_observations_list(self):
|
||||
response = self.client.get('/observations/')
|
||||
|
@ -281,10 +273,9 @@ class ObservationsListViewTest(TestCase):
|
|||
|
||||
def test_observations_list_deselect_bad(self):
|
||||
response = self.client.get('/observations/?bad=0')
|
||||
print response
|
||||
|
||||
for x in self.observations:
|
||||
self.assertNotContains(response, x.transmitter.mode.name)
|
||||
self.assertContains(response, x.transmitter.mode.name)
|
||||
|
||||
def test_observations_list_deselect_good(self):
|
||||
response = self.client.get('/observations/?good=0')
|
||||
|
@ -330,6 +321,7 @@ class ObservationViewTest(TestCase):
|
|||
observation = None
|
||||
satellites = []
|
||||
transmitters = []
|
||||
stations = []
|
||||
user = None
|
||||
|
||||
def setUp(self):
|
||||
|
@ -340,6 +332,8 @@ class ObservationViewTest(TestCase):
|
|||
self.satellites.append(SatelliteFactory())
|
||||
for x in xrange(1, 10):
|
||||
self.transmitters.append(TransmitterFactory())
|
||||
for x in xrange(1, 10):
|
||||
self.stations.append(StationFactory())
|
||||
self.observation = ObservationFactory()
|
||||
|
||||
def test_observation(self):
|
||||
|
@ -471,13 +465,12 @@ class SettingsSiteViewTest(TestCase):
|
|||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
class DataVerifyViewtest(TestCase):
|
||||
class ObservationVerifyViewtest(TestCase):
|
||||
"""
|
||||
Test marking data as vetted
|
||||
"""
|
||||
client = Client()
|
||||
user = None
|
||||
data = None
|
||||
satellites = []
|
||||
stations = []
|
||||
transmitters = []
|
||||
|
@ -490,30 +483,26 @@ class DataVerifyViewtest(TestCase):
|
|||
self.satellites.append(SatelliteFactory())
|
||||
for x in xrange(1, 10):
|
||||
self.transmitters.append(TransmitterFactory())
|
||||
for x in xrange(1, 10):
|
||||
self.observations.append(ObservationFactory())
|
||||
|
||||
for x in xrange(1, 10):
|
||||
self.stations.append(StationFactory())
|
||||
|
||||
self.data = DataFactory()
|
||||
self.observation = ObservationFactory()
|
||||
|
||||
def test_get_data_verify(self):
|
||||
response = self.client.get('/data_verify/%d/' % self.data.id)
|
||||
self.assertRedirects(response, '/observations/%d/' % self.data.observation.id)
|
||||
data = Data.objects.get(id=self.data.id)
|
||||
self.assertEqual(data.vetted_user.username, self.user.username)
|
||||
self.assertEqual(data.vetted_status, 'verified')
|
||||
def test_get_observation_verify(self):
|
||||
response = self.client.get('/observation_verify/%d/' % self.observation.id)
|
||||
self.assertRedirects(response, '/observations/%d/' % self.observation.id)
|
||||
observation = Observation.objects.get(id=self.observation.id)
|
||||
self.assertEqual(observation.vetted_user.username, self.user.username)
|
||||
self.assertEqual(observation.vetted_status, 'verified')
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
class DataMarkBadViewtest(TestCase):
|
||||
class ObservationMarkBadViewtest(TestCase):
|
||||
"""
|
||||
Test marking data as vetted
|
||||
"""
|
||||
client = Client()
|
||||
user = None
|
||||
data = None
|
||||
satellites = []
|
||||
stations = []
|
||||
transmitters = []
|
||||
|
@ -528,14 +517,14 @@ class DataMarkBadViewtest(TestCase):
|
|||
for x in xrange(1, 10):
|
||||
self.stations.append(StationFactory())
|
||||
|
||||
self.data = DataFactory()
|
||||
self.observation = ObservationFactory()
|
||||
|
||||
def test_get_data_mark_bad(self):
|
||||
response = self.client.get('/data_mark_bad/%d/' % self.data.id)
|
||||
self.assertRedirects(response, '/observations/%d/' % self.data.observation.id)
|
||||
data = Data.objects.get(id=self.data.id)
|
||||
self.assertEqual(data.vetted_user.username, self.user.username)
|
||||
self.assertEqual(data.vetted_status, 'no_data')
|
||||
def test_get_observation_mark_bad(self):
|
||||
response = self.client.get('/observation_mark_bad/%d/' % self.observation.id)
|
||||
self.assertRedirects(response, '/observations/%d/' % self.observation.id)
|
||||
observation = Observation.objects.get(id=self.observation.id)
|
||||
self.assertEqual(observation.vetted_user.username, self.user.username)
|
||||
self.assertEqual(observation.vetted_status, 'no_data')
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
|
@ -578,9 +567,6 @@ class ObservationModelTest(TestCase):
|
|||
self.observation.end = now()
|
||||
self.observation.save()
|
||||
|
||||
def test_has_submitted_data(self):
|
||||
self.assertEqual(0, self.observation.has_submitted_data)
|
||||
|
||||
def test_is_passed(self):
|
||||
self.assertTrue(self.observation.is_past)
|
||||
|
||||
|
@ -599,34 +585,3 @@ class ObservationModelTest(TestCase):
|
|||
self.observation.end = now() - timedelta(minutes=200)
|
||||
self.observation.save()
|
||||
self.assertTrue(self.observation.is_deletable_after_end)
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
class DataModelTest(TestCase):
|
||||
"""
|
||||
Test various properties of the Observation Model
|
||||
"""
|
||||
data = None
|
||||
data2 = None
|
||||
satellites = []
|
||||
transmitters = []
|
||||
|
||||
def setUp(self):
|
||||
for x in xrange(1, 10):
|
||||
self.satellites.append(SatelliteFactory())
|
||||
for x in xrange(1, 10):
|
||||
self.transmitters.append(TransmitterFactory())
|
||||
self.data = DataFactory()
|
||||
self.data.end = now()
|
||||
self.data.vetted_status = 'no_data'
|
||||
self.data.save()
|
||||
self.data2 = DataFactory(payload=None)
|
||||
|
||||
def test_is_no_data(self):
|
||||
self.assertTrue(self.data.is_no_data)
|
||||
|
||||
def test_is_passed(self):
|
||||
self.assertTrue(self.data.is_past)
|
||||
|
||||
def test_payload_exists(self):
|
||||
self.assertFalse(self.data.payload_exists)
|
||||
|
|
|
@ -11,7 +11,8 @@ base_urlpatterns = ([
|
|||
|
||||
# Observations
|
||||
url(r'^observations/$', views.ObservationListView.as_view(), name='observations_list'),
|
||||
url(r'^observations/(?P<id>[0-9]+)/$', views.observation_view, name='observation_view'),
|
||||
url(r'^observations/(?P<id>[0-9]+)/$', views.observation_view,
|
||||
name='observation_view'),
|
||||
url(r'^observations/(?P<id>[0-9]+)/delete/$', views.observation_delete,
|
||||
name='observation_delete'),
|
||||
url(r'^observations/new/$', views.observation_new, name='observation_new'),
|
||||
|
@ -21,10 +22,10 @@ base_urlpatterns = ([
|
|||
url(r'^prediction_windows/(?P<sat_id>[\w.@+-]+)/(?P<transmitter>[\w.@+-]+)/'
|
||||
'(?P<start_date>.+)/(?P<end_date>.+)/$',
|
||||
views.prediction_windows, name='prediction_windows'),
|
||||
url(r'^data_verify/(?P<id>[0-9]+)/$', views.data_verify, name='data_verify'),
|
||||
url(r'^data_mark_bad/(?P<id>[0-9]+)/$', views.data_mark_bad, name='data_mark_bad'),
|
||||
url(r'^observations/data/(?P<id>[0-9]+)/$', views.observation_data_view,
|
||||
name='observation_data_view'),
|
||||
url(r'^observation_verify/(?P<id>[0-9]+)/$', views.observation_verify,
|
||||
name='observation_verify'),
|
||||
url(r'^observation_mark_bad/(?P<id>[0-9]+)/$', views.observation_mark_bad,
|
||||
name='observation_mark_bad'),
|
||||
|
||||
# Stations
|
||||
url(r'^stations/$', views.stations_list, name='stations_list'),
|
||||
|
|
|
@ -5,7 +5,6 @@ from operator import itemgetter
|
|||
from datetime import datetime, timedelta
|
||||
from StringIO import StringIO
|
||||
|
||||
from django.db.models import Count, Case, When, F
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
@ -21,7 +20,7 @@ from django.views.generic import ListView
|
|||
from rest_framework import serializers, viewsets
|
||||
|
||||
from network.base.models import (Station, Transmitter, Observation,
|
||||
Data, Satellite, Antenna, Tle, Rig)
|
||||
Satellite, Antenna, Tle, Rig)
|
||||
from network.base.forms import StationForm, SatelliteFilterForm
|
||||
from network.base.decorators import admin_required
|
||||
from network.base.helpers import calculate_polar_data, resolve_overlaps
|
||||
|
@ -148,40 +147,18 @@ class ObservationListView(ListView):
|
|||
unvetted = True
|
||||
|
||||
if norad_cat_id == '':
|
||||
observations = Observation.objects.all().order_by('-id')
|
||||
observations = Observation.objects.all()
|
||||
else:
|
||||
observations = Observation.objects.filter(
|
||||
satellite__norad_cat_id=norad_cat_id).order_by('-id')
|
||||
satellite__norad_cat_id=norad_cat_id)
|
||||
|
||||
# Add the data subqueries as annotations
|
||||
observations = observations.annotate(
|
||||
data_count=Count('data', distinct=True),
|
||||
nodata_count=Count(
|
||||
Case(
|
||||
When(data__vetted_status='no_data', then=1)
|
||||
), distinct=True
|
||||
),
|
||||
unknown_count=Count(
|
||||
Case(
|
||||
When(data__vetted_status='unknown', then=1)
|
||||
), distinct=True
|
||||
),
|
||||
vetted_count=Count(
|
||||
Case(
|
||||
When(data__vetted_status='verified', then=1)
|
||||
), distinct=True
|
||||
)
|
||||
)
|
||||
|
||||
# Start with an empty queryset and add each filter as an or/union
|
||||
resultset = Observation.objects.none()
|
||||
if bad:
|
||||
resultset |= observations.filter(nodata_count=F('data_count'))
|
||||
if good:
|
||||
resultset |= observations.filter(vetted_count__gt=0)
|
||||
if unvetted:
|
||||
resultset |= observations.filter(unknown_count__gt=0)
|
||||
return resultset
|
||||
if not bad:
|
||||
observations = observations.exclude(vetted_status='no_data')
|
||||
if not good:
|
||||
observations = observations.exclude(vetted_status='verified')
|
||||
if not unvetted:
|
||||
observations = observations.exclude(vetted_status='unknown')
|
||||
return observations
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
|
@ -221,9 +198,6 @@ def observation_new(request):
|
|||
sat = Satellite.objects.get(norad_cat_id=sat_id)
|
||||
trans = Transmitter.objects.get(id=trans_id)
|
||||
tle = Tle.objects.get(id=sat.latest_tle.id)
|
||||
obs = Observation(satellite=sat, transmitter=trans, tle=tle,
|
||||
author=me, start=start, end=end)
|
||||
obs.save()
|
||||
|
||||
sat_ephem = ephem.readtle(str(sat.latest_tle.tle0),
|
||||
str(sat.latest_tle.tle1),
|
||||
|
@ -247,15 +221,18 @@ def observation_new(request):
|
|||
observer.elevation = ground_station.alt
|
||||
tr, azr, tt, altt, ts, azs = observer.next_pass(sat_ephem)
|
||||
|
||||
Data.objects.create(start=make_aware(start, utc), end=make_aware(end, utc),
|
||||
ground_station=ground_station, observation=obs,
|
||||
rise_azimuth=format(math.degrees(azr), '.0f'),
|
||||
max_altitude=format(math.degrees(altt), '.0f'),
|
||||
set_azimuth=format(math.degrees(azs), '.0f'))
|
||||
Observation.objects.create(satellite=sat, transmitter=trans, tle=tle, author=me,
|
||||
start=make_aware(start, utc), end=make_aware(end, utc),
|
||||
ground_station=ground_station,
|
||||
rise_azimuth=format(math.degrees(azr), '.0f'),
|
||||
max_altitude=format(math.degrees(altt), '.0f'),
|
||||
set_azimuth=format(math.degrees(azs), '.0f'))
|
||||
time_start_new = ephem.Date(ts).datetime() + timedelta(minutes=1)
|
||||
observer.date = time_start_new.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
|
||||
return redirect(reverse('base:observation_view', kwargs={'id': obs.id}))
|
||||
messages.success(request, 'Your selected Observations were scheduled successfully.')
|
||||
|
||||
return redirect(reverse('base:observations_list'))
|
||||
|
||||
satellites = Satellite.objects.filter(transmitters__alive=True) \
|
||||
.filter(status='alive').distinct()
|
||||
|
@ -377,7 +354,7 @@ def prediction_windows(request, sat_id, transmitter, start_date, end_date,
|
|||
window_end = make_aware(ephem.Date(ts).datetime(), utc)
|
||||
|
||||
# Check if overlaps with existing scheduled observations
|
||||
gs_data = Data.objects.filter(ground_station=station)
|
||||
gs_data = Observation.objects.filter(ground_station=station)
|
||||
window = resolve_overlaps(station, gs_data, window_start, window_end)
|
||||
|
||||
if window:
|
||||
|
@ -424,15 +401,13 @@ def prediction_windows(request, sat_id, transmitter, start_date, end_date,
|
|||
def observation_view(request, id):
|
||||
"""View for single observation page."""
|
||||
observation = get_object_or_404(Observation, id=id)
|
||||
dataset = Data.objects.filter(observation=observation)
|
||||
|
||||
# not all users will be able to vet data within an observation, allow
|
||||
# staff, observation requestors, and station owners
|
||||
is_vetting_user = False
|
||||
if request.user.is_authenticated():
|
||||
if request.user == observation.author or \
|
||||
dataset.filter(
|
||||
ground_station__in=Station.objects.filter(owner=request.user)).count or \
|
||||
Station.objects.filter(owner=request.user).filter(id=observation.id).count() or \
|
||||
request.user.is_staff:
|
||||
is_vetting_user = True
|
||||
|
||||
|
@ -444,8 +419,8 @@ def observation_view(request, id):
|
|||
observation.is_deletable_after_end:
|
||||
is_deletable = True
|
||||
|
||||
if settings.ENVIRONMENT == 'production':
|
||||
discuss_slug = 'https://community.satnogs.org/t/observation-{0}-{1}-{2}' \
|
||||
if settings.ENVIRONMENT == 'dev':
|
||||
discuss_slug = 'https://community.libre.space/t/observation-{0}-{1}-{2}' \
|
||||
.format(observation.id, slugify(observation.satellite.name),
|
||||
observation.satellite.norad_cat_id)
|
||||
discuss_url = ('https://community.libre.space/new-topic?title=Observation {0}: {1}'
|
||||
|
@ -462,14 +437,13 @@ def observation_view(request, id):
|
|||
has_comments = False
|
||||
|
||||
return render(request, 'base/observation_view.html',
|
||||
{'observation': observation, 'dataset': dataset,
|
||||
'has_comments': has_comments, 'discuss_url': discuss_url,
|
||||
'discuss_slug': discuss_slug, 'is_vetting_user': is_vetting_user,
|
||||
'is_deletable': is_deletable})
|
||||
{'observation': observation, 'has_comments': has_comments,
|
||||
'discuss_url': discuss_url, 'discuss_slug': discuss_slug,
|
||||
'is_vetting_user': is_vetting_user, 'is_deletable': is_deletable})
|
||||
|
||||
return render(request, 'base/observation_view.html',
|
||||
{'observation': observation, 'dataset': dataset,
|
||||
'is_vetting_user': is_vetting_user, 'is_deletable': is_deletable})
|
||||
{'observation': observation, 'is_vetting_user': is_vetting_user,
|
||||
'is_deletable': is_deletable})
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -489,25 +463,25 @@ def observation_delete(request, id):
|
|||
|
||||
|
||||
@login_required
|
||||
def data_verify(request, id):
|
||||
def observation_verify(request, id):
|
||||
me = request.user
|
||||
data = get_object_or_404(Data, id=id)
|
||||
data.vetted_status = 'verified'
|
||||
data.vetted_user = me
|
||||
data.vetted_datetime = datetime.today()
|
||||
data.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
|
||||
return redirect(reverse('base:observation_view', kwargs={'id': data.observation}))
|
||||
observation = get_object_or_404(Observation, id=id)
|
||||
observation.vetted_status = 'verified'
|
||||
observation.vetted_user = me
|
||||
observation.vetted_datetime = datetime.today()
|
||||
observation.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
|
||||
return redirect(reverse('base:observation_view', kwargs={'id': observation.id}))
|
||||
|
||||
|
||||
@login_required
|
||||
def data_mark_bad(request, id):
|
||||
def observation_mark_bad(request, id):
|
||||
me = request.user
|
||||
data = get_object_or_404(Data, id=id)
|
||||
data.vetted_status = 'no_data'
|
||||
data.vetted_user = me
|
||||
data.vetted_datetime = datetime.today()
|
||||
data.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
|
||||
return redirect(reverse('base:observation_view', kwargs={'id': data.observation}))
|
||||
observation = get_object_or_404(Observation, id=id)
|
||||
observation.vetted_status = 'no_data'
|
||||
observation.vetted_user = me
|
||||
observation.vetted_datetime = datetime.today()
|
||||
observation.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
|
||||
return redirect(reverse('base:observation_view', kwargs={'id': observation.id}))
|
||||
|
||||
|
||||
def stations_list(request):
|
||||
|
@ -698,9 +672,3 @@ def satellite_view(request, id):
|
|||
}
|
||||
|
||||
return JsonResponse(data, safe=False)
|
||||
|
||||
|
||||
def observation_data_view(request, id):
|
||||
observation = get_object_or_404(Observation, data__id=id)
|
||||
return redirect(reverse('base:observation_view',
|
||||
kwargs={'id': observation.id}) + '#{0}'.format(id))
|
||||
|
|
|
@ -259,6 +259,13 @@ CSP_STYLE_SRC = (
|
|||
# Database
|
||||
DATABASE_URL = getenv('DATABASE_URL', 'sqlite:///db.sqlite3')
|
||||
DATABASES = {'default': db_url(DATABASE_URL)}
|
||||
DATABASES_EXTRAS = {
|
||||
'OPTIONS': {
|
||||
'init_command': 'SET sql_mode="STRICT_TRANS_TABLES"'
|
||||
},
|
||||
}
|
||||
if DATABASES['default']['ENGINE'].split('.')[-1] == 'mysql':
|
||||
DATABASES['default'].update(DATABASES_EXTRAS)
|
||||
|
||||
# Mapbox API
|
||||
MAPBOX_GEOCODE_URL = 'https://api.tiles.mapbox.com/v4/geocode/mapbox.places/'
|
||||
|
|
|
@ -154,14 +154,23 @@ footer {
|
|||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.gs-front-data {
|
||||
.front-data {
|
||||
float: right;
|
||||
width: 200px;
|
||||
display: flow-root;
|
||||
}
|
||||
|
||||
.gs-front-line {
|
||||
.front-line {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
|
||||
.front-border {
|
||||
border-right: 1px solid lightgray;
|
||||
}
|
||||
}
|
||||
|
||||
#navbar-logo {
|
||||
|
@ -347,32 +356,6 @@ span.datetime-time {
|
|||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.observation-data .panel-title {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.panel {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.panel-title > a {
|
||||
color: #337ab7;
|
||||
}
|
||||
|
||||
.verified {
|
||||
color: #009933;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.no_data {
|
||||
color: #cc3300;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.vetting {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.waterfall {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
|
|
@ -1,51 +1,18 @@
|
|||
/* global d3 WaveSurfer URI */
|
||||
/* global WaveSurfer URI */
|
||||
|
||||
$(document).ready(function() {
|
||||
'use strict';
|
||||
|
||||
var observation_start = 1000 * $('#observation-info').data('start');
|
||||
var observation_end = 1000 * $('#observation-info').data('end');
|
||||
|
||||
var observation_data = [];
|
||||
|
||||
var formatTime = function(timeSeconds) {
|
||||
var minute = Math.floor(timeSeconds / 60); // get minute(integer) from timeSeconds
|
||||
var tmp = Math.round(timeSeconds - (minute * 60)); // get second(integer) from timeSeconds
|
||||
var second = (tmp < 10 ? '0' : '') + tmp; // make two-figured integer if less than 10
|
||||
|
||||
return String(minute + ':' + second); // combine minute and second in string
|
||||
};
|
||||
|
||||
$('.observation-data').each(function(){
|
||||
var $this = $(this);
|
||||
var data_groundstation = $this.data('groundstation');
|
||||
var data_time_start = 1000 * $this.data('start');
|
||||
var data_time_end = 1000 * $this.data('end');
|
||||
observation_data.push({label : data_groundstation, times : [{starting_time: data_time_start, ending_time: data_time_end}]});
|
||||
});
|
||||
|
||||
var chart = d3.timeline()
|
||||
.stack()
|
||||
.beginning(observation_start)
|
||||
.ending(observation_end)
|
||||
.hover(function (d, i, datum) {
|
||||
var div = $('#hoverRes');
|
||||
var colors = chart.colors();
|
||||
div.find('.coloredDiv').css('background-color', colors(i));
|
||||
div.find('#name').text(datum.label);
|
||||
})
|
||||
.margin({left:140, right:10, top:0, bottom:50})
|
||||
.tickFormat({format: d3.time.format.utc('%H:%M'), tickTime: d3.time.minutes, tickInterval: 30, tickSize: 6});
|
||||
|
||||
var svg_width = 1140;
|
||||
if (screen.width < 1200) { svg_width = 940; }
|
||||
if (screen.width < 992) { svg_width = 720; }
|
||||
if (screen.width < 768) { svg_width = screen.width - 30; }
|
||||
d3.select('#timeline').append('svg').attr('width', svg_width)
|
||||
.datum(observation_data).call(chart);
|
||||
// Format time for the player
|
||||
function formatTime(timeSeconds) {
|
||||
var minute = Math.floor(timeSeconds / 60);
|
||||
var tmp = Math.round(timeSeconds - (minute * 60));
|
||||
var second = (tmp < 10 ? '0' : '') + tmp;
|
||||
return String(minute + ':' + second);
|
||||
}
|
||||
|
||||
// Set width for not selected tabs
|
||||
var panelWidth = $('.panel-body').first().width();
|
||||
var panelWidth = $('.tab-content').first().width();
|
||||
$('.tab-pane').css('width', panelWidth);
|
||||
|
||||
// Waveform loading
|
||||
|
@ -93,9 +60,9 @@ $(document).ready(function() {
|
|||
wavesurfer.playPause();
|
||||
});
|
||||
|
||||
$('a[href="#tab-audio-' + wid + '"]').on('shown.bs.tab', function () {
|
||||
$('a[href="#tab-audio"]').on('shown.bs.tab', function () {
|
||||
wavesurfer.load(data_payload_url);
|
||||
$('a[href="#tab-audio-' + wid + '"]').off('shown.bs.tab');
|
||||
$('a[href="#tab-audio"]').off('shown.bs.tab');
|
||||
});
|
||||
|
||||
wavesurfer.on('ready', function() {
|
||||
|
@ -103,11 +70,12 @@ $(document).ready(function() {
|
|||
var spectrogram = Object.create(WaveSurfer.Spectrogram);
|
||||
spectrogram.init({
|
||||
wavesurfer: wavesurfer,
|
||||
container: '#wave-spectrogram-' + wid,
|
||||
container: '#wave-spectrogram',
|
||||
fftSamples: 256,
|
||||
windowFunc: 'hann'
|
||||
});
|
||||
|
||||
//$playbackTime.text(formatTime(wavesurfer.getCurrentTime()));
|
||||
$playbackTime.text(formatTime(wavesurfer.getCurrentTime()));
|
||||
|
||||
wavesurfer.on('audioprocess', function(evt) {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
$(document).ready(function() {
|
||||
'use strict';
|
||||
|
||||
function drawPolarPlot(canvas, data) {
|
||||
var ctx = canvas.getContext('2d');
|
||||
var centerΧ = ctx.canvas.width / 2;
|
||||
var centerY = ctx.canvas.height / 2;
|
||||
var canvasSize = Math.min(ctx.canvas.width, ctx.canvas.height);
|
||||
var altUnit = canvasSize/(2.5 * 90);
|
||||
var fontRatio = 0.07;
|
||||
var radius;
|
||||
var radians;
|
||||
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
//Draw altitude circles
|
||||
ctx.beginPath();
|
||||
var altCircles = [90, 60, 30];
|
||||
for (var i=0; i < altCircles.length; i++) {
|
||||
radius = altCircles[i] * altUnit;
|
||||
ctx.moveTo(centerΧ + radius,centerY);
|
||||
for(var th=1;th<=360;th+=1) {
|
||||
radians = (Math.PI/180) * th;
|
||||
ctx.lineTo(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians));
|
||||
}
|
||||
}
|
||||
|
||||
ctx.strokeStyle = '#444444';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
//Draw axis and letters
|
||||
radius = 96 * altUnit;
|
||||
ctx.moveTo(centerΧ, centerY);
|
||||
ctx.lineTo(centerΧ, centerY + radius);
|
||||
ctx.moveTo(centerΧ, centerY);
|
||||
ctx.lineTo(centerΧ, centerY-radius);
|
||||
ctx.moveTo(centerΧ, centerY);
|
||||
ctx.lineTo(centerΧ + radius, centerY);
|
||||
ctx.moveTo(centerΧ, centerY);
|
||||
ctx.lineTo(centerΧ-radius, centerY);
|
||||
|
||||
radius = 98 * altUnit;
|
||||
ctx.font = (canvasSize * fontRatio) + 'px serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('N', centerΧ, centerY - radius);
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('S', centerΧ, centerY + radius);
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.textAlign = 'right';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('W', centerΧ - radius, centerY);
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText('E', centerΧ + radius, centerY);
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
//Draw data
|
||||
ctx.beginPath();
|
||||
radians = (Math.PI/180) * (data[0][1] - 90);
|
||||
radius = (90 - data[0][0]) * altUnit;
|
||||
ctx.moveTo(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians));
|
||||
|
||||
var dataLength = data.length;
|
||||
for (var j=1; j< dataLength; j++) {
|
||||
radians = (Math.PI/180) * (data[j][1] - 90);
|
||||
radius = (90 - data[j][0] ) * altUnit;
|
||||
ctx.lineTo(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians));
|
||||
}
|
||||
ctx.strokeStyle = 'rgb(0, 0, 255)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
//Draw start and end
|
||||
radians = (Math.PI/180) * (data[0][1] - 90);
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians), 3, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = 'lightgreen';
|
||||
ctx.fill();
|
||||
|
||||
radians = (Math.PI/180) * (data[dataLength-1][1] - 90);
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians), 3, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
$('canvas').each(function(){
|
||||
var $this = $(this);
|
||||
drawPolarPlot($this.get(0), $this.data().points);
|
||||
});
|
||||
});
|
|
@ -23,95 +23,6 @@ $(document).ready(function() {
|
|||
});
|
||||
}
|
||||
|
||||
function drawPolarPlot (canvas, data) {
|
||||
var ctx = canvas.getContext('2d');
|
||||
var centerΧ = ctx.canvas.width / 2;
|
||||
var centerY = ctx.canvas.height / 2;
|
||||
var canvasSize = Math.min(ctx.canvas.width, ctx.canvas.height);
|
||||
var altUnit = canvasSize/(2.5 * 90);
|
||||
var fontRatio = 0.07;
|
||||
var radius;
|
||||
var radians;
|
||||
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
//Draw altitude circles
|
||||
ctx.beginPath();
|
||||
var altCircles = [90, 60, 30];
|
||||
for (var i=0; i < altCircles.length; i++) {
|
||||
radius = altCircles[i] * altUnit;
|
||||
ctx.moveTo(centerΧ + radius,centerY);
|
||||
for(var th=1;th<=360;th+=1) {
|
||||
radians = (Math.PI/180) * th;
|
||||
ctx.lineTo(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians));
|
||||
}
|
||||
}
|
||||
|
||||
ctx.strokeStyle = '#444444';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
//Draw axis and letters
|
||||
radius = 96 * altUnit;
|
||||
ctx.moveTo(centerΧ, centerY);
|
||||
ctx.lineTo(centerΧ, centerY + radius);
|
||||
ctx.moveTo(centerΧ, centerY);
|
||||
ctx.lineTo(centerΧ, centerY-radius);
|
||||
ctx.moveTo(centerΧ, centerY);
|
||||
ctx.lineTo(centerΧ + radius, centerY);
|
||||
ctx.moveTo(centerΧ, centerY);
|
||||
ctx.lineTo(centerΧ-radius, centerY);
|
||||
|
||||
radius = 98 * altUnit;
|
||||
ctx.font = (canvasSize * fontRatio) + 'px serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('N', centerΧ, centerY - radius);
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText('S', centerΧ, centerY + radius);
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.textAlign = 'right';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('W', centerΧ - radius, centerY);
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText('E', centerΧ + radius, centerY);
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
//Draw data
|
||||
ctx.beginPath();
|
||||
radians = (Math.PI/180) * (data[0][1] - 90);
|
||||
radius = (90 - data[0][0]) * altUnit;
|
||||
ctx.moveTo(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians));
|
||||
|
||||
var dataLength = data.length;
|
||||
for (var j=1; j< dataLength; j++) {
|
||||
radians = (Math.PI/180) * (data[j][1] - 90);
|
||||
radius = (90 - data[j][0] ) * altUnit;
|
||||
ctx.lineTo(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians));
|
||||
}
|
||||
ctx.strokeStyle = 'rgb(0, 0, 255)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
//Draw start and end
|
||||
radians = (Math.PI/180) * (data[0][1] - 90);
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians), 3, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = 'lightgreen';
|
||||
ctx.fill();
|
||||
|
||||
radians = (Math.PI/180) * (data[dataLength-1][1] - 90);
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerΧ + radius * Math.cos(radians),centerY + radius * Math.sin(radians), 3, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
$('canvas').each(function(){
|
||||
var $this = $(this);
|
||||
drawPolarPlot($this.get(0), $this.data().points);
|
||||
});
|
||||
|
||||
// Init the map
|
||||
var mapboxid = $('div#map-station').data('mapboxid');
|
||||
var mapboxtoken = $('div#map-station').data('mapboxtoken');
|
||||
|
|
|
@ -110,18 +110,18 @@
|
|||
<th>Frequency</th>
|
||||
<th>Encoding</th>
|
||||
<th>Timeframe</th>
|
||||
<th>Stations</th>
|
||||
<th>Station</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for observation in latest_observations.all %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'base:observation_view' id=observation.id %}">
|
||||
{% if observation.has_verified_data %}
|
||||
{% if observation.is_verified %}
|
||||
<span class="label label-success" title="There is known good data in this observation"
|
||||
{% elif observation.is_future %}
|
||||
<span class="label label-info" title="This observation is in the future"
|
||||
{% elif observation.has_unvetted_data %}
|
||||
{% elif not observation.is_vetted %}
|
||||
<span class="label label-warning" title="There is data that needs vetting in this observation"
|
||||
{% else %}
|
||||
<span class="label label-danger" title="No good data in this observation"
|
||||
|
@ -144,12 +144,9 @@
|
|||
<span class="datetime-time">{{ observation.end|date:"H:i:s" }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% regroup observation.data_set.all by ground_station as station_list %}
|
||||
{% for station in station_list %}
|
||||
<a href="{% url 'base:station_view' id=station.grouper.id %}">
|
||||
<span class="label label-primary" title="{{ station.grouper.name }}" data-toggle="tooltip">{{ station.grouper.id }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
<a href="{% url 'base:station_view' id=observation.ground_station.id %}">
|
||||
{{ observation.ground_station.name }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -190,12 +187,9 @@
|
|||
<span class="datetime-time">{{ observation.end|date:"H:i:s" }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% regroup observation.data_set.all by ground_station as station_list %}
|
||||
{% for station in station_list %}
|
||||
<a href="{% url 'base:station_view' id=station.grouper.id %}">
|
||||
<span class="label label-primary" title="{{ station.grouper.name }}" data-toggle="tooltip">{{ station.grouper.id }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
<a href="{% url 'base:station_view' id=observation.ground_station.id %}">
|
||||
<span class="label label-primary">{{ observation.ground_station.name }}</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button type="button" id="calculate-observation" class="btn btn-primary pull-right">
|
||||
Calculate Observation
|
||||
Calculate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -148,7 +148,7 @@
|
|||
<div class="row calculation-result">
|
||||
<div class="col-md-12">
|
||||
<button type="submit" id="schedule-observation" class="btn btn-success pull-right" disabled="True">
|
||||
Schedule Observation
|
||||
Schedule
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,27 @@
|
|||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<h2>
|
||||
{% if discuss_slug %}
|
||||
<a id="obs-discuss"
|
||||
data-slug="{{ discuss_slug }}"
|
||||
href="{% if has_comments %}{{ discuss_slug }}{% else %}{{ discuss_url }}{% endif %}"
|
||||
class="btn btn-primary" target="_blank">
|
||||
<span class="glyphicon glyphicon-comment" aria-hidden="true"></span>
|
||||
Discuss
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if not observation.is_vetted and is_vetting_user %}
|
||||
<a href="{% url 'base:observation_verify' id=observation.id %}">
|
||||
<button id="verify-data" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> Verify
|
||||
</button>
|
||||
</a>
|
||||
<a href="{% url 'base:observation_mark_bad' id=observation.id %}">
|
||||
<button id="bad-data" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-thumbs-down" aria-hidden="true"></span> Bad
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if is_deletable %}
|
||||
<a href="{% url 'base:observation_delete' id=observation.id %}" id="obs-delete" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete Observation"></span>
|
||||
|
@ -30,243 +51,209 @@
|
|||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<th>Satellite</th>
|
||||
<th>Frequency</th>
|
||||
<th>Encoding</th>
|
||||
<th>Timeframe</th>
|
||||
<th>Observer</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="#" data-toggle="modal" data-target="#SatelliteModal" data-id="{{ observation.satellite.norad_cat_id }}">
|
||||
{{ observation.satellite.norad_cat_id }} - {{ observation.satellite.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ observation.transmitter.downlink_low|frq }}</td>
|
||||
<td>{{ observation.transmitter.mode|default:"-" }}</td>
|
||||
<td>{{ observation.start|date:"Y-m-d H:i:s" }}</br>{{ observation.end|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>
|
||||
<a href="{% url 'users:view_user' username=observation.author.username %}">
|
||||
{{ observation.author.displayname }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="col-md-4 front-border">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Satellite</span>
|
||||
<span class="front-data">
|
||||
<a href="#" data-toggle="modal" data-target="#SatelliteModal" data-id="{{ observation.satellite.norad_cat_id }}">
|
||||
{{ observation.satellite.norad_cat_id }} - {{ observation.satellite.name }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Station</span>
|
||||
<span class="front-data">
|
||||
<a href="{% url 'base:station_view' id=observation.ground_station.id %}">
|
||||
{{ observation.ground_station.name }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Observer</span>
|
||||
<span class="front-data">
|
||||
<a href="{% url 'users:view_user' username=observation.author.username %}">
|
||||
{{ observation.author.displayname }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Status</span>
|
||||
<span class="front-data">
|
||||
{% if observation.is_verified %}
|
||||
<span class="label label-xs label-success" aria-hidden="true"
|
||||
data-toggle="tooltip" data-placement="right"
|
||||
title="Verified good on {{ observation.vetted_datetime|date:"Y-m-d H:i:s" }} by {{ observation.vetted_user.displayname }}">Verified</span>
|
||||
{% elif observation.is_no_data %}
|
||||
<span class="label label-xs label-danger" aria-hidden="true"
|
||||
data-toggle="tooltip" data-placement="right"
|
||||
title="Verified bad on {{ observation.vetted_datetime|date:"Y-m-d H:i:s" }} by {{ observation.vetted_user.displayname }}">Bad</span>
|
||||
{% elif observation.is_future %}
|
||||
<span class="label label-xs label-info" aria-hidden="true"
|
||||
data-toggle="tooltip" data-placement="right"
|
||||
title="This observation is in the future">Pending</span>
|
||||
{% else %}
|
||||
<span class="label label-xs label-warning" aria-hidden="true"
|
||||
data-toggle="tooltip" data-placement="right"
|
||||
title="This observation needs vetting">Unvetted</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 front-border">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Frequency</span>
|
||||
<span class="front-data">
|
||||
{{ observation.transmitter.downlink_low|frq }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Encoding</span>
|
||||
<span class="front-data">
|
||||
{{ observation.transmitter.mode|default:"-" }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Timeframe</span>
|
||||
<span class="front-data">
|
||||
{{ observation.start|date:"Y-m-d H:i:s" }}<br>{{ observation.end|date:"Y-m-d H:i:s" }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Rise</span>
|
||||
<span class="front-data">
|
||||
{{ observation.rise_azimuth }}°
|
||||
</span>
|
||||
</div>
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Max</span>
|
||||
<span class="front-data">
|
||||
{{ observation.max_altitude }}°
|
||||
</span>
|
||||
</div>
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Set</span>
|
||||
<span class="front-data">
|
||||
{{ observation.set_azimuth }}°
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not observation.data_set.all %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="notice">
|
||||
No data associated with this observation.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Timeline</h3>
|
||||
<div id="timeline"></div>
|
||||
<div id="hoverRes">
|
||||
<div class="coloredDiv"></div>
|
||||
<div id="name"></div>
|
||||
<div id="scrolled_date"></div>
|
||||
<hr>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 observation-data" id="{{ observation.id }}"
|
||||
data-start="{{ observation.start|date:"U" }}"
|
||||
data-end="{{ observation.end|date:"U" }}"
|
||||
data-groundstation="{{ observation.ground_station }}">
|
||||
<h3>Payload</h3>
|
||||
|
||||
<ul class="nav nav-tabs data-tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a href="#tab-waterfall"
|
||||
aria-controls="tab-waterfall"
|
||||
role="tab"
|
||||
data-toggle="tab">Waterfall
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab-audio"
|
||||
aria-controls="tab-audio"
|
||||
role="tab"
|
||||
data-toggle="tab">Audio</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab-data"
|
||||
aria-controls="tab-data"
|
||||
role="tab"
|
||||
data-toggle="tab">Data</a>
|
||||
</li>
|
||||
|
||||
<div class="pull-right">
|
||||
{% if observation.payload %}
|
||||
<a href="{{ MEDIA_URL }}{{ observation.payload }}"
|
||||
target="_blank"
|
||||
download="">
|
||||
<button type="button" class="btn btn-default btn-xs">
|
||||
<span class="glyphicon glyphicon-download"></span> Audio
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if observation.waterfall %}
|
||||
<a href="{{ MEDIA_URL }}{{ observation.waterfall }}"
|
||||
target="_blank"
|
||||
download="">
|
||||
<button type="button" class="btn btn-default btn-xs">
|
||||
<span class="glyphicon glyphicon-download"></span> Waterfall
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="tab-waterfall">
|
||||
{% if observation.waterfall %}
|
||||
<div id="waterfall-{{ observation.id }}" class="waterfall">
|
||||
<img class="img-responsive waterfall" src="{{ MEDIA_URL }}{{ observation.waterfall }}" alt="waterfall">
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="notice">
|
||||
Waiting for waterfall
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tab-audio">
|
||||
{% if observation.payload %}
|
||||
<div id="loading-{{ observation.id }}" class="notice">Loading audio...</div>
|
||||
<div class="progress progress-striped active" id="progress-bar-{{ observation.id }}">
|
||||
<div class="progress-bar progress-bar-info"></div>
|
||||
</div>
|
||||
<div class="wave tab-data" id="data-{{ observation.id }}"
|
||||
data-id="{{ observation.id }}"
|
||||
data-payload="{{ MEDIA_URL }}{{ observation.payload }}"></div>
|
||||
<div id="wave-spectrogram"></div>
|
||||
<button type="button" class="btn btn-primary btn-xs playpause">
|
||||
<span class="glyphicon glyphicon-play"></span>
|
||||
<span class="glyphicon glyphicon-pause"></span>
|
||||
</button>
|
||||
<span id="playback-time-{{ observation.id }}" class="label label-info playback-time"></span>
|
||||
{% else %}
|
||||
<div class="notice">
|
||||
Waiting for audio
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-data" id="tab-data">
|
||||
{% if observation.demoddata.all %}
|
||||
{% for demoddata in observation.demoddata.all %}
|
||||
<span class="label label-default">{{ demoddata.payload_demod }}</span>
|
||||
<div class="well well-sm data-well">
|
||||
{% if demoddata.is_image %}
|
||||
<img src="{{ demoddata.payload_demod.url }}" alt="data-{{ data.pk }}" class="img-responsive">
|
||||
{% else %}
|
||||
{{ demoddata.display_payload }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="notice">
|
||||
Waiting for demoded data
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h3>Data</h3>
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
{% if discuss_slug %}
|
||||
<h3>
|
||||
<a id="obs-discuss"
|
||||
data-slug="{{ discuss_slug }}"
|
||||
href="{% if has_comments %}{{ discuss_slug }}{% else %}{{ discuss_url }}{% endif %}"
|
||||
class="btn btn-primary" target="_blank">
|
||||
<span class="glyphicon glyphicon-comment" aria-hidden="true"></span>
|
||||
Discuss Observation
|
||||
</a>
|
||||
</h3>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for data in dataset %}
|
||||
<div class="panel panel-default observation-data" id="{{ data.id }}"
|
||||
data-start="{{ data.start|date:"U" }}"
|
||||
data-end="{{ data.end|date:"U" }}"
|
||||
data-groundstation="{{ data.ground_station }}">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% if data.is_vetted %}
|
||||
{% if data.is_verified %}
|
||||
<span class="glyphicon glyphicon-ok-sign {{ data.vetted_status }}" aria-hidden="true"
|
||||
data-toggle="tooltip" data-placement="right"
|
||||
title="Verified good on {{ data.vetted_datetime|date:"Y-m-d H:i:s" }} by {{ data.vetted_user.displayname }}"></span>
|
||||
{% else %}
|
||||
<span class="glyphicon glyphicon-remove-sign {{ data.vetted_status }}" aria-hidden="true"
|
||||
data-toggle="tooltip" data-placement="right"
|
||||
title="Verified bad on {{ data.vetted_datetime|date:"Y-m-d H:i:s" }} by {{ data.vetted_user.displayname }}"></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
Data payload <a href="{% url 'base:observation_data_view' id=data.id %}">#{{ data.id }}</a> ~
|
||||
|
||||
Ground Station:
|
||||
<a href="{% url 'base:station_view' id=data.ground_station.id %}">
|
||||
{{ data.ground_station }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<div class="vetting">
|
||||
{% if not data.is_vetted and is_vetting_user %}
|
||||
<a href="{% url 'base:data_verify' id=data.id %}">
|
||||
<button id="verify-data" type="button" class="btn btn-default btn-xs">
|
||||
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> Verify
|
||||
</button>
|
||||
</a>
|
||||
<a href="{% url 'base:data_mark_bad' id=data.id %}">
|
||||
<button id="bad-data" type="button" class="btn btn-default btn-xs">
|
||||
<span class="glyphicon glyphicon-thumbs-down" aria-hidden="true"></span> No Data
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ul class="nav nav-tabs data-tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a href="#tab-waterfall-{{ data.id }}"
|
||||
aria-controls="tab-waterfall-{{ data.id }}"
|
||||
role="tab"
|
||||
data-toggle="tab">Waterfall</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab-audio-{{ data.id }}"
|
||||
aria-controls="tab-audio-{{ data.id }}"
|
||||
role="tab"
|
||||
data-toggle="tab">Audio</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab-data-{{ data.id }}"
|
||||
aria-controls="tab-data-{{ data.id }}"
|
||||
role="tab"
|
||||
data-toggle="tab">Data</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="tab-waterfall-{{ data.id }}">
|
||||
{% if data.waterfall %}
|
||||
<div id="waterfall-{{ data.id }}" class="waterfall">
|
||||
<img class="img-responsive waterfall" src="{{ MEDIA_URL }}{{ data.waterfall }}" alt="waterfall">
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="notice">
|
||||
Waiting for waterfall
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tab-audio-{{ data.id }}">
|
||||
{% if data.payload %}
|
||||
<div id="loading-{{ data.id }}" class="notice">Loading data...</div>
|
||||
<div class="progress progress-striped active" id="progress-bar-{{ data.id }}">
|
||||
<div class="progress-bar progress-bar-info"></div>
|
||||
</div>
|
||||
<div class="wave tab-data" id="data-{{ data.id }}"
|
||||
data-id="{{ data.id }}"
|
||||
data-payload="{{ MEDIA_URL }}{{ data.payload }}"></div>
|
||||
<div id="wave-spectrogram-{{ data.id }}"></div>
|
||||
<button type="button" class="btn btn-primary btn-xs playpause">
|
||||
<span class="glyphicon glyphicon-play"></span>
|
||||
<span class="glyphicon glyphicon-pause"></span>
|
||||
</button>
|
||||
<span id="playback-time-{{ data.id }}" class="label label-info playback-time"></span>
|
||||
{% else %}
|
||||
<div class="notice">
|
||||
Waiting for audio
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-data" id="tab-data-{{ data.id }}">
|
||||
{% if data.demoddata.all %}
|
||||
{% for demoddata in data.demoddata.all %}
|
||||
<span class="label label-default">{{ demoddata.payload_demod }}</span>
|
||||
<div class="well well-sm data-well">
|
||||
{% if demoddata.is_image %}
|
||||
<img src="{{ demoddata.payload_demod.url }}" alt="data-{{ data.pk }}">
|
||||
{% else %}
|
||||
{{ demoddata.display_payload }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="notice">
|
||||
Waiting for demoded data
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span class="hidden-xs">
|
||||
{% if data.rise_azimuth %}
|
||||
Rise Az: {{ data.rise_azimuth }}°
|
||||
{% endif %}
|
||||
{% if data.max_altitude %}
|
||||
Max Alt: {{ data.max_altitude }}°
|
||||
{% endif %}
|
||||
{% if data.set_azimuth %}
|
||||
Set Az: {{ data.set_azimuth }}°
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
{% if data.payload %}
|
||||
<a href="{{ MEDIA_URL }}{{ data.payload }}"
|
||||
target="_blank"
|
||||
download="">
|
||||
<button type="button" class="btn btn-default btn-xs">
|
||||
<span class="glyphicon glyphicon-download"></span> Audio
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if data.waterfall %}
|
||||
<a href="{{ MEDIA_URL }}{{ data.waterfall }}"
|
||||
target="_blank"
|
||||
download="">
|
||||
<button type="button" class="btn btn-default btn-xs">
|
||||
<span class="glyphicon glyphicon-download"></span> Waterfall
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="pull-right hidden-xs">
|
||||
<span class="label label-default">Timeframe</span>
|
||||
{{ data.start|date:"Y-m-d H:i:s" }} - {{ data.end|date:"Y-m-d H:i:s" }}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<hr>
|
||||
|
||||
<div class="row tle-data">
|
||||
<div class="col-md-12">
|
||||
<h3>TLE used <small>by Celestrak</small></h3>
|
||||
<pre>{{ observation.tle.tle1 }}<br>{{ observation.tle.tle2 }}</pre>
|
||||
<pre>{{ observation.tle.tle1 }}<br>{{ observation.tle.tle2 }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -279,8 +266,6 @@
|
|||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'lib/d3/d3.min.js' %}"></script>
|
||||
<script src="{% static 'lib/d3-timeline/src/d3-timeline.js' %}"></script>
|
||||
<script src="{% static 'lib/wavesurfer.js/dist/wavesurfer.min.js' %}"></script>
|
||||
<script src="{% static 'lib/wavesurfer.js/dist/plugin/wavesurfer.spectrogram.min.js' %}"></script>
|
||||
<script src="{% static 'lib/moment/min/moment.min.js' %}"></script>
|
||||
|
|
|
@ -61,18 +61,18 @@
|
|||
<th>Encoding</th>
|
||||
<th>Timeframe</th>
|
||||
<th>Observer</th>
|
||||
<th>Stations</th>
|
||||
<th>Station</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for observation in observations %}
|
||||
<tr data-norad="{{ observation.satellite.norad_cat_id }}">
|
||||
<td>
|
||||
<a href="{% url 'base:observation_view' id=observation.id %}">
|
||||
{% if observation.has_verified_data %}
|
||||
{% if observation.is_verified %}
|
||||
<span class="label label-success" title="There is known good data in this observation"
|
||||
{% elif observation.is_future %}
|
||||
<span class="label label-info" title="This observation is in the future"
|
||||
{% elif observation.has_unvetted_data %}
|
||||
{% elif not observation.is_vetted %}
|
||||
<span class="label label-warning" title="There is data that needs vetting in this observation"
|
||||
{% else %}
|
||||
<span class="label label-danger" title="No good data in this observation"
|
||||
|
@ -100,12 +100,9 @@
|
|||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% regroup observation.data_set.all by ground_station as station_list %}
|
||||
{% for station in station_list %}
|
||||
<a href="{% url 'base:station_view' id=station.grouper.id %}">
|
||||
<span class="label label-primary" title="{{ station.grouper.name }}" data-toggle="tooltip">{{ station.grouper.id }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
<a href="{% url 'base:station_view' id=observation.ground_station.id %}">
|
||||
{{ observation.ground_station.name }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -37,65 +37,65 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Owner</span>
|
||||
<span class="gs-front-data">
|
||||
<span class="front-data">
|
||||
<a href="{% url 'users:view_user' username=station.owner.username %}">
|
||||
{{ station.owner.displayname }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
{% if station.qthlocator %}
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">QTH Locator</span>
|
||||
<span class="gs-front-data">
|
||||
<span class="front-data">
|
||||
{{ station.qthlocator }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if station.location %}
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Location</span>
|
||||
<span class="gs-front-data">
|
||||
<span class="front-data">
|
||||
{{ station.location }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Coordinates</span>
|
||||
<span class="gs-front-data">
|
||||
<span class="front-data">
|
||||
{{ station.lat|floatformat:-3 }}°, {{ station.lng|floatformat:-3 }}°
|
||||
</span>
|
||||
</div>
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Altitude</span>
|
||||
<span class="gs-front-data">
|
||||
<span class="front-data">
|
||||
{{ station.alt }} m
|
||||
</span>
|
||||
</div>
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Min Horizon</span>
|
||||
<span class="gs-front-data">
|
||||
<span class="front-data">
|
||||
{{ station.horizon }}°
|
||||
</span>
|
||||
</div>
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Rig</span>
|
||||
<span class="gs-front-data">
|
||||
<span class="front-data">
|
||||
{{ station.rig|default:"-" }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Creation Date</span>
|
||||
<span class="gs-front-data"
|
||||
<span class="front-data"
|
||||
title="{{ station.created|date:"c" }}">
|
||||
{{ station.created|timesince }} ago
|
||||
</span>
|
||||
</div>
|
||||
{% if station.success_rate %}
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">Success Rate</span>
|
||||
<span class="gs-front-data">
|
||||
<span class="front-data">
|
||||
<div class="progress" title="{{ station.success_rate }}%">
|
||||
<div class="gs progress-bar progress-bar-success" data-success-rate="{{ station.success_rate }}">
|
||||
<span class="sr-only">{{ station.success_rate }}% Complete (success)</span>
|
||||
|
@ -107,13 +107,13 @@
|
|||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
{% if station.online %}
|
||||
<span class="label label-success">Online</span>
|
||||
{% else %}
|
||||
<span class="label label-danger">Offline</span>
|
||||
{% endif %}
|
||||
<span class="gs-front-data"
|
||||
<span class="front-data"
|
||||
title="{{ station.last_seen|date:"c" }}">
|
||||
{% if station.last_seen %}
|
||||
Last seen {{ station.last_seen|timesince }} ago
|
||||
|
@ -125,7 +125,7 @@
|
|||
</div>
|
||||
<div class="col-md-4">
|
||||
{% for antenna in station.antenna.all %}
|
||||
<div class="gs-front-line">
|
||||
<div class="front-line">
|
||||
<span class="label label-default">{{ antenna.get_antenna_type_display }} {{ antenna.band }} Antenna</span>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
|
@ -184,53 +184,43 @@
|
|||
<th>Encoding</th>
|
||||
<th>Timeframe</th>
|
||||
<th>Observer</th>
|
||||
<th>Stations</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% regroup station.data_set.all by observation as observations %}
|
||||
{% for observation in observations|slice:":30" %}
|
||||
{% for observation in station.observations.all|slice:":30" %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'base:observation_view' id=observation.grouper.id %}">
|
||||
{% if observation.grouper.has_verified_data %}
|
||||
<a href="{% url 'base:observation_view' id=observation.id %}">
|
||||
{% if observation.is_verified %}
|
||||
<span class="label label-success" title="There is known good data in this observation"
|
||||
{% elif observation.grouper.is_future %}
|
||||
{% elif observation.is_future %}
|
||||
<span class="label label-info" title="This observation is in the future"
|
||||
{% elif observation.grouper.has_unvetted_data %}
|
||||
{% elif not observation.is_vetted %}
|
||||
<span class="label label-warning" title="There is data that needs vetting in this observation"
|
||||
{% else %}
|
||||
<span class="label label-danger" title="No good data in this observation"
|
||||
{% endif %}>
|
||||
{{ observation.grouper.id }}
|
||||
{{ observation.id }}
|
||||
</span>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" data-toggle="modal" data-target="#SatelliteModal" data-id="{{ observation.grouper.satellite.norad_cat_id }}">
|
||||
{{ observation.grouper.satellite.name }}
|
||||
<a href="#" data-toggle="modal" data-target="#SatelliteModal" data-id="{{ observation.satellite.norad_cat_id }}">
|
||||
{{ observation.satellite.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ observation.grouper.transmitter.downlink_low|frq }}</td>
|
||||
<td>{{ observation.grouper.transmitter.mode|default:"-" }}</td>
|
||||
<td>{{ observation.transmitter.downlink_low|frq }}</td>
|
||||
<td>{{ observation.transmitter.mode|default:"-" }}</td>
|
||||
<td>
|
||||
<span class="datetime-date">{{ observation.grouper.start|date:"Y-m-d" }}</span>
|
||||
<span class="datetime-time">{{ observation.grouper.start|date:"H:i:s" }}</span><br>
|
||||
<span class="datetime-date">{{ observation.grouper.end|date:"Y-m-d" }}</span>
|
||||
<span class="datetime-time">{{ observation.grouper.end|date:"H:i:s" }}</span>
|
||||
<span class="datetime-date">{{ observation.start|date:"Y-m-d" }}</span>
|
||||
<span class="datetime-time">{{ observation.start|date:"H:i:s" }}</span><br>
|
||||
<span class="datetime-date">{{ observation.end|date:"Y-m-d" }}</span>
|
||||
<span class="datetime-time">{{ observation.end|date:"H:i:s" }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'users:view_user' username=observation.grouper.author.username %}">
|
||||
{{ observation.grouper.author.displayname }}
|
||||
<a href="{% url 'users:view_user' username=observation.author.username %}">
|
||||
{{ observation.author.displayname }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% regroup observation.grouper.data_set.all by ground_station as station_list %}
|
||||
{% for station in station_list %}
|
||||
<a href="{% url 'base:station_view' id=station.grouper.id %}">
|
||||
<span class="label label-primary" title="{{ station.grouper.name }}" data-toggle="tooltip">{{ station.grouper.id }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -303,10 +293,10 @@
|
|||
</td>
|
||||
<td>
|
||||
{% if nextpass.valid and station.online and station.active %}
|
||||
<a href="{% url 'base:observation_new' %}?norad={{ nextpass.norad_cat_id }}&ground_station={{ station.id }}&start_date={{ nextpass.tr|date:"Y/m/d H:i" }}&end_date={{ nextpass.ts|date:"Y/m/d H:i" }}"
|
||||
class="btn btn-default">schedule</a>
|
||||
<a href="{% url 'base:observation_new' %}?norad={{ nextpass.norad_cat_id }}&ground_station={{ station.id }}&start_date={{ nextpass.tr|date:"Y/m/d H:i" }}&end_date={{ nextpass.ts|date:"Y/m/d H:i" }}"
|
||||
class="btn btn-default">schedule</a>
|
||||
{% else %}
|
||||
<a href="#" class="btn btn-default" disabled>schedule</a>
|
||||
<a class="btn btn-default" disabled>schedule</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -396,4 +386,5 @@
|
|||
<script src="{% static 'js/station_view.js' %}"></script>
|
||||
<script src="{% static 'js/gridsquare.js' %}"></script>
|
||||
<script src="{% static 'js/satellite.js' %}"></script>
|
||||
<script src="{% static 'js/polar.js' %}"></script>
|
||||
{% endblock javascript %}
|
||||
|
|
|
@ -111,6 +111,7 @@
|
|||
<th>Frequency</th>
|
||||
<th>Encoding</th>
|
||||
<th>Timeframe</th>
|
||||
<th>Station</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for observation in observations.all %}
|
||||
|
@ -118,14 +119,14 @@
|
|||
<td>
|
||||
<a href="{% url 'base:observation_view' id=observation.id %}">
|
||||
<span class="label
|
||||
{% if observation.has_verified_data %}
|
||||
label-success" title="There is known good data in this observation"
|
||||
{% if observation.is_verified %}
|
||||
label-success" title="There is known good data in this observation"
|
||||
{% elif observation.is_future %}
|
||||
label-info" title="This observation is in the future"
|
||||
{% elif observation.has_unvetted_data %}
|
||||
label-warning" title="There is data that needs vetting in this observation"
|
||||
label-info" title="This observation is in the future"
|
||||
{% elif not observation.is_vetted %}
|
||||
label-warning" title="There is data that needs vetting in this observation"
|
||||
{% else %}
|
||||
label-danger" title="No good data in this observation"
|
||||
label-danger" title="No good data in this observation"
|
||||
{% endif %}>
|
||||
{{ observation.id }}
|
||||
</span>
|
||||
|
@ -144,6 +145,11 @@
|
|||
<span class="datetime-date">{{ observation.end|date:"Y-m-d" }}</span>
|
||||
<span class="datetime-time">{{ observation.end|date:"H:i:s" }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'base:station_view' id=observation.ground_station.id %}">
|
||||
{{ observation.ground_station.name }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -64,9 +64,9 @@ coverage==4.4.1 \
|
|||
--hash=sha256:36aa6c8db83bc27346ddcd8c2a60846a7178ecd702672689d3ea1828eb1a4d11 \
|
||||
--hash=sha256:9824e15b387d331c0fc0fef905a539ab69784368a1d6ac3db864b4182e520948 \
|
||||
--hash=sha256:4a678e1b9619a29c51301af61ab84122e2f8cc7a0a6b40854b808ac6be604300
|
||||
Faker==0.8.0 \
|
||||
--hash=sha256:42c54a8563b671717ae153e069acf0b1c7a12de4576d4916e6958a438ef0e46d \
|
||||
--hash=sha256:f45909af78ec9ea3f67a56030d0cdadf28de75784e87c02ac5da8bf81f5c41ca
|
||||
Faker==0.8.3 \
|
||||
--hash=sha256:499fc2c238501b9846b7e5c771c54f73177f300049aa61e829a46f47e6cd7af2 \
|
||||
--hash=sha256:79b0682d59c4c6961f10edcc9453563a278640e9ffcfee21427fea7a19972bf8
|
||||
docopts==0.6.1 \
|
||||
--hash=sha256:ea8d6b03a0931c75a0e4919a0b0856f1c187c38a96174a750c86b41b903a693a
|
||||
mock==2.0.0 \
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Basic
|
||||
Django==1.11.4 \
|
||||
--hash=sha256:6fd30e05dc9af265f7d7d10cfb0efa013e6236db0853c9f47c74c585587c5a57 \
|
||||
--hash=sha256:abe86e67dda9897a1536a727ed57dbefb5a42b41943be3b116fe3edab4c07bb2
|
||||
Django==1.11.5 \
|
||||
--hash=sha256:89162f70a74aac62a53f975128faba6099a7ef2c9d8140a41ae9d6210bda05cd \
|
||||
--hash=sha256:1836878162dfdf865492bacfdff0321e4ee8f1e7d51d93192546000b54982b29
|
||||
django-shortuuidfield==0.1.3 \
|
||||
--hash=sha256:a292c0fe5538abe947b131e2b914edd9ac44afcc6a40eaec71448e6231a3ef00
|
||||
|
||||
|
|
Loading…
Reference in New Issue