Fix C0111 lint issues
Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>merge-requests/789/head
parent
07f924b907
commit
758c1dfc03
|
@ -5,7 +5,6 @@ ignored-argument-names=args|kwargs
|
|||
|
||||
[MESSAGES CONTROL]
|
||||
disable=
|
||||
C0111,
|
||||
C0412,
|
||||
E1101,
|
||||
E1121,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""SatNOGS Network Auth0 login module admin class"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# from django.contrib import admin
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""SatNOGS Network Auth0 login app config"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class Auth0LoginConfig(AppConfig):
|
||||
"""Set the name of the django app for auth0login"""
|
||||
name = 'auth0login'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network Auth0 login module auth backend"""
|
||||
import requests
|
||||
from social_core.backends.oauth import BaseOAuth2
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""SatNOGS Network Auth0 login module models"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# from django.db import models
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""SatNOGS Network Auth0 login module test suites"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# from django.test import TestCase
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network Auth0 login module URL routers"""
|
||||
from django.conf.urls import include, url
|
||||
|
||||
from . import views
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
"""SatNOGS Network Auth0 login module views"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
def index(request):
|
||||
"""Returns the index view"""
|
||||
return render(request, 'index.html')
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""The core django app for SatNOGS Network"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from ._version import get_versions
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network django rest framework Filters class"""
|
||||
import django_filters
|
||||
from django_filters.rest_framework import FilterSet
|
||||
|
||||
|
@ -5,6 +6,7 @@ from network.base.models import Observation, Station, Transmitter
|
|||
|
||||
|
||||
class ObservationViewFilter(FilterSet):
|
||||
"""SatNOGS Network Observation API View Filter"""
|
||||
start = django_filters.IsoDateTimeFilter(name='start', lookup_expr='gte')
|
||||
end = django_filters.IsoDateTimeFilter(name='end', lookup_expr='lte')
|
||||
|
||||
|
@ -17,12 +19,14 @@ class ObservationViewFilter(FilterSet):
|
|||
|
||||
|
||||
class StationViewFilter(FilterSet):
|
||||
"""SatNOGS Network Station API View Filter"""
|
||||
class Meta:
|
||||
model = Station
|
||||
fields = ['id', 'name', 'status', 'client_version']
|
||||
|
||||
|
||||
class TransmitterViewFilter(FilterSet):
|
||||
"""SatNOGS Network Transmitter API View Filter"""
|
||||
class Meta:
|
||||
model = Transmitter
|
||||
fields = ['uuid', 'sync_to_db']
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network API permissions, django rest framework"""
|
||||
from rest_framework import permissions
|
||||
|
||||
from network.base.perms import schedule_perms
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network API serializers, django rest framework"""
|
||||
from rest_framework import serializers
|
||||
|
||||
from network.base.db_api import DBConnectionError, \
|
||||
|
@ -14,12 +15,14 @@ from network.base.validators import ObservationOverlapError, OutOfRangeError, \
|
|||
|
||||
|
||||
class DemodDataSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS Network DemodData API Serializer"""
|
||||
class Meta:
|
||||
model = DemodData
|
||||
fields = ('payload_demod', )
|
||||
|
||||
|
||||
class ObservationSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS Network Observation API Serializer"""
|
||||
transmitter = serializers.SerializerMethodField()
|
||||
transmitter_updated = serializers.SerializerMethodField()
|
||||
norad_cat_id = serializers.SerializerMethodField()
|
||||
|
@ -54,44 +57,52 @@ class ObservationSerializer(serializers.ModelSerializer):
|
|||
]
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""Updates observation object with validated data"""
|
||||
validated_data.pop('demoddata')
|
||||
super(ObservationSerializer, self).update(instance, validated_data)
|
||||
return instance
|
||||
|
||||
def get_transmitter(self, obj):
|
||||
"""Returns Transmitter UUID"""
|
||||
try:
|
||||
return obj.transmitter_uuid
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
def get_transmitter_updated(self, obj):
|
||||
"""Returns Transmitter last update date"""
|
||||
try:
|
||||
return obj.transmitter_created
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
def get_norad_cat_id(self, obj):
|
||||
"""Returns Satellite NORAD ID"""
|
||||
return obj.satellite.norad_cat_id
|
||||
|
||||
def get_station_name(self, obj):
|
||||
"""Returns Station name"""
|
||||
try:
|
||||
return obj.ground_station.name
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def get_station_lat(self, obj):
|
||||
"""Returns Station latitude"""
|
||||
try:
|
||||
return obj.ground_station.lat
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def get_station_lng(self, obj):
|
||||
"""Returns Station longitude"""
|
||||
try:
|
||||
return obj.ground_station.lng
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def get_station_alt(self, obj):
|
||||
"""Returns Station elevation"""
|
||||
try:
|
||||
return obj.ground_station.alt
|
||||
except AttributeError:
|
||||
|
@ -99,7 +110,9 @@ class ObservationSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class NewObservationListSerializer(serializers.ListSerializer):
|
||||
"""SatNOGS Network New Observation API List Serializer"""
|
||||
def validate(self, attrs):
|
||||
"""Validates data from a list of new observations"""
|
||||
user = self.context['request'].user
|
||||
station_list = []
|
||||
transmitter_uuid_list = []
|
||||
|
@ -151,6 +164,7 @@ class NewObservationListSerializer(serializers.ListSerializer):
|
|||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Creates new observations from a list of new observations validated data"""
|
||||
new_observations = []
|
||||
for observation_data in validated_data:
|
||||
station = observation_data['ground_station']
|
||||
|
@ -170,10 +184,15 @@ class NewObservationListSerializer(serializers.ListSerializer):
|
|||
return new_observations
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""Updates observations from a list of validated data
|
||||
|
||||
currently disabled and returns None
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class NewObservationSerializer(serializers.Serializer):
|
||||
"""SatNOGS Network New Observation API Serializer"""
|
||||
start = serializers.DateTimeField(
|
||||
input_formats=['%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S'],
|
||||
error_messages={
|
||||
|
@ -212,6 +231,7 @@ class NewObservationSerializer(serializers.Serializer):
|
|||
)
|
||||
|
||||
def validate_start(self, value):
|
||||
"""Validates start datetime of a new observation"""
|
||||
try:
|
||||
check_start_datetime(value)
|
||||
except ValueError as error:
|
||||
|
@ -219,6 +239,7 @@ class NewObservationSerializer(serializers.Serializer):
|
|||
return value
|
||||
|
||||
def validate_end(self, value):
|
||||
"""Validates end datetime of a new observation"""
|
||||
try:
|
||||
check_end_datetime(value)
|
||||
except ValueError as error:
|
||||
|
@ -226,6 +247,7 @@ class NewObservationSerializer(serializers.Serializer):
|
|||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
"""Validates combination of start and end datetimes of a new observation"""
|
||||
start = attrs['start']
|
||||
end = attrs['end']
|
||||
try:
|
||||
|
@ -235,15 +257,23 @@ class NewObservationSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
# If in the future we want to implement this serializer accepting and creating observation
|
||||
# from single object instead from a list of objects, we should remove raising the exception
|
||||
# below and implement the validations that exist now only on NewObservationListSerializer
|
||||
"""Creates a new observation
|
||||
|
||||
Currently not implemented and raises exception. If in the future we want to implement this
|
||||
serializer accepting and creating observation from single object instead from a list of
|
||||
objects, we should remove raising the exception below and implement the validations that
|
||||
exist now only on NewObservationListSerializer
|
||||
"""
|
||||
raise serializers.ValidationError(
|
||||
"Serializer is implemented for accepting and schedule\
|
||||
only lists of observations"
|
||||
)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""Updates an observation from validated data
|
||||
|
||||
currently disabled and returns None
|
||||
"""
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
|
@ -251,12 +281,14 @@ class NewObservationSerializer(serializers.Serializer):
|
|||
|
||||
|
||||
class AntennaSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS Network Antenna API Serializer"""
|
||||
class Meta:
|
||||
model = Antenna
|
||||
fields = ('frequency', 'frequency_max', 'band', 'antenna_type')
|
||||
|
||||
|
||||
class StationSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS Network Station API Serializer"""
|
||||
antenna = AntennaSerializer(many=True)
|
||||
altitude = serializers.SerializerMethodField()
|
||||
min_horizon = serializers.SerializerMethodField()
|
||||
|
@ -272,13 +304,17 @@ class StationSerializer(serializers.ModelSerializer):
|
|||
)
|
||||
|
||||
def get_altitude(self, obj):
|
||||
"""Returns Station elevation"""
|
||||
return obj.alt
|
||||
|
||||
def get_min_horizon(self, obj):
|
||||
"""Returns Station minimum horizon"""
|
||||
return obj.horizon
|
||||
|
||||
def get_antenna(self, obj):
|
||||
"""Returns Station antenna list"""
|
||||
def antenna_name(antenna):
|
||||
"""Returns Station antenna"""
|
||||
return antenna.band + " " + antenna.get_antenna_type_display()
|
||||
|
||||
try:
|
||||
|
@ -287,12 +323,14 @@ class StationSerializer(serializers.ModelSerializer):
|
|||
return None
|
||||
|
||||
def get_observations(self, obj):
|
||||
"""Returns Station observations number"""
|
||||
try:
|
||||
return obj.observations_count
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def get_status(self, obj):
|
||||
"""Returns Station status"""
|
||||
try:
|
||||
return obj.get_status_display()
|
||||
except AttributeError:
|
||||
|
@ -300,6 +338,7 @@ class StationSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class JobSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS Network Job API Serializer"""
|
||||
frequency = serializers.SerializerMethodField()
|
||||
tle0 = serializers.SerializerMethodField()
|
||||
tle1 = serializers.SerializerMethodField()
|
||||
|
@ -316,6 +355,7 @@ class JobSerializer(serializers.ModelSerializer):
|
|||
)
|
||||
|
||||
def get_frequency(self, obj):
|
||||
"""Returns Transmitter downlink low frequency"""
|
||||
frequency = obj.transmitter_downlink_low
|
||||
frequency_drift = obj.transmitter_downlink_drift
|
||||
if frequency_drift is None:
|
||||
|
@ -323,28 +363,35 @@ class JobSerializer(serializers.ModelSerializer):
|
|||
return int(round(frequency + ((frequency * frequency_drift) / float(pow(10, 9)))))
|
||||
|
||||
def get_transmitter(self, obj):
|
||||
"""Returns Transmitter UUID"""
|
||||
return obj.transmitter_uuid
|
||||
|
||||
def get_tle0(self, obj):
|
||||
"""Returns line 0 of TLE"""
|
||||
return obj.tle.tle0
|
||||
|
||||
def get_tle1(self, obj):
|
||||
"""Returns line 1 of TLE"""
|
||||
return obj.tle.tle1
|
||||
|
||||
def get_tle2(self, obj):
|
||||
"""Returns line 2 of TLE"""
|
||||
return obj.tle.tle2
|
||||
|
||||
def get_mode(self, obj):
|
||||
"""Returns Transmitter mode"""
|
||||
try:
|
||||
return obj.transmitter_mode
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
def get_baud(self, obj):
|
||||
"""Returns Transmitter baudrate"""
|
||||
return obj.transmitter_baud
|
||||
|
||||
|
||||
class TransmitterSerializer(serializers.ModelSerializer):
|
||||
"""SatNOGS Network Transmitter API Serializer"""
|
||||
stats = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
|
@ -352,6 +399,7 @@ class TransmitterSerializer(serializers.ModelSerializer):
|
|||
fields = ('uuid', 'sync_to_db', 'stats')
|
||||
|
||||
def get_stats(self, obj):
|
||||
"""Returns Transmitter statistics"""
|
||||
stats = transmitter_stats_by_uuid(obj.uuid)
|
||||
for statistic in stats:
|
||||
stats[statistic] = int(stats[statistic])
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network API test suites"""
|
||||
import json
|
||||
|
||||
from django.test import TestCase
|
||||
|
@ -25,6 +26,7 @@ class JobViewApiTest(TestCase):
|
|||
self.observation = ObservationFactory()
|
||||
|
||||
def test_job_view_api(self):
|
||||
"""Test the Job View API"""
|
||||
response = self.client.get('/api/jobs/')
|
||||
response_json = json.loads(response.content)
|
||||
self.assertEqual(response_json, [])
|
||||
|
@ -43,6 +45,7 @@ class StationViewApiTest(TestCase):
|
|||
self.station = StationFactory.create(antennas=[self.antenna])
|
||||
|
||||
def test_station_view_api(self):
|
||||
"""Test the Station View API"""
|
||||
|
||||
ants = self.station.antenna.all()
|
||||
ser_ants = [
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network django rest framework API url routings"""
|
||||
from rest_framework import routers
|
||||
|
||||
from network.api import views
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network API django rest framework Views"""
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.timezone import now
|
||||
|
@ -14,17 +15,20 @@ from network.base.validators import NegativeElevationError, \
|
|||
|
||||
class ObservationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin,
|
||||
mixins.CreateModelMixin, viewsets.GenericViewSet):
|
||||
"""SatNOGS Network Observation API view class"""
|
||||
queryset = Observation.objects.all()
|
||||
filter_class = filters.ObservationViewFilter
|
||||
permission_classes = [StationOwnerPermission]
|
||||
pagination_class = pagination.LinkedHeaderPageNumberPagination
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""Returns the right serializer depending on http method that is used"""
|
||||
if self.request.method == 'POST':
|
||||
return serializers.NewObservationSerializer
|
||||
return serializers.ObservationSerializer
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""Creates observations from a list of observation data"""
|
||||
serializer = self.get_serializer(data=request.data, many=True, allow_empty=False)
|
||||
try:
|
||||
if serializer.is_valid():
|
||||
|
@ -53,6 +57,7 @@ class ObservationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.U
|
|||
return response
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
"""Updates observation with audio, waterfall or demoded data"""
|
||||
instance = self.get_object()
|
||||
if request.data.get('client_version'):
|
||||
instance.ground_station.client_version = request.data.get('client_version')
|
||||
|
@ -83,6 +88,7 @@ class ObservationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.U
|
|||
|
||||
|
||||
class StationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
|
||||
"""SatNOGS Network Station API view class"""
|
||||
queryset = Station.objects.all()
|
||||
serializer_class = serializers.StationSerializer
|
||||
filter_class = filters.StationViewFilter
|
||||
|
@ -90,6 +96,7 @@ class StationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.Gen
|
|||
|
||||
|
||||
class TransmitterView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
|
||||
"""SatNOGS Network Transmitter API view class"""
|
||||
queryset = Transmitter.objects.all().order_by('uuid')
|
||||
serializer_class = serializers.TransmitterSerializer
|
||||
filter_class = filters.TransmitterViewFilter
|
||||
|
@ -97,12 +104,14 @@ class TransmitterView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets
|
|||
|
||||
|
||||
class JobView(viewsets.ReadOnlyModelViewSet):
|
||||
"""SatNOGS Network Job API view class"""
|
||||
queryset = Observation.objects.filter(payload='')
|
||||
serializer_class = serializers.JobSerializer
|
||||
filter_class = filters.ObservationViewFilter
|
||||
filter_fields = ('ground_station')
|
||||
|
||||
def get_queryset(self):
|
||||
"""Returns queryset for Job API view"""
|
||||
queryset = self.queryset.filter(start__gte=now())
|
||||
ground_station_id = self.request.query_params.get('ground_station', None)
|
||||
if ground_station_id and self.request.user.is_authenticated():
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Define functions and settings for the django admin base interface"""
|
||||
from django.contrib import admin
|
||||
|
||||
from network.base.models import Antenna, DemodData, Observation, Satellite, \
|
||||
|
@ -7,6 +8,7 @@ from network.base.utils import export_as_csv, export_station_status
|
|||
|
||||
@admin.register(Antenna)
|
||||
class AntennaAdmin(admin.ModelAdmin):
|
||||
"""Define Antenna view in django admin UI"""
|
||||
list_display = (
|
||||
'id',
|
||||
'__unicode__',
|
||||
|
@ -19,14 +21,17 @@ class AntennaAdmin(admin.ModelAdmin):
|
|||
)
|
||||
|
||||
def antenna_count(self, obj):
|
||||
"""Return the number of antennas"""
|
||||
return obj.stations.all().count()
|
||||
|
||||
def station_list(self, obj):
|
||||
"""Return stations that use the antenna"""
|
||||
return ",\n".join([str(s.id) for s in obj.stations.all()])
|
||||
|
||||
|
||||
@admin.register(Station)
|
||||
class StationAdmin(admin.ModelAdmin):
|
||||
"""Define Station view in django admin UI"""
|
||||
list_display = (
|
||||
'id', 'name', 'owner', 'get_email', 'lng', 'lat', 'qthlocator', 'client_version',
|
||||
'created_date', 'state', 'target_utilization'
|
||||
|
@ -39,15 +44,18 @@ class StationAdmin(admin.ModelAdmin):
|
|||
export_station_status.short_description = "Export selected status"
|
||||
|
||||
def created_date(self, obj):
|
||||
"""Return when the station was created"""
|
||||
return obj.created.strftime('%d.%m.%Y, %H:%M')
|
||||
|
||||
def get_email(self, obj):
|
||||
"""Return station owner email address"""
|
||||
return obj.owner.email
|
||||
|
||||
get_email.admin_order_field = 'email'
|
||||
get_email.short_description = 'Owner Email'
|
||||
|
||||
def get_actions(self, request):
|
||||
"""Return the list of actions for station admin view"""
|
||||
actions = super(StationAdmin, self).get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
|
@ -56,6 +64,7 @@ class StationAdmin(admin.ModelAdmin):
|
|||
|
||||
@admin.register(Satellite)
|
||||
class SatelliteAdmin(admin.ModelAdmin):
|
||||
"""Define Satellite view in django admin UI"""
|
||||
list_display = ('id', 'name', 'norad_cat_id', 'manual_tle', 'norad_follow_id', 'status')
|
||||
list_filter = (
|
||||
'status',
|
||||
|
@ -67,15 +76,18 @@ class SatelliteAdmin(admin.ModelAdmin):
|
|||
|
||||
@admin.register(Tle)
|
||||
class TleAdmin(admin.ModelAdmin):
|
||||
"""Define TLE view in django admin UI"""
|
||||
list_display = ('satellite_name', 'tle0', 'tle1', 'updated')
|
||||
list_filter = ('satellite__name', )
|
||||
|
||||
def satellite_name(self, obj):
|
||||
"""Return the satellite name"""
|
||||
return obj.satellite.name
|
||||
|
||||
|
||||
@admin.register(Transmitter)
|
||||
class TransmitterAdmin(admin.ModelAdmin):
|
||||
"""Define Transmitter view in django admin UI"""
|
||||
list_display = ('uuid', 'sync_to_db')
|
||||
search_fields = ('uuid', )
|
||||
list_filter = ('sync_to_db', )
|
||||
|
@ -83,12 +95,13 @@ class TransmitterAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
class DemodDataInline(admin.TabularInline):
|
||||
"""Defines DemodData inline template for use in Observation view in django admin UI"""
|
||||
"""Define DemodData inline template for use in Observation view in django admin UI"""
|
||||
model = DemodData
|
||||
|
||||
|
||||
@admin.register(Observation)
|
||||
class ObservationAdmin(admin.ModelAdmin):
|
||||
"""Define Observation view in django admin UI"""
|
||||
list_display = ('id', 'author', 'satellite', 'transmitter_uuid', 'start', 'end')
|
||||
list_filter = ('start', 'end')
|
||||
search_fields = ('satellite', 'author')
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network django context processors"""
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.timezone import now
|
||||
|
@ -20,6 +21,7 @@ def stage_notice(request):
|
|||
|
||||
|
||||
def user_processor(request):
|
||||
"""Returns number of user's unvetted observations."""
|
||||
if request.user.is_authenticated():
|
||||
owner_vetting_count = Observation.objects.filter(
|
||||
author=request.user, vetted_status='unknown', end__lt=now()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network functions that consume DB API"""
|
||||
import requests
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -5,10 +6,12 @@ DB_API_URL = settings.DB_API_ENDPOINT
|
|||
|
||||
|
||||
class DBConnectionError(Exception):
|
||||
"""Error when there are connection issues with DB API"""
|
||||
pass
|
||||
|
||||
|
||||
def transmitters_api_request(url):
|
||||
"""Perform transmitter query on SatNOGS DB API and return the results"""
|
||||
if not DB_API_URL:
|
||||
raise DBConnectionError('Error in DB API connection. Blank DB API URL!')
|
||||
try:
|
||||
|
@ -19,26 +22,31 @@ def transmitters_api_request(url):
|
|||
|
||||
|
||||
def get_transmitter_by_uuid(uuid):
|
||||
"""Returns transmitter filtered by Transmitter UUID"""
|
||||
transmitters_url = "{}transmitters/?uuid={}".format(DB_API_URL, uuid)
|
||||
return transmitters_api_request(transmitters_url)
|
||||
|
||||
|
||||
def get_transmitters_by_norad_id(norad_id):
|
||||
"""Returns transmitters filtered by NORAD ID"""
|
||||
transmitters_url = "{}transmitters/?satellite__norad_cat_id={}".format(DB_API_URL, norad_id)
|
||||
return transmitters_api_request(transmitters_url)
|
||||
|
||||
|
||||
def get_transmitters_by_status(status):
|
||||
"""Returns transmitters filtered by status"""
|
||||
transmitters_url = "{}transmitters/?status={}".format(DB_API_URL, status)
|
||||
return transmitters_api_request(transmitters_url)
|
||||
|
||||
|
||||
def get_transmitters():
|
||||
"""Returns all transmitters"""
|
||||
transmitters_url = "{}transmitters".format(DB_API_URL)
|
||||
return transmitters_api_request(transmitters_url)
|
||||
|
||||
|
||||
def get_transmitters_by_uuid_list(uuid_list):
|
||||
"""Returns transmitters filtered by Transmitter UUID list"""
|
||||
if not uuid_list:
|
||||
raise ValueError('Expected a non empty list of UUIDs.')
|
||||
if len(uuid_list) == 1:
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
"""SatNOGS Network base decorators"""
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.shortcuts import redirect
|
||||
|
||||
|
||||
def admin_required(function):
|
||||
"""Decorator for requiring admin permission"""
|
||||
def wrap(request, *args, **kwargs):
|
||||
"""Wrap function of decorator"""
|
||||
if not request.user.is_authenticated():
|
||||
return redirect(reverse('account_login'))
|
||||
if request.user.is_superuser:
|
||||
|
@ -15,7 +18,9 @@ def admin_required(function):
|
|||
|
||||
|
||||
def ajax_required(function):
|
||||
"""Decorator for requiring request to be and ajax one"""
|
||||
def wrap(request, *args, **kwargs):
|
||||
"""Wrap function of decorator"""
|
||||
if not request.is_ajax():
|
||||
return HttpResponseBadRequest()
|
||||
return function(request, *args, **kwargs)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network django base Forms class"""
|
||||
from django import forms
|
||||
|
||||
from network.base.db_api import DBConnectionError, \
|
||||
|
@ -11,7 +12,7 @@ from network.base.validators import ObservationOverlapError, OutOfRangeError, \
|
|||
|
||||
|
||||
class ObservationForm(forms.ModelForm):
|
||||
|
||||
"""Model Form class for Observation objects"""
|
||||
start = forms.DateTimeField(
|
||||
input_formats=['%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S'],
|
||||
error_messages={
|
||||
|
@ -41,6 +42,7 @@ class ObservationForm(forms.ModelForm):
|
|||
)
|
||||
|
||||
def clean_start(self):
|
||||
"""Validates start datetime of a new observation"""
|
||||
start = self.cleaned_data['start']
|
||||
try:
|
||||
check_start_datetime(start)
|
||||
|
@ -49,6 +51,7 @@ class ObservationForm(forms.ModelForm):
|
|||
return start
|
||||
|
||||
def clean_end(self):
|
||||
"""Validates end datetime of a new observation"""
|
||||
end = self.cleaned_data['end']
|
||||
try:
|
||||
check_end_datetime(end)
|
||||
|
@ -57,6 +60,7 @@ class ObservationForm(forms.ModelForm):
|
|||
return end
|
||||
|
||||
def clean(self):
|
||||
"""Validates combination of start and end datetimes of a new observation"""
|
||||
if any(self.errors):
|
||||
# If there are errors in fields validation no need for validating the form
|
||||
return
|
||||
|
@ -75,11 +79,14 @@ class ObservationForm(forms.ModelForm):
|
|||
|
||||
|
||||
class BaseObservationFormSet(forms.BaseFormSet):
|
||||
"""Base FormSet class for Observation objects forms"""
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
"""Initializes Observation FormSet"""
|
||||
self.user = user
|
||||
super(BaseObservationFormSet, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
"""Validates Observation FormSet data"""
|
||||
if any(self.errors):
|
||||
# If there are errors in forms validation no need for validating the formset
|
||||
return
|
||||
|
@ -135,6 +142,7 @@ class BaseObservationFormSet(forms.BaseFormSet):
|
|||
|
||||
|
||||
class StationForm(forms.ModelForm):
|
||||
"""Model Form class for Station objects"""
|
||||
class Meta:
|
||||
model = Station
|
||||
fields = [
|
||||
|
@ -145,6 +153,7 @@ class StationForm(forms.ModelForm):
|
|||
|
||||
|
||||
class SatelliteFilterForm(forms.Form):
|
||||
"""Form class for Satellite objects"""
|
||||
norad = forms.IntegerField(required=False)
|
||||
start = forms.CharField(required=False)
|
||||
end = forms.CharField(required=False)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network django management command to fetch data (Satellites and Transmitters)"""
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
@ -6,7 +7,8 @@ from network.base.models import Satellite, Transmitter
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Fetch Modes, Satellites and Transmitters from satnogs-db'
|
||||
"""Django management command to fetch Satellites and Transmitters from SatNOGS DB"""
|
||||
help = 'Fetches Satellites and Transmitters from SaTNOGS DB'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
db_api_url = settings.DB_API_ENDPOINT
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network django management command to fetch TLEs"""
|
||||
from optparse import make_option
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
@ -7,6 +8,7 @@ from network.base.models import Satellite
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Django management command to update/insert TLEs for certain Satellites"""
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option(
|
||||
'--delete', action='store_true', dest='delete', default=False, help='Delete Satellite'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network django management command to initialize a new database"""
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
@ -7,6 +8,7 @@ from network.base.tests import DemodDataFactory, ObservationFactory, \
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Django management command to initialize a new database"""
|
||||
help = 'Create initial fixtures'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
"""SatNOGS Network django management command to update TLE entries"""
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from network.base.tasks import update_all_tle
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Django management command to update TLE entries"""
|
||||
help = 'Update TLEs for existing Satellites'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network django management command to set last seen value on stations entries"""
|
||||
from django.core.management.base import LabelCommand
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
@ -5,6 +6,7 @@ from network.base.models import Station
|
|||
|
||||
|
||||
class Command(LabelCommand):
|
||||
"""Django management command to set last seen value on stations entries"""
|
||||
args = '<Station IDs>'
|
||||
help = 'Updates Last_Seen Timestamp for given Stations'
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
"""Django base manager for SatNOGS Network"""
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
class ObservationManager(models.QuerySet):
|
||||
"""Observation Manager with extra functionality"""
|
||||
def is_future(self):
|
||||
"""Return future observations"""
|
||||
return self.filter(end__gte=now())
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Django database base model for SatNOGS Network"""
|
||||
import logging
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
@ -56,10 +57,12 @@ TRANSMITTER_TYPE = ['Transmitter', 'Transceiver', 'Transponder']
|
|||
|
||||
|
||||
def _name_obs_files(instance, filename):
|
||||
"""Return a filepath formatted by Observation ID"""
|
||||
return 'data_obs/{0}/{1}'.format(instance.id, filename)
|
||||
|
||||
|
||||
def _name_obs_demoddata(instance, filename):
|
||||
"""Return a filepath for DemodData formatted by Observation ID"""
|
||||
# On change of the string bellow, change it also at api/views.py
|
||||
return 'data_obs/{0}/{1}'.format(instance.observation.id, filename)
|
||||
|
||||
|
@ -67,7 +70,7 @@ def _name_obs_demoddata(instance, filename):
|
|||
def _observation_post_save(sender, instance, created, **kwargs): # pylint: disable=W0613
|
||||
"""
|
||||
Post save Observation operations
|
||||
* Auto vet as good obserfvation with Demod Data
|
||||
* Auto vet as good observation with DemodData
|
||||
* Mark Observations from testing stations
|
||||
* Update client version for ground station
|
||||
"""
|
||||
|
@ -116,6 +119,7 @@ def _tle_post_save(sender, instance, created, **kwargs): # pylint: disable=W061
|
|||
|
||||
|
||||
def validate_image(fieldfile_obj):
|
||||
"""Validates image size"""
|
||||
filesize = fieldfile_obj.file.size
|
||||
megabyte_limit = 2.0
|
||||
if filesize > megabyte_limit * 1024 * 1024:
|
||||
|
@ -181,12 +185,14 @@ class Station(models.Model):
|
|||
ordering = ['-status']
|
||||
|
||||
def get_image(self):
|
||||
"""Return the image of the station or the default image if there is a defined one"""
|
||||
if self.image and hasattr(self.image, 'url'):
|
||||
return self.image.url
|
||||
return settings.STATION_DEFAULT_IMAGE
|
||||
|
||||
@property
|
||||
def is_online(self):
|
||||
"""Return true if station is online"""
|
||||
try:
|
||||
heartbeat = self.last_seen + timedelta(minutes=int(settings.STATION_HEARTBEAT_TIME))
|
||||
return heartbeat > now()
|
||||
|
@ -195,16 +201,19 @@ class Station(models.Model):
|
|||
|
||||
@property
|
||||
def is_offline(self):
|
||||
"""Return true if station is offline"""
|
||||
return not self.is_online
|
||||
|
||||
@property
|
||||
def is_testing(self):
|
||||
"""Return true if station is online and in testing mode"""
|
||||
if self.is_online:
|
||||
if self.status == 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
def state(self):
|
||||
"""Return the station status in html format"""
|
||||
if not self.status:
|
||||
return format_html('<span style="color:red;">Offline</span>')
|
||||
if self.status == 1:
|
||||
|
@ -213,6 +222,7 @@ class Station(models.Model):
|
|||
|
||||
@property
|
||||
def success_rate(self):
|
||||
"""Return the success rate of the station - successful observation over failed ones"""
|
||||
rate = cache.get('station-{0}-rate'.format(self.id))
|
||||
if not rate:
|
||||
observations = self.observations.exclude(testing=True).exclude(vetted_status="unknown")
|
||||
|
@ -228,16 +238,19 @@ class Station(models.Model):
|
|||
|
||||
@property
|
||||
def observations_count(self):
|
||||
"""Return the number of station's observations"""
|
||||
count = self.observations.all().count()
|
||||
return count
|
||||
|
||||
@property
|
||||
def observations_future_count(self):
|
||||
"""Return the number of future station's observations"""
|
||||
count = self.observations.is_future().count()
|
||||
return count
|
||||
|
||||
@property
|
||||
def apikey(self):
|
||||
"""Return station owner API key"""
|
||||
try:
|
||||
token = Token.objects.get(user=self.owner)
|
||||
except Token.DoesNotExist:
|
||||
|
@ -252,6 +265,7 @@ post_save.connect(_station_post_save, sender=Station)
|
|||
|
||||
|
||||
class StationStatusLog(models.Model):
|
||||
"""Model for keeping Status log for Station."""
|
||||
station = models.ForeignKey(
|
||||
Station, related_name='station_logs', on_delete=models.CASCADE, null=True, blank=True
|
||||
)
|
||||
|
@ -281,6 +295,7 @@ class Satellite(models.Model):
|
|||
ordering = ['norad_cat_id']
|
||||
|
||||
def get_image(self):
|
||||
"""Return the station image or the default if doesn't exist one"""
|
||||
if self.image:
|
||||
return self.image
|
||||
return settings.SATELLITE_DEFAULT_IMAGE
|
||||
|
@ -290,6 +305,7 @@ class Satellite(models.Model):
|
|||
|
||||
|
||||
class Tle(models.Model):
|
||||
"""Model for TLEs."""
|
||||
tle0 = models.CharField(max_length=100, blank=True)
|
||||
tle1 = models.CharField(max_length=200, blank=True)
|
||||
tle2 = models.CharField(max_length=200, blank=True)
|
||||
|
@ -307,6 +323,7 @@ class Tle(models.Model):
|
|||
|
||||
@property
|
||||
def str_array(self):
|
||||
"""Return TLE in string array format"""
|
||||
# tle fields are unicode, pyephem and others expect python strings
|
||||
return [str(self.tle0), str(self.tle1), str(self.tle2)]
|
||||
|
||||
|
@ -395,34 +412,41 @@ class Observation(models.Model):
|
|||
|
||||
@property
|
||||
def is_past(self):
|
||||
"""Return true if observation is in the past (end time is in the past)"""
|
||||
return self.end < now()
|
||||
|
||||
@property
|
||||
def is_future(self):
|
||||
"""Return true if observation is in the future (end time is in the future)"""
|
||||
return self.end > now()
|
||||
|
||||
@property
|
||||
def is_started(self):
|
||||
"""Return true if observation has started (start time is in the past)"""
|
||||
return self.start < now()
|
||||
|
||||
# this payload has been vetted good/bad/failed by someone
|
||||
@property
|
||||
def is_vetted(self):
|
||||
"""Return true if observation is vetted"""
|
||||
return not self.vetted_status == 'unknown'
|
||||
|
||||
# this payload has been vetted as good by someone
|
||||
@property
|
||||
def is_good(self):
|
||||
"""Return true if observation is vetted as good"""
|
||||
return self.vetted_status == 'good'
|
||||
|
||||
# this payload has been vetted as bad by someone
|
||||
@property
|
||||
def is_bad(self):
|
||||
"""Return true if observation is vetted as bad"""
|
||||
return self.vetted_status == 'bad'
|
||||
|
||||
# this payload has been vetted as failed by someone
|
||||
@property
|
||||
def is_failed(self):
|
||||
"""Return true if observation is vetted as failed"""
|
||||
return self.vetted_status == 'failed'
|
||||
|
||||
@property
|
||||
|
@ -458,6 +482,7 @@ class Observation(models.Model):
|
|||
|
||||
@property
|
||||
def audio_url(self):
|
||||
"""Return url for observation's audio file"""
|
||||
if self.has_audio:
|
||||
if self.archive_url:
|
||||
try:
|
||||
|
@ -479,11 +504,13 @@ class Observation(models.Model):
|
|||
return str(self.id)
|
||||
|
||||
def get_absolute_url(self):
|
||||
"""Return absolute url of the model object"""
|
||||
return reverse('base:observation_view', kwargs={'observation_id': self.id})
|
||||
|
||||
|
||||
@receiver(models.signals.post_delete, sender=Observation)
|
||||
def observation_remove_files(sender, instance, **kwargs): # pylint: disable=W0613
|
||||
"""Remove audio and waterfall files of an observation if the observation is deleted"""
|
||||
if instance.payload:
|
||||
if os.path.isfile(instance.payload.path):
|
||||
os.remove(instance.payload.path)
|
||||
|
@ -496,6 +523,7 @@ post_save.connect(_observation_post_save, sender=Observation)
|
|||
|
||||
|
||||
class DemodData(models.Model):
|
||||
"""Model for DemodData."""
|
||||
observation = models.ForeignKey(
|
||||
Observation, related_name='demoddata', on_delete=models.CASCADE
|
||||
)
|
||||
|
@ -503,6 +531,7 @@ class DemodData(models.Model):
|
|||
copied_to_db = models.BooleanField(default=False)
|
||||
|
||||
def is_image(self):
|
||||
"""Return true if data file is an image"""
|
||||
with open(self.payload_demod.path) as file_path:
|
||||
try:
|
||||
Image.open(file_path)
|
||||
|
@ -512,6 +541,7 @@ class DemodData(models.Model):
|
|||
return True
|
||||
|
||||
def display_payload(self):
|
||||
"""Return the content of the data file"""
|
||||
with open(self.payload_demod.path) as file_path:
|
||||
payload = file_path.read()
|
||||
try:
|
||||
|
@ -523,6 +553,7 @@ class DemodData(models.Model):
|
|||
|
||||
@receiver(models.signals.post_delete, sender=DemodData)
|
||||
def demoddata_remove_files(sender, instance, **kwargs): # pylint: disable=W0613
|
||||
"""Remove data file of an observation if the observation is deleted"""
|
||||
if instance.payload_demod:
|
||||
if os.path.isfile(instance.payload_demod.path):
|
||||
os.remove(instance.payload_demod.path)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
"""SatNOGS Network base permissions"""
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
|
||||
class UserNoPermissionError(Exception):
|
||||
"""Error when user has not persmission"""
|
||||
pass
|
||||
|
||||
|
||||
|
@ -56,6 +58,7 @@ def schedule_station_perms(user, station):
|
|||
|
||||
|
||||
def check_schedule_perms_per_station(user, station_list):
|
||||
"""Checks if user has permissions to schedule on stations"""
|
||||
stations_without_permissions = [
|
||||
int(s.id) for s in station_list if not schedule_station_perms(user, s)
|
||||
]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network scheduling functions"""
|
||||
import math
|
||||
from datetime import timedelta
|
||||
|
||||
|
@ -12,6 +13,7 @@ from network.base.validators import NegativeElevationError, \
|
|||
|
||||
|
||||
def get_altitude(observer, satellite, date):
|
||||
"""Returns altitude of satellite in a specific date for a specific observer"""
|
||||
observer = observer.copy()
|
||||
satellite = satellite.copy()
|
||||
observer.date = date
|
||||
|
@ -20,6 +22,7 @@ def get_altitude(observer, satellite, date):
|
|||
|
||||
|
||||
def get_azimuth(observer, satellite, date):
|
||||
"""Returns azimuth of satellite in a specific date for a specific observer"""
|
||||
observer = observer.copy()
|
||||
satellite = satellite.copy()
|
||||
observer.date = date
|
||||
|
@ -28,10 +31,12 @@ def get_azimuth(observer, satellite, date):
|
|||
|
||||
|
||||
def over_min_duration(duration):
|
||||
"""Returns if duration is bigger than the minimum one set in settings"""
|
||||
return duration > settings.OBSERVATION_DURATION_MIN
|
||||
|
||||
|
||||
def max_altitude_in_window(observer, satellite, pass_tca, window_start, window_end):
|
||||
"""Finds the maximum altitude of a satellite during a certain observation window"""
|
||||
# In this case this is an overlapped observation
|
||||
# re-calculate altitude and start/end azimuth
|
||||
if window_start > pass_tca:
|
||||
|
@ -86,6 +91,7 @@ def create_station_window(
|
|||
window_start, window_end, azr, azs, altitude, tle, valid_duration, overlapped,
|
||||
overlap_ratio=0
|
||||
):
|
||||
"""Creates an observation window"""
|
||||
return {
|
||||
'start': window_start.strftime("%Y-%m-%d %H:%M:%S.%f"),
|
||||
'end': window_end.strftime("%Y-%m-%d %H:%M:%S.%f"),
|
||||
|
@ -172,6 +178,7 @@ def create_station_windows(scheduled_obs, overlapped, pass_params, observer, sat
|
|||
|
||||
|
||||
def next_pass(observer, satellite):
|
||||
"""Returns the next pass of the satellite above the observer"""
|
||||
rise_time, rise_az, tca_time, tca_alt, set_time, set_az = observer.next_pass(satellite, True)
|
||||
# Convert output of pyephems.next_pass into processible values
|
||||
pass_start = make_aware(ephem.Date(rise_time).datetime(), utc)
|
||||
|
@ -271,6 +278,7 @@ def predict_available_observation_windows(station, min_horizon, overlapped, tle,
|
|||
|
||||
|
||||
def create_new_observation(station, transmitter, start, end, author):
|
||||
"""Creates and returns a new Observation object"""
|
||||
scheduled_obs = Observation.objects.filter(ground_station=station).filter(end__gt=now())
|
||||
window = resolve_overlaps(scheduled_obs, start, end)
|
||||
|
||||
|
@ -351,6 +359,7 @@ def create_new_observation(station, transmitter, start, end, author):
|
|||
|
||||
|
||||
def get_available_stations(stations, downlink, user):
|
||||
"""Returns stations for scheduling filtered by a specific downlink and user's permissions"""
|
||||
available_stations = []
|
||||
for station in stations:
|
||||
if not schedule_station_perms(user, station):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Module for calculating and keep in cache satellite and transmitter statistics"""
|
||||
import math
|
||||
|
||||
from django.core.cache import cache
|
||||
|
@ -8,6 +9,7 @@ from network.base.models import Observation
|
|||
|
||||
|
||||
def transmitter_stats_by_uuid(uuid):
|
||||
"""Calculate and put in cache transmitter statistics"""
|
||||
stats = cache.get('tr-{0}-stats'.format(uuid))
|
||||
if stats is None:
|
||||
# Sum - Case - When should be replaced with Count and filter when we move to Django 2.*
|
||||
|
@ -64,6 +66,7 @@ def transmitter_stats_by_uuid(uuid):
|
|||
|
||||
|
||||
def satellite_stats_by_transmitter_list(transmitter_list):
|
||||
"""Calculate satellite statistics"""
|
||||
total_count = 0
|
||||
unvetted_count = 0
|
||||
future_count = 0
|
||||
|
|
|
@ -98,6 +98,7 @@ def fetch_data():
|
|||
|
||||
@APP.task(ignore_result=True)
|
||||
def archive_audio(obs_id):
|
||||
"""Upload audio of observation in archive.org"""
|
||||
obs = Observation.objects.get(id=obs_id)
|
||||
suffix = '-{0}'.format(settings.ENVIRONMENT)
|
||||
if settings.ENVIRONMENT == 'production':
|
||||
|
@ -216,6 +217,7 @@ def notify_for_stations_without_results():
|
|||
|
||||
@APP.task(ignore_result=True)
|
||||
def stations_cache_rates():
|
||||
"""Cache the success rate of the stations"""
|
||||
stations = Station.objects.all()
|
||||
for station in stations:
|
||||
observations = station.observations.exclude(testing=True).exclude(vetted_status="unknown")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Django template tags for SatNOGS Network"""
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
|
@ -6,6 +7,7 @@ register = template.Library()
|
|||
|
||||
@register.simple_tag
|
||||
def active(request, urls):
|
||||
"""Returns if this is an active URL"""
|
||||
if request.path in (reverse(url) for url in urls.split()):
|
||||
return 'active'
|
||||
return None
|
||||
|
@ -13,11 +15,13 @@ def active(request, urls):
|
|||
|
||||
@register.simple_tag
|
||||
def drifted_frq(frq, drift):
|
||||
"""Returns drifred frequency"""
|
||||
return int(round(frq + ((frq * drift) / float(pow(10, 9)))))
|
||||
|
||||
|
||||
@register.filter
|
||||
def frq(value):
|
||||
"""Returns Hz formatted frequency html string"""
|
||||
try:
|
||||
to_format = float(value)
|
||||
except (TypeError, ValueError):
|
||||
|
@ -29,6 +33,7 @@ def frq(value):
|
|||
|
||||
@register.filter
|
||||
def percentagerest(value):
|
||||
"""Returns the rest of percentage from a given (percentage) value"""
|
||||
try:
|
||||
return 100 - value
|
||||
except (TypeError, ValueError):
|
||||
|
@ -37,6 +42,7 @@ def percentagerest(value):
|
|||
|
||||
@register.filter
|
||||
def sortdemoddata(demoddata):
|
||||
"""Returns a date sorted list of DemodData"""
|
||||
try:
|
||||
return sorted(list(demoddata), key=lambda x: str(x.payload_demod).split('/', 2)[2:])
|
||||
except (TypeError, ValueError):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Tag to replace field in GET request"""
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network base test suites"""
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
@ -20,6 +21,7 @@ OBSERVATION_STATUS_IDS = [c[0] for c in OBSERVATION_STATUSES]
|
|||
|
||||
|
||||
def generate_payload():
|
||||
"""Create data payloads"""
|
||||
payload = '{0:b}'.format(random.randint(500000000, 510000000))
|
||||
digits = 1824
|
||||
while digits:
|
||||
|
@ -30,6 +32,7 @@ def generate_payload():
|
|||
|
||||
|
||||
def generate_payload_name():
|
||||
"""Create payload names"""
|
||||
filename = datetime.strftime(
|
||||
fuzzy.FuzzyDateTime(now() - timedelta(days=10), now()).fuzz(), '%Y%m%dT%H%M%SZ'
|
||||
)
|
||||
|
@ -62,6 +65,7 @@ class StationFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
@factory.post_generation
|
||||
def antennas(self, create, extracted, **kwargs): # pylint: disable=W0613
|
||||
"""Generate antennas"""
|
||||
if not create:
|
||||
return
|
||||
|
||||
|
@ -128,6 +132,7 @@ class ObservationFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
|
||||
class DemodDataFactory(factory.django.DjangoModelFactory):
|
||||
"""DemodData model factory."""
|
||||
observation = factory.Iterator(Observation.objects.all())
|
||||
payload_demod = factory.django.FileField()
|
||||
|
||||
|
@ -141,6 +146,7 @@ class HomeViewTest(TestCase):
|
|||
Simple test to make sure the home page is working
|
||||
"""
|
||||
def test_home_page(self):
|
||||
"""Test for string in home page"""
|
||||
response = self.client.get('/')
|
||||
self.assertContains(response, 'Crowd-sourced satellite operations')
|
||||
|
||||
|
@ -151,6 +157,7 @@ class AboutViewTest(TestCase):
|
|||
Simple test to make sure the about page is working
|
||||
"""
|
||||
def test_about_page(self):
|
||||
"""Test for string in about page"""
|
||||
response = self.client.get('/about/')
|
||||
self.assertContains(response, 'SatNOGS Network is a global management interface')
|
||||
|
||||
|
@ -168,6 +175,7 @@ class StationListViewTest(TestCase):
|
|||
self.stations.append(StationFactory())
|
||||
|
||||
def test_station_list(self):
|
||||
"""Test for owners and station names in station page"""
|
||||
response = self.client.get('/stations/')
|
||||
for station in self.stations:
|
||||
self.assertContains(response, station.owner)
|
||||
|
@ -213,23 +221,27 @@ class ObservationsListViewTest(TestCase):
|
|||
self.observations.append(obs)
|
||||
|
||||
def test_observations_list(self):
|
||||
"""Test for transmitter modes of each observation in observations page"""
|
||||
response = self.client.get('/observations/')
|
||||
for observation in self.observations:
|
||||
self.assertContains(response, observation.transmitter_mode)
|
||||
|
||||
def test_observations_list_select_bad(self):
|
||||
"""Test for transmitter modes of each bad observation in observations page"""
|
||||
response = self.client.get('/observations/?bad=1')
|
||||
|
||||
for observation in self.observations_bad:
|
||||
self.assertContains(response, observation.transmitter_mode)
|
||||
|
||||
def test_observations_list_select_good(self):
|
||||
"""Test for transmitter modes of each good observation in observations page"""
|
||||
response = self.client.get('/observations/?good=1')
|
||||
|
||||
for observation in self.observations_good:
|
||||
self.assertContains(response, observation.transmitter_mode)
|
||||
|
||||
def test_observations_list_select_unvetted(self):
|
||||
"""Test for transmitter modes of each unvetted observation in observations page"""
|
||||
response = self.client.get('/observations/?unvetted=1')
|
||||
|
||||
for observation in self.observations_unvetted:
|
||||
|
@ -243,6 +255,7 @@ class NotFoundErrorTest(TestCase):
|
|||
client = Client()
|
||||
|
||||
def test_404_not_found(self):
|
||||
"""Test for "404" html status code in response for requesting a non-existed page"""
|
||||
response = self.client.get('/blah')
|
||||
self.assertEquals(response.status_code, 404)
|
||||
|
||||
|
@ -254,6 +267,7 @@ class RobotsViewTest(TestCase):
|
|||
client = Client()
|
||||
|
||||
def test_robots(self):
|
||||
"""Test for "Disallow" string in response for requesting robots.txt"""
|
||||
response = self.client.get('/robots.txt')
|
||||
self.assertContains(response, 'Disallow: /')
|
||||
|
||||
|
@ -280,6 +294,7 @@ class ObservationViewTest(TestCase):
|
|||
self.observation = ObservationFactory()
|
||||
|
||||
def test_observation(self):
|
||||
"""Test for observer and transmitter mode in observation page"""
|
||||
response = self.client.get('/observations/%d/' % self.observation.id)
|
||||
self.assertContains(response, self.observation.author.username)
|
||||
self.assertContains(response, self.observation.transmitter_mode)
|
||||
|
@ -363,6 +378,7 @@ class StationViewTest(TestCase):
|
|||
self.station = StationFactory()
|
||||
|
||||
def test_observation(self):
|
||||
"""Test for owner, elevation and min horizon in station page"""
|
||||
response = self.client.get('/stations/%d/' % self.station.id)
|
||||
self.assertContains(response, self.station.owner.username)
|
||||
self.assertContains(response, self.station.alt)
|
||||
|
@ -386,6 +402,7 @@ class StationDeleteTest(TestCase):
|
|||
self.station.save()
|
||||
|
||||
def test_station_delete(self):
|
||||
"""Test deletion of station"""
|
||||
response = self.client.get('/stations/%d/delete/' % self.station.id)
|
||||
self.assertRedirects(response, '/users/%s/' % self.user.username)
|
||||
with self.assertRaises(Station.DoesNotExist):
|
||||
|
@ -407,6 +424,7 @@ class SettingsSiteViewTest(TestCase):
|
|||
self.client.force_login(self.user)
|
||||
|
||||
def test_get(self):
|
||||
"""Test for "Fetch Data" in Settings Site page"""
|
||||
response = self.client.get('/settings_site/')
|
||||
self.assertContains(response, 'Fetch Data')
|
||||
|
||||
|
@ -429,4 +447,5 @@ class ObservationModelTest(TestCase):
|
|||
self.observation.save()
|
||||
|
||||
def test_is_passed(self):
|
||||
"""Test for observation be in past"""
|
||||
self.assertTrue(self.observation.is_past)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Django base URL routings for SatNOGS Network"""
|
||||
from django.conf.urls import url
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Miscellaneous functions for SatNOGS Network"""
|
||||
import csv
|
||||
import urllib
|
||||
import urllib2
|
||||
|
@ -13,6 +14,7 @@ from network.base.models import DemodData
|
|||
|
||||
|
||||
def export_as_csv(modeladmin, request, queryset):
|
||||
"""Exports admin panel table in csv format"""
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied
|
||||
opts = modeladmin.model._meta
|
||||
|
@ -54,6 +56,7 @@ def export_as_csv(modeladmin, request, queryset):
|
|||
|
||||
|
||||
def export_station_status(self, request, queryset):
|
||||
"""Exports status of selected stations in csv format"""
|
||||
meta = self.model._meta
|
||||
field_names = ["id", "status"]
|
||||
|
||||
|
@ -69,7 +72,7 @@ def export_station_status(self, request, queryset):
|
|||
|
||||
|
||||
def demod_to_db(frame_id):
|
||||
"""Task to send a frame from SatNOGS network to SatNOGS db"""
|
||||
"""Task to send a frame from SatNOGS Network to SatNOGS DB"""
|
||||
frame = DemodData.objects.get(id=frame_id)
|
||||
obs = frame.observation
|
||||
sat = obs.satellite
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Django base validators for SatNOGS Network"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -5,22 +6,27 @@ from django.utils.timezone import make_aware, utc
|
|||
|
||||
|
||||
class ObservationOverlapError(Exception):
|
||||
"""Error when observation overlaps with already scheduled one"""
|
||||
pass
|
||||
|
||||
|
||||
class OutOfRangeError(Exception):
|
||||
"""Error when transmitter is our of station's antenna frequency range"""
|
||||
pass
|
||||
|
||||
|
||||
class NegativeElevationError(Exception):
|
||||
"""Error when satellite doesn't raise above station's horizon"""
|
||||
pass
|
||||
|
||||
|
||||
class SinglePassError(Exception):
|
||||
"""Error when between given start and end datetimes there are more than one satellite passes"""
|
||||
pass
|
||||
|
||||
|
||||
def check_start_datetime(start):
|
||||
"""Validate start datetime"""
|
||||
if start < make_aware(datetime.now(), utc):
|
||||
raise ValueError("Start datetime should be in the future!")
|
||||
if start < make_aware(datetime.now() + timedelta(minutes=settings.OBSERVATION_DATE_MIN_START),
|
||||
|
@ -33,6 +39,7 @@ def check_start_datetime(start):
|
|||
|
||||
|
||||
def check_end_datetime(end):
|
||||
"""Validate end datetime"""
|
||||
if end < make_aware(datetime.now(), utc):
|
||||
raise ValueError("End datetime should be in the future!")
|
||||
max_duration = settings.OBSERVATION_DATE_MIN_START + settings.OBSERVATION_DATE_MAX_RANGE
|
||||
|
@ -44,6 +51,7 @@ def check_end_datetime(end):
|
|||
|
||||
|
||||
def check_start_end_datetimes(start, end):
|
||||
"""Validate the pair of start and end datetimes"""
|
||||
if start > end:
|
||||
raise ValueError("End datetime should be after Start datetime!")
|
||||
if (end - start) < timedelta(seconds=settings.OBSERVATION_DURATION_MIN):
|
||||
|
@ -55,12 +63,14 @@ def check_start_end_datetimes(start, end):
|
|||
|
||||
|
||||
def downlink_low_is_in_range(antenna, transmitter):
|
||||
"""Return true if transmitter frequency is in station's antenna frequency range"""
|
||||
if transmitter['downlink_low'] is not None:
|
||||
return antenna.frequency <= transmitter['downlink_low'] <= antenna.frequency_max
|
||||
return False
|
||||
|
||||
|
||||
def is_transmitter_in_station_range(transmitter, station):
|
||||
"""Return true if transmitter frequency is in one of the station's antennas frequency ranges"""
|
||||
for gs_antenna in station.antenna.all():
|
||||
if downlink_low_is_in_range(gs_antenna, transmitter):
|
||||
return True
|
||||
|
@ -68,6 +78,7 @@ def is_transmitter_in_station_range(transmitter, station):
|
|||
|
||||
|
||||
def check_transmitter_station_pairs(transmitter_station_list):
|
||||
"""Validate the pairs of transmitter and stations"""
|
||||
out_of_range_pairs = [
|
||||
(str(pair[0]['uuid']), int(pair[1].id)) for pair in transmitter_station_list
|
||||
if not is_transmitter_in_station_range(pair[0], pair[1])
|
||||
|
@ -86,6 +97,7 @@ def check_transmitter_station_pairs(transmitter_station_list):
|
|||
|
||||
|
||||
def check_overlaps(stations_dict):
|
||||
"""Check for overlaps among requested observations"""
|
||||
for station in stations_dict.keys():
|
||||
periods = stations_dict[station]
|
||||
total_periods = len(periods)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Django base views for SatNOGS Network"""
|
||||
import urllib2
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
|
@ -38,6 +39,7 @@ from network.users.models import User
|
|||
|
||||
|
||||
class StationSerializer(serializers.ModelSerializer):
|
||||
"""Django model Serializer for Station model"""
|
||||
status_display = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
|
@ -45,6 +47,7 @@ class StationSerializer(serializers.ModelSerializer):
|
|||
fields = ('name', 'lat', 'lng', 'id', 'status', 'status_display')
|
||||
|
||||
def get_status_display(self, obj):
|
||||
"""Returns the station status"""
|
||||
try:
|
||||
return obj.get_status_display()
|
||||
except AttributeError:
|
||||
|
@ -52,6 +55,7 @@ class StationSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class StationAllView(viewsets.ReadOnlyModelViewSet):
|
||||
"""Station View of all non offline stations"""
|
||||
queryset = Station.objects.exclude(status=0)
|
||||
serializer_class = StationSerializer
|
||||
|
||||
|
@ -67,6 +71,7 @@ def index(request):
|
|||
|
||||
|
||||
def robots(request):
|
||||
"""Returns response for robots.txt requests"""
|
||||
data = render(request, 'robots.txt', {'environment': settings.ENVIRONMENT})
|
||||
response = HttpResponse(data, content_type='text/plain; charset=utf-8')
|
||||
return response
|
||||
|
@ -221,6 +226,7 @@ class ObservationListView(ListView):
|
|||
|
||||
|
||||
def observation_new_post(request):
|
||||
"""Handles POST requests for creating one or more new observations."""
|
||||
ObservationFormSet = formset_factory( # pylint: disable=C0103
|
||||
ObservationForm, formset=BaseObservationFormSet, min_num=1, validate_min=True
|
||||
)
|
||||
|
@ -348,6 +354,7 @@ def observation_new(request):
|
|||
|
||||
@ajax_required
|
||||
def prediction_windows(request):
|
||||
"""Calculates and returns passes of satellites over stations"""
|
||||
sat_norad_id = request.POST['satellite']
|
||||
transmitter = request.POST['transmitter']
|
||||
start = request.POST['start']
|
||||
|
@ -505,6 +512,7 @@ def observation_delete(request, observation_id):
|
|||
@login_required
|
||||
@ajax_required
|
||||
def observation_vet(request, observation_id):
|
||||
"""Handles request for vetting an observation"""
|
||||
try:
|
||||
observation = Observation.objects.get(id=observation_id)
|
||||
except Observation.DoesNotExist:
|
||||
|
@ -828,6 +836,7 @@ def station_delete(request, station_id):
|
|||
|
||||
|
||||
def transmitters_with_stats(transmitters_list):
|
||||
"""Returns a list of transmitters with their statistics"""
|
||||
transmitters_with_stats_list = []
|
||||
for transmitter in transmitters_list:
|
||||
transmitter_stats = transmitter_stats_by_uuid(transmitter['uuid'])
|
||||
|
@ -837,6 +846,7 @@ def transmitters_with_stats(transmitters_list):
|
|||
|
||||
|
||||
def satellite_view(request, norad_id):
|
||||
"""Returns a satellite JSON object with information and statistics"""
|
||||
try:
|
||||
sat = Satellite.objects.get(norad_cat_id=norad_id)
|
||||
except Satellite.DoesNotExist:
|
||||
|
@ -867,6 +877,7 @@ def satellite_view(request, norad_id):
|
|||
|
||||
|
||||
def transmitters_view(request):
|
||||
"""Returns a transmitter JSON object with information and statistics"""
|
||||
norad_id = request.POST['satellite']
|
||||
station_id = request.POST.get('station_id', None)
|
||||
try:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network celery task workers"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
|
@ -21,6 +22,7 @@ APP.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
|||
|
||||
@APP.on_after_finalize.connect
|
||||
def setup_periodic_tasks(sender, **kwargs): # pylint: disable=W0613
|
||||
"""Initializes celery tasks that need to run on a scheduled basis"""
|
||||
from network.base.tasks import (
|
||||
update_all_tle, fetch_data, clean_observations, station_status_update,
|
||||
stations_cache_rates, notify_for_stations_without_results, sync_to_db
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
"""SatNOGS Network Application django settings
|
||||
|
||||
For local installation settings please copy .env-dist to .env and edit
|
||||
the appropriate settings in that file. You should not need to edit this
|
||||
file for local settings!
|
||||
"""
|
||||
import sentry_sdk
|
||||
from decouple import Csv, config
|
||||
from dj_database_url import parse as db_url
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
<h4 class="modal-title" id="myModalLabel">Join SatNGOS Network</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
SatNOGS network is a global management interface to facilitate multiple ground station
|
||||
SatNOGS Network is a global management interface to facilitate multiple ground station
|
||||
operations remotely. An observer is able to take advantage of the full network of
|
||||
SatNOGS ground stations around the world.
|
||||
<h3>Observations</h3>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Base Django URL mapping for SatNOGS Network"""
|
||||
from allauth import urls as allauth_urls
|
||||
from avatar import urls as avatar_urls
|
||||
from django.conf import settings
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Define functions and settings for the django admin users interface"""
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
|
||||
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||
|
@ -7,6 +8,7 @@ from network.users.models import User
|
|||
|
||||
|
||||
class HasStationListFilter(admin.SimpleListFilter):
|
||||
"""Filter users by checking if the own a station or not"""
|
||||
title = 'having station'
|
||||
parameter_name = 'has_station'
|
||||
|
||||
|
@ -25,6 +27,7 @@ class HasStationListFilter(admin.SimpleListFilter):
|
|||
|
||||
|
||||
class UserAdmin(AuthUserAdmin):
|
||||
"""Class of AuthUserAdmin"""
|
||||
create_form_class = UserCreationForm
|
||||
update_form_class = UserChangeForm
|
||||
list_filter = AuthUserAdmin.list_filter + (HasStationListFilter, )
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
"""Django users forms for SatNOGS Network"""
|
||||
from django import forms
|
||||
|
||||
from network.users.models import User
|
||||
|
||||
|
||||
class UserForm(forms.ModelForm):
|
||||
"""Model Form class for User objects"""
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ("first_name", "last_name")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Django database users model for SatNOGS Network"""
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.validators import MaxLengthValidator
|
||||
from django.db import models
|
||||
|
@ -6,6 +7,7 @@ from rest_framework.authtoken.models import Token
|
|||
|
||||
|
||||
def gen_token(sender, instance, created, **kwargs): # pylint: disable=W0613
|
||||
"""Generate token for user"""
|
||||
try:
|
||||
Token.objects.get(user=instance)
|
||||
except Token.DoesNotExist:
|
||||
|
@ -19,6 +21,7 @@ class User(AbstractUser):
|
|||
|
||||
@property
|
||||
def displayname(self):
|
||||
"""Return the display name of user"""
|
||||
if self.get_full_name():
|
||||
return self.get_full_name()
|
||||
return self.username
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""SatNOGS Network users test suites"""
|
||||
import datetime
|
||||
|
||||
import factory
|
||||
|
@ -40,5 +41,6 @@ class UserViewTest(TestCase):
|
|||
self.client.force_login(self.user)
|
||||
|
||||
def test_view_user(self):
|
||||
"""Test of user view"""
|
||||
response = self.client.get('/users/%s/' % self.user.username)
|
||||
self.assertContains(response, self.user.username)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Django users URL routings for SatNOGS Network"""
|
||||
from django.conf.urls import url
|
||||
|
||||
from network.users import views
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""Django users views for SatNOGS Network"""
|
||||
from braces.views import LoginRequiredMixin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import Count
|
||||
|
@ -13,6 +14,7 @@ from network.users.models import User
|
|||
|
||||
|
||||
class UserRedirectView(LoginRequiredMixin, RedirectView):
|
||||
"""View for user redirect"""
|
||||
permanent = False
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
|
@ -20,7 +22,7 @@ class UserRedirectView(LoginRequiredMixin, RedirectView):
|
|||
|
||||
|
||||
class UserUpdateView(LoginRequiredMixin, UpdateView):
|
||||
|
||||
"""View for user update"""
|
||||
form_class = UserForm
|
||||
|
||||
model = User
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
"""WSGI module for SatNOGS Network"""
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
|
Loading…
Reference in New Issue