1
0
Fork 0

Fix C0111 lint issues

Signed-off-by: Alfredos-Panagiotis Damkalis <fredy@fredy.gr>
merge-requests/789/head
Alfredos-Panagiotis Damkalis 2019-11-22 20:23:24 +02:00
parent 07f924b907
commit 758c1dfc03
49 changed files with 260 additions and 11 deletions

View File

@ -5,7 +5,6 @@ ignored-argument-names=args|kwargs
[MESSAGES CONTROL] [MESSAGES CONTROL]
disable= disable=
C0111,
C0412, C0412,
E1101, E1101,
E1121, E1121,

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""SatNOGS Network Auth0 login module admin class"""
from __future__ import unicode_literals from __future__ import unicode_literals
# from django.contrib import admin # from django.contrib import admin

View File

@ -1,8 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""SatNOGS Network Auth0 login app config"""
from __future__ import unicode_literals from __future__ import unicode_literals
from django.apps import AppConfig from django.apps import AppConfig
class Auth0LoginConfig(AppConfig): class Auth0LoginConfig(AppConfig):
"""Set the name of the django app for auth0login"""
name = 'auth0login' name = 'auth0login'

View File

@ -1,3 +1,4 @@
"""SatNOGS Network Auth0 login module auth backend"""
import requests import requests
from social_core.backends.oauth import BaseOAuth2 from social_core.backends.oauth import BaseOAuth2

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""SatNOGS Network Auth0 login module models"""
from __future__ import unicode_literals from __future__ import unicode_literals
# from django.db import models # from django.db import models

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""SatNOGS Network Auth0 login module test suites"""
from __future__ import unicode_literals from __future__ import unicode_literals
# from django.test import TestCase # from django.test import TestCase

View File

@ -1,3 +1,4 @@
"""SatNOGS Network Auth0 login module URL routers"""
from django.conf.urls import include, url from django.conf.urls import include, url
from . import views from . import views

View File

@ -1,7 +1,9 @@
"""SatNOGS Network Auth0 login module views"""
from __future__ import unicode_literals from __future__ import unicode_literals
from django.shortcuts import render from django.shortcuts import render
def index(request): def index(request):
"""Returns the index view"""
return render(request, 'index.html') return render(request, 'index.html')

View File

@ -1,3 +1,4 @@
"""The core django app for SatNOGS Network"""
from __future__ import absolute_import from __future__ import absolute_import
from ._version import get_versions from ._version import get_versions

View File

@ -1,3 +1,4 @@
"""SatNOGS Network django rest framework Filters class"""
import django_filters import django_filters
from django_filters.rest_framework import FilterSet from django_filters.rest_framework import FilterSet
@ -5,6 +6,7 @@ from network.base.models import Observation, Station, Transmitter
class ObservationViewFilter(FilterSet): class ObservationViewFilter(FilterSet):
"""SatNOGS Network Observation API View Filter"""
start = django_filters.IsoDateTimeFilter(name='start', lookup_expr='gte') start = django_filters.IsoDateTimeFilter(name='start', lookup_expr='gte')
end = django_filters.IsoDateTimeFilter(name='end', lookup_expr='lte') end = django_filters.IsoDateTimeFilter(name='end', lookup_expr='lte')
@ -17,12 +19,14 @@ class ObservationViewFilter(FilterSet):
class StationViewFilter(FilterSet): class StationViewFilter(FilterSet):
"""SatNOGS Network Station API View Filter"""
class Meta: class Meta:
model = Station model = Station
fields = ['id', 'name', 'status', 'client_version'] fields = ['id', 'name', 'status', 'client_version']
class TransmitterViewFilter(FilterSet): class TransmitterViewFilter(FilterSet):
"""SatNOGS Network Transmitter API View Filter"""
class Meta: class Meta:
model = Transmitter model = Transmitter
fields = ['uuid', 'sync_to_db'] fields = ['uuid', 'sync_to_db']

View File

@ -1,3 +1,4 @@
"""SatNOGS Network API permissions, django rest framework"""
from rest_framework import permissions from rest_framework import permissions
from network.base.perms import schedule_perms from network.base.perms import schedule_perms

View File

@ -1,3 +1,4 @@
"""SatNOGS Network API serializers, django rest framework"""
from rest_framework import serializers from rest_framework import serializers
from network.base.db_api import DBConnectionError, \ from network.base.db_api import DBConnectionError, \
@ -14,12 +15,14 @@ from network.base.validators import ObservationOverlapError, OutOfRangeError, \
class DemodDataSerializer(serializers.ModelSerializer): class DemodDataSerializer(serializers.ModelSerializer):
"""SatNOGS Network DemodData API Serializer"""
class Meta: class Meta:
model = DemodData model = DemodData
fields = ('payload_demod', ) fields = ('payload_demod', )
class ObservationSerializer(serializers.ModelSerializer): class ObservationSerializer(serializers.ModelSerializer):
"""SatNOGS Network Observation API Serializer"""
transmitter = serializers.SerializerMethodField() transmitter = serializers.SerializerMethodField()
transmitter_updated = serializers.SerializerMethodField() transmitter_updated = serializers.SerializerMethodField()
norad_cat_id = serializers.SerializerMethodField() norad_cat_id = serializers.SerializerMethodField()
@ -54,44 +57,52 @@ class ObservationSerializer(serializers.ModelSerializer):
] ]
def update(self, instance, validated_data): def update(self, instance, validated_data):
"""Updates observation object with validated data"""
validated_data.pop('demoddata') validated_data.pop('demoddata')
super(ObservationSerializer, self).update(instance, validated_data) super(ObservationSerializer, self).update(instance, validated_data)
return instance return instance
def get_transmitter(self, obj): def get_transmitter(self, obj):
"""Returns Transmitter UUID"""
try: try:
return obj.transmitter_uuid return obj.transmitter_uuid
except AttributeError: except AttributeError:
return '' return ''
def get_transmitter_updated(self, obj): def get_transmitter_updated(self, obj):
"""Returns Transmitter last update date"""
try: try:
return obj.transmitter_created return obj.transmitter_created
except AttributeError: except AttributeError:
return '' return ''
def get_norad_cat_id(self, obj): def get_norad_cat_id(self, obj):
"""Returns Satellite NORAD ID"""
return obj.satellite.norad_cat_id return obj.satellite.norad_cat_id
def get_station_name(self, obj): def get_station_name(self, obj):
"""Returns Station name"""
try: try:
return obj.ground_station.name return obj.ground_station.name
except AttributeError: except AttributeError:
return None return None
def get_station_lat(self, obj): def get_station_lat(self, obj):
"""Returns Station latitude"""
try: try:
return obj.ground_station.lat return obj.ground_station.lat
except AttributeError: except AttributeError:
return None return None
def get_station_lng(self, obj): def get_station_lng(self, obj):
"""Returns Station longitude"""
try: try:
return obj.ground_station.lng return obj.ground_station.lng
except AttributeError: except AttributeError:
return None return None
def get_station_alt(self, obj): def get_station_alt(self, obj):
"""Returns Station elevation"""
try: try:
return obj.ground_station.alt return obj.ground_station.alt
except AttributeError: except AttributeError:
@ -99,7 +110,9 @@ class ObservationSerializer(serializers.ModelSerializer):
class NewObservationListSerializer(serializers.ListSerializer): class NewObservationListSerializer(serializers.ListSerializer):
"""SatNOGS Network New Observation API List Serializer"""
def validate(self, attrs): def validate(self, attrs):
"""Validates data from a list of new observations"""
user = self.context['request'].user user = self.context['request'].user
station_list = [] station_list = []
transmitter_uuid_list = [] transmitter_uuid_list = []
@ -151,6 +164,7 @@ class NewObservationListSerializer(serializers.ListSerializer):
return attrs return attrs
def create(self, validated_data): def create(self, validated_data):
"""Creates new observations from a list of new observations validated data"""
new_observations = [] new_observations = []
for observation_data in validated_data: for observation_data in validated_data:
station = observation_data['ground_station'] station = observation_data['ground_station']
@ -170,10 +184,15 @@ class NewObservationListSerializer(serializers.ListSerializer):
return new_observations return new_observations
def update(self, instance, validated_data): def update(self, instance, validated_data):
"""Updates observations from a list of validated data
currently disabled and returns None
"""
return None return None
class NewObservationSerializer(serializers.Serializer): class NewObservationSerializer(serializers.Serializer):
"""SatNOGS Network New Observation API Serializer"""
start = serializers.DateTimeField( start = serializers.DateTimeField(
input_formats=['%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S'], input_formats=['%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S'],
error_messages={ error_messages={
@ -212,6 +231,7 @@ class NewObservationSerializer(serializers.Serializer):
) )
def validate_start(self, value): def validate_start(self, value):
"""Validates start datetime of a new observation"""
try: try:
check_start_datetime(value) check_start_datetime(value)
except ValueError as error: except ValueError as error:
@ -219,6 +239,7 @@ class NewObservationSerializer(serializers.Serializer):
return value return value
def validate_end(self, value): def validate_end(self, value):
"""Validates end datetime of a new observation"""
try: try:
check_end_datetime(value) check_end_datetime(value)
except ValueError as error: except ValueError as error:
@ -226,6 +247,7 @@ class NewObservationSerializer(serializers.Serializer):
return value return value
def validate(self, attrs): def validate(self, attrs):
"""Validates combination of start and end datetimes of a new observation"""
start = attrs['start'] start = attrs['start']
end = attrs['end'] end = attrs['end']
try: try:
@ -235,15 +257,23 @@ class NewObservationSerializer(serializers.Serializer):
return attrs return attrs
def create(self, validated_data): def create(self, validated_data):
# If in the future we want to implement this serializer accepting and creating observation """Creates a new 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 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( raise serializers.ValidationError(
"Serializer is implemented for accepting and schedule\ "Serializer is implemented for accepting and schedule\
only lists of observations" only lists of observations"
) )
def update(self, instance, validated_data): def update(self, instance, validated_data):
"""Updates an observation from validated data
currently disabled and returns None
"""
return None return None
class Meta: class Meta:
@ -251,12 +281,14 @@ class NewObservationSerializer(serializers.Serializer):
class AntennaSerializer(serializers.ModelSerializer): class AntennaSerializer(serializers.ModelSerializer):
"""SatNOGS Network Antenna API Serializer"""
class Meta: class Meta:
model = Antenna model = Antenna
fields = ('frequency', 'frequency_max', 'band', 'antenna_type') fields = ('frequency', 'frequency_max', 'band', 'antenna_type')
class StationSerializer(serializers.ModelSerializer): class StationSerializer(serializers.ModelSerializer):
"""SatNOGS Network Station API Serializer"""
antenna = AntennaSerializer(many=True) antenna = AntennaSerializer(many=True)
altitude = serializers.SerializerMethodField() altitude = serializers.SerializerMethodField()
min_horizon = serializers.SerializerMethodField() min_horizon = serializers.SerializerMethodField()
@ -272,13 +304,17 @@ class StationSerializer(serializers.ModelSerializer):
) )
def get_altitude(self, obj): def get_altitude(self, obj):
"""Returns Station elevation"""
return obj.alt return obj.alt
def get_min_horizon(self, obj): def get_min_horizon(self, obj):
"""Returns Station minimum horizon"""
return obj.horizon return obj.horizon
def get_antenna(self, obj): def get_antenna(self, obj):
"""Returns Station antenna list"""
def antenna_name(antenna): def antenna_name(antenna):
"""Returns Station antenna"""
return antenna.band + " " + antenna.get_antenna_type_display() return antenna.band + " " + antenna.get_antenna_type_display()
try: try:
@ -287,12 +323,14 @@ class StationSerializer(serializers.ModelSerializer):
return None return None
def get_observations(self, obj): def get_observations(self, obj):
"""Returns Station observations number"""
try: try:
return obj.observations_count return obj.observations_count
except AttributeError: except AttributeError:
return None return None
def get_status(self, obj): def get_status(self, obj):
"""Returns Station status"""
try: try:
return obj.get_status_display() return obj.get_status_display()
except AttributeError: except AttributeError:
@ -300,6 +338,7 @@ class StationSerializer(serializers.ModelSerializer):
class JobSerializer(serializers.ModelSerializer): class JobSerializer(serializers.ModelSerializer):
"""SatNOGS Network Job API Serializer"""
frequency = serializers.SerializerMethodField() frequency = serializers.SerializerMethodField()
tle0 = serializers.SerializerMethodField() tle0 = serializers.SerializerMethodField()
tle1 = serializers.SerializerMethodField() tle1 = serializers.SerializerMethodField()
@ -316,6 +355,7 @@ class JobSerializer(serializers.ModelSerializer):
) )
def get_frequency(self, obj): def get_frequency(self, obj):
"""Returns Transmitter downlink low frequency"""
frequency = obj.transmitter_downlink_low frequency = obj.transmitter_downlink_low
frequency_drift = obj.transmitter_downlink_drift frequency_drift = obj.transmitter_downlink_drift
if frequency_drift is None: if frequency_drift is None:
@ -323,28 +363,35 @@ class JobSerializer(serializers.ModelSerializer):
return int(round(frequency + ((frequency * frequency_drift) / float(pow(10, 9))))) return int(round(frequency + ((frequency * frequency_drift) / float(pow(10, 9)))))
def get_transmitter(self, obj): def get_transmitter(self, obj):
"""Returns Transmitter UUID"""
return obj.transmitter_uuid return obj.transmitter_uuid
def get_tle0(self, obj): def get_tle0(self, obj):
"""Returns line 0 of TLE"""
return obj.tle.tle0 return obj.tle.tle0
def get_tle1(self, obj): def get_tle1(self, obj):
"""Returns line 1 of TLE"""
return obj.tle.tle1 return obj.tle.tle1
def get_tle2(self, obj): def get_tle2(self, obj):
"""Returns line 2 of TLE"""
return obj.tle.tle2 return obj.tle.tle2
def get_mode(self, obj): def get_mode(self, obj):
"""Returns Transmitter mode"""
try: try:
return obj.transmitter_mode return obj.transmitter_mode
except AttributeError: except AttributeError:
return '' return ''
def get_baud(self, obj): def get_baud(self, obj):
"""Returns Transmitter baudrate"""
return obj.transmitter_baud return obj.transmitter_baud
class TransmitterSerializer(serializers.ModelSerializer): class TransmitterSerializer(serializers.ModelSerializer):
"""SatNOGS Network Transmitter API Serializer"""
stats = serializers.SerializerMethodField() stats = serializers.SerializerMethodField()
class Meta: class Meta:
@ -352,6 +399,7 @@ class TransmitterSerializer(serializers.ModelSerializer):
fields = ('uuid', 'sync_to_db', 'stats') fields = ('uuid', 'sync_to_db', 'stats')
def get_stats(self, obj): def get_stats(self, obj):
"""Returns Transmitter statistics"""
stats = transmitter_stats_by_uuid(obj.uuid) stats = transmitter_stats_by_uuid(obj.uuid)
for statistic in stats: for statistic in stats:
stats[statistic] = int(stats[statistic]) stats[statistic] = int(stats[statistic])

View File

@ -1,3 +1,4 @@
"""SatNOGS Network API test suites"""
import json import json
from django.test import TestCase from django.test import TestCase
@ -25,6 +26,7 @@ class JobViewApiTest(TestCase):
self.observation = ObservationFactory() self.observation = ObservationFactory()
def test_job_view_api(self): def test_job_view_api(self):
"""Test the Job View API"""
response = self.client.get('/api/jobs/') response = self.client.get('/api/jobs/')
response_json = json.loads(response.content) response_json = json.loads(response.content)
self.assertEqual(response_json, []) self.assertEqual(response_json, [])
@ -43,6 +45,7 @@ class StationViewApiTest(TestCase):
self.station = StationFactory.create(antennas=[self.antenna]) self.station = StationFactory.create(antennas=[self.antenna])
def test_station_view_api(self): def test_station_view_api(self):
"""Test the Station View API"""
ants = self.station.antenna.all() ants = self.station.antenna.all()
ser_ants = [ ser_ants = [

View File

@ -1,3 +1,4 @@
"""SatNOGS Network django rest framework API url routings"""
from rest_framework import routers from rest_framework import routers
from network.api import views from network.api import views

View File

@ -1,3 +1,4 @@
"""SatNOGS Network API django rest framework Views"""
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.timezone import now from django.utils.timezone import now
@ -14,17 +15,20 @@ from network.base.validators import NegativeElevationError, \
class ObservationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, class ObservationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin,
mixins.CreateModelMixin, viewsets.GenericViewSet): mixins.CreateModelMixin, viewsets.GenericViewSet):
"""SatNOGS Network Observation API view class"""
queryset = Observation.objects.all() queryset = Observation.objects.all()
filter_class = filters.ObservationViewFilter filter_class = filters.ObservationViewFilter
permission_classes = [StationOwnerPermission] permission_classes = [StationOwnerPermission]
pagination_class = pagination.LinkedHeaderPageNumberPagination pagination_class = pagination.LinkedHeaderPageNumberPagination
def get_serializer_class(self): def get_serializer_class(self):
"""Returns the right serializer depending on http method that is used"""
if self.request.method == 'POST': if self.request.method == 'POST':
return serializers.NewObservationSerializer return serializers.NewObservationSerializer
return serializers.ObservationSerializer return serializers.ObservationSerializer
def create(self, request, *args, **kwargs): 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) serializer = self.get_serializer(data=request.data, many=True, allow_empty=False)
try: try:
if serializer.is_valid(): if serializer.is_valid():
@ -53,6 +57,7 @@ class ObservationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.U
return response return response
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
"""Updates observation with audio, waterfall or demoded data"""
instance = self.get_object() instance = self.get_object()
if request.data.get('client_version'): if request.data.get('client_version'):
instance.ground_station.client_version = 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): class StationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
"""SatNOGS Network Station API view class"""
queryset = Station.objects.all() queryset = Station.objects.all()
serializer_class = serializers.StationSerializer serializer_class = serializers.StationSerializer
filter_class = filters.StationViewFilter filter_class = filters.StationViewFilter
@ -90,6 +96,7 @@ class StationView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.Gen
class TransmitterView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): class TransmitterView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
"""SatNOGS Network Transmitter API view class"""
queryset = Transmitter.objects.all().order_by('uuid') queryset = Transmitter.objects.all().order_by('uuid')
serializer_class = serializers.TransmitterSerializer serializer_class = serializers.TransmitterSerializer
filter_class = filters.TransmitterViewFilter filter_class = filters.TransmitterViewFilter
@ -97,12 +104,14 @@ class TransmitterView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets
class JobView(viewsets.ReadOnlyModelViewSet): class JobView(viewsets.ReadOnlyModelViewSet):
"""SatNOGS Network Job API view class"""
queryset = Observation.objects.filter(payload='') queryset = Observation.objects.filter(payload='')
serializer_class = serializers.JobSerializer serializer_class = serializers.JobSerializer
filter_class = filters.ObservationViewFilter filter_class = filters.ObservationViewFilter
filter_fields = ('ground_station') filter_fields = ('ground_station')
def get_queryset(self): def get_queryset(self):
"""Returns queryset for Job API view"""
queryset = self.queryset.filter(start__gte=now()) queryset = self.queryset.filter(start__gte=now())
ground_station_id = self.request.query_params.get('ground_station', None) ground_station_id = self.request.query_params.get('ground_station', None)
if ground_station_id and self.request.user.is_authenticated(): if ground_station_id and self.request.user.is_authenticated():

View File

@ -1,3 +1,4 @@
"""Define functions and settings for the django admin base interface"""
from django.contrib import admin from django.contrib import admin
from network.base.models import Antenna, DemodData, Observation, Satellite, \ 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) @admin.register(Antenna)
class AntennaAdmin(admin.ModelAdmin): class AntennaAdmin(admin.ModelAdmin):
"""Define Antenna view in django admin UI"""
list_display = ( list_display = (
'id', 'id',
'__unicode__', '__unicode__',
@ -19,14 +21,17 @@ class AntennaAdmin(admin.ModelAdmin):
) )
def antenna_count(self, obj): def antenna_count(self, obj):
"""Return the number of antennas"""
return obj.stations.all().count() return obj.stations.all().count()
def station_list(self, obj): def station_list(self, obj):
"""Return stations that use the antenna"""
return ",\n".join([str(s.id) for s in obj.stations.all()]) return ",\n".join([str(s.id) for s in obj.stations.all()])
@admin.register(Station) @admin.register(Station)
class StationAdmin(admin.ModelAdmin): class StationAdmin(admin.ModelAdmin):
"""Define Station view in django admin UI"""
list_display = ( list_display = (
'id', 'name', 'owner', 'get_email', 'lng', 'lat', 'qthlocator', 'client_version', 'id', 'name', 'owner', 'get_email', 'lng', 'lat', 'qthlocator', 'client_version',
'created_date', 'state', 'target_utilization' 'created_date', 'state', 'target_utilization'
@ -39,15 +44,18 @@ class StationAdmin(admin.ModelAdmin):
export_station_status.short_description = "Export selected status" export_station_status.short_description = "Export selected status"
def created_date(self, obj): def created_date(self, obj):
"""Return when the station was created"""
return obj.created.strftime('%d.%m.%Y, %H:%M') return obj.created.strftime('%d.%m.%Y, %H:%M')
def get_email(self, obj): def get_email(self, obj):
"""Return station owner email address"""
return obj.owner.email return obj.owner.email
get_email.admin_order_field = 'email' get_email.admin_order_field = 'email'
get_email.short_description = 'Owner Email' get_email.short_description = 'Owner Email'
def get_actions(self, request): def get_actions(self, request):
"""Return the list of actions for station admin view"""
actions = super(StationAdmin, self).get_actions(request) actions = super(StationAdmin, self).get_actions(request)
if 'delete_selected' in actions: if 'delete_selected' in actions:
del actions['delete_selected'] del actions['delete_selected']
@ -56,6 +64,7 @@ class StationAdmin(admin.ModelAdmin):
@admin.register(Satellite) @admin.register(Satellite)
class SatelliteAdmin(admin.ModelAdmin): 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_display = ('id', 'name', 'norad_cat_id', 'manual_tle', 'norad_follow_id', 'status')
list_filter = ( list_filter = (
'status', 'status',
@ -67,15 +76,18 @@ class SatelliteAdmin(admin.ModelAdmin):
@admin.register(Tle) @admin.register(Tle)
class TleAdmin(admin.ModelAdmin): class TleAdmin(admin.ModelAdmin):
"""Define TLE view in django admin UI"""
list_display = ('satellite_name', 'tle0', 'tle1', 'updated') list_display = ('satellite_name', 'tle0', 'tle1', 'updated')
list_filter = ('satellite__name', ) list_filter = ('satellite__name', )
def satellite_name(self, obj): def satellite_name(self, obj):
"""Return the satellite name"""
return obj.satellite.name return obj.satellite.name
@admin.register(Transmitter) @admin.register(Transmitter)
class TransmitterAdmin(admin.ModelAdmin): class TransmitterAdmin(admin.ModelAdmin):
"""Define Transmitter view in django admin UI"""
list_display = ('uuid', 'sync_to_db') list_display = ('uuid', 'sync_to_db')
search_fields = ('uuid', ) search_fields = ('uuid', )
list_filter = ('sync_to_db', ) list_filter = ('sync_to_db', )
@ -83,12 +95,13 @@ class TransmitterAdmin(admin.ModelAdmin):
class DemodDataInline(admin.TabularInline): 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 model = DemodData
@admin.register(Observation) @admin.register(Observation)
class ObservationAdmin(admin.ModelAdmin): class ObservationAdmin(admin.ModelAdmin):
"""Define Observation view in django admin UI"""
list_display = ('id', 'author', 'satellite', 'transmitter_uuid', 'start', 'end') list_display = ('id', 'author', 'satellite', 'transmitter_uuid', 'start', 'end')
list_filter = ('start', 'end') list_filter = ('start', 'end')
search_fields = ('satellite', 'author') search_fields = ('satellite', 'author')

View File

@ -1,3 +1,4 @@
"""SatNOGS Network django context processors"""
from django.conf import settings from django.conf import settings
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.timezone import now from django.utils.timezone import now
@ -20,6 +21,7 @@ def stage_notice(request):
def user_processor(request): def user_processor(request):
"""Returns number of user's unvetted observations."""
if request.user.is_authenticated(): if request.user.is_authenticated():
owner_vetting_count = Observation.objects.filter( owner_vetting_count = Observation.objects.filter(
author=request.user, vetted_status='unknown', end__lt=now() author=request.user, vetted_status='unknown', end__lt=now()

View File

@ -1,3 +1,4 @@
"""SatNOGS Network functions that consume DB API"""
import requests import requests
from django.conf import settings from django.conf import settings
@ -5,10 +6,12 @@ DB_API_URL = settings.DB_API_ENDPOINT
class DBConnectionError(Exception): class DBConnectionError(Exception):
"""Error when there are connection issues with DB API"""
pass pass
def transmitters_api_request(url): def transmitters_api_request(url):
"""Perform transmitter query on SatNOGS DB API and return the results"""
if not DB_API_URL: if not DB_API_URL:
raise DBConnectionError('Error in DB API connection. Blank DB API URL!') raise DBConnectionError('Error in DB API connection. Blank DB API URL!')
try: try:
@ -19,26 +22,31 @@ def transmitters_api_request(url):
def get_transmitter_by_uuid(uuid): def get_transmitter_by_uuid(uuid):
"""Returns transmitter filtered by Transmitter UUID"""
transmitters_url = "{}transmitters/?uuid={}".format(DB_API_URL, uuid) transmitters_url = "{}transmitters/?uuid={}".format(DB_API_URL, uuid)
return transmitters_api_request(transmitters_url) return transmitters_api_request(transmitters_url)
def get_transmitters_by_norad_id(norad_id): 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) transmitters_url = "{}transmitters/?satellite__norad_cat_id={}".format(DB_API_URL, norad_id)
return transmitters_api_request(transmitters_url) return transmitters_api_request(transmitters_url)
def get_transmitters_by_status(status): def get_transmitters_by_status(status):
"""Returns transmitters filtered by status"""
transmitters_url = "{}transmitters/?status={}".format(DB_API_URL, status) transmitters_url = "{}transmitters/?status={}".format(DB_API_URL, status)
return transmitters_api_request(transmitters_url) return transmitters_api_request(transmitters_url)
def get_transmitters(): def get_transmitters():
"""Returns all transmitters"""
transmitters_url = "{}transmitters".format(DB_API_URL) transmitters_url = "{}transmitters".format(DB_API_URL)
return transmitters_api_request(transmitters_url) return transmitters_api_request(transmitters_url)
def get_transmitters_by_uuid_list(uuid_list): def get_transmitters_by_uuid_list(uuid_list):
"""Returns transmitters filtered by Transmitter UUID list"""
if not uuid_list: if not uuid_list:
raise ValueError('Expected a non empty list of UUIDs.') raise ValueError('Expected a non empty list of UUIDs.')
if len(uuid_list) == 1: if len(uuid_list) == 1:

View File

@ -1,10 +1,13 @@
"""SatNOGS Network base decorators"""
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest
from django.shortcuts import redirect from django.shortcuts import redirect
def admin_required(function): def admin_required(function):
"""Decorator for requiring admin permission"""
def wrap(request, *args, **kwargs): def wrap(request, *args, **kwargs):
"""Wrap function of decorator"""
if not request.user.is_authenticated(): if not request.user.is_authenticated():
return redirect(reverse('account_login')) return redirect(reverse('account_login'))
if request.user.is_superuser: if request.user.is_superuser:
@ -15,7 +18,9 @@ def admin_required(function):
def ajax_required(function): def ajax_required(function):
"""Decorator for requiring request to be and ajax one"""
def wrap(request, *args, **kwargs): def wrap(request, *args, **kwargs):
"""Wrap function of decorator"""
if not request.is_ajax(): if not request.is_ajax():
return HttpResponseBadRequest() return HttpResponseBadRequest()
return function(request, *args, **kwargs) return function(request, *args, **kwargs)

View File

@ -1,3 +1,4 @@
"""SatNOGS Network django base Forms class"""
from django import forms from django import forms
from network.base.db_api import DBConnectionError, \ from network.base.db_api import DBConnectionError, \
@ -11,7 +12,7 @@ from network.base.validators import ObservationOverlapError, OutOfRangeError, \
class ObservationForm(forms.ModelForm): class ObservationForm(forms.ModelForm):
"""Model Form class for Observation objects"""
start = forms.DateTimeField( start = forms.DateTimeField(
input_formats=['%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S'], input_formats=['%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S'],
error_messages={ error_messages={
@ -41,6 +42,7 @@ class ObservationForm(forms.ModelForm):
) )
def clean_start(self): def clean_start(self):
"""Validates start datetime of a new observation"""
start = self.cleaned_data['start'] start = self.cleaned_data['start']
try: try:
check_start_datetime(start) check_start_datetime(start)
@ -49,6 +51,7 @@ class ObservationForm(forms.ModelForm):
return start return start
def clean_end(self): def clean_end(self):
"""Validates end datetime of a new observation"""
end = self.cleaned_data['end'] end = self.cleaned_data['end']
try: try:
check_end_datetime(end) check_end_datetime(end)
@ -57,6 +60,7 @@ class ObservationForm(forms.ModelForm):
return end return end
def clean(self): def clean(self):
"""Validates combination of start and end datetimes of a new observation"""
if any(self.errors): if any(self.errors):
# If there are errors in fields validation no need for validating the form # If there are errors in fields validation no need for validating the form
return return
@ -75,11 +79,14 @@ class ObservationForm(forms.ModelForm):
class BaseObservationFormSet(forms.BaseFormSet): class BaseObservationFormSet(forms.BaseFormSet):
"""Base FormSet class for Observation objects forms"""
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
"""Initializes Observation FormSet"""
self.user = user self.user = user
super(BaseObservationFormSet, self).__init__(*args, **kwargs) super(BaseObservationFormSet, self).__init__(*args, **kwargs)
def clean(self): def clean(self):
"""Validates Observation FormSet data"""
if any(self.errors): if any(self.errors):
# If there are errors in forms validation no need for validating the formset # If there are errors in forms validation no need for validating the formset
return return
@ -135,6 +142,7 @@ class BaseObservationFormSet(forms.BaseFormSet):
class StationForm(forms.ModelForm): class StationForm(forms.ModelForm):
"""Model Form class for Station objects"""
class Meta: class Meta:
model = Station model = Station
fields = [ fields = [
@ -145,6 +153,7 @@ class StationForm(forms.ModelForm):
class SatelliteFilterForm(forms.Form): class SatelliteFilterForm(forms.Form):
"""Form class for Satellite objects"""
norad = forms.IntegerField(required=False) norad = forms.IntegerField(required=False)
start = forms.CharField(required=False) start = forms.CharField(required=False)
end = forms.CharField(required=False) end = forms.CharField(required=False)

View File

@ -1,3 +1,4 @@
"""SatNOGS Network django management command to fetch data (Satellites and Transmitters)"""
import requests import requests
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
@ -6,7 +7,8 @@ from network.base.models import Satellite, Transmitter
class Command(BaseCommand): 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): def handle(self, *args, **options):
db_api_url = settings.DB_API_ENDPOINT db_api_url = settings.DB_API_ENDPOINT

View File

@ -1,3 +1,4 @@
"""SatNOGS Network django management command to fetch TLEs"""
from optparse import make_option from optparse import make_option
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
@ -7,6 +8,7 @@ from network.base.models import Satellite
class Command(BaseCommand): class Command(BaseCommand):
"""Django management command to update/insert TLEs for certain Satellites"""
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option( make_option(
'--delete', action='store_true', dest='delete', default=False, help='Delete Satellite' '--delete', action='store_true', dest='delete', default=False, help='Delete Satellite'

View File

@ -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 import call_command
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
@ -7,6 +8,7 @@ from network.base.tests import DemodDataFactory, ObservationFactory, \
class Command(BaseCommand): class Command(BaseCommand):
"""Django management command to initialize a new database"""
help = 'Create initial fixtures' help = 'Create initial fixtures'
def handle(self, *args, **options): def handle(self, *args, **options):

View File

@ -1,9 +1,11 @@
"""SatNOGS Network django management command to update TLE entries"""
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from network.base.tasks import update_all_tle from network.base.tasks import update_all_tle
class Command(BaseCommand): class Command(BaseCommand):
"""Django management command to update TLE entries"""
help = 'Update TLEs for existing Satellites' help = 'Update TLEs for existing Satellites'
def handle(self, *args, **options): def handle(self, *args, **options):

View File

@ -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.core.management.base import LabelCommand
from django.utils.timezone import now from django.utils.timezone import now
@ -5,6 +6,7 @@ from network.base.models import Station
class Command(LabelCommand): class Command(LabelCommand):
"""Django management command to set last seen value on stations entries"""
args = '<Station IDs>' args = '<Station IDs>'
help = 'Updates Last_Seen Timestamp for given Stations' help = 'Updates Last_Seen Timestamp for given Stations'

View File

@ -1,7 +1,10 @@
"""Django base manager for SatNOGS Network"""
from django.db import models from django.db import models
from django.utils.timezone import now from django.utils.timezone import now
class ObservationManager(models.QuerySet): class ObservationManager(models.QuerySet):
"""Observation Manager with extra functionality"""
def is_future(self): def is_future(self):
"""Return future observations"""
return self.filter(end__gte=now()) return self.filter(end__gte=now())

View File

@ -1,3 +1,4 @@
"""Django database base model for SatNOGS Network"""
import logging import logging
import os import os
from datetime import timedelta from datetime import timedelta
@ -56,10 +57,12 @@ TRANSMITTER_TYPE = ['Transmitter', 'Transceiver', 'Transponder']
def _name_obs_files(instance, filename): def _name_obs_files(instance, filename):
"""Return a filepath formatted by Observation ID"""
return 'data_obs/{0}/{1}'.format(instance.id, filename) return 'data_obs/{0}/{1}'.format(instance.id, filename)
def _name_obs_demoddata(instance, 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 # On change of the string bellow, change it also at api/views.py
return 'data_obs/{0}/{1}'.format(instance.observation.id, filename) 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 def _observation_post_save(sender, instance, created, **kwargs): # pylint: disable=W0613
""" """
Post save Observation operations Post save Observation operations
* Auto vet as good obserfvation with Demod Data * Auto vet as good observation with DemodData
* Mark Observations from testing stations * Mark Observations from testing stations
* Update client version for ground station * 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): def validate_image(fieldfile_obj):
"""Validates image size"""
filesize = fieldfile_obj.file.size filesize = fieldfile_obj.file.size
megabyte_limit = 2.0 megabyte_limit = 2.0
if filesize > megabyte_limit * 1024 * 1024: if filesize > megabyte_limit * 1024 * 1024:
@ -181,12 +185,14 @@ class Station(models.Model):
ordering = ['-status'] ordering = ['-status']
def get_image(self): 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'): if self.image and hasattr(self.image, 'url'):
return self.image.url return self.image.url
return settings.STATION_DEFAULT_IMAGE return settings.STATION_DEFAULT_IMAGE
@property @property
def is_online(self): def is_online(self):
"""Return true if station is online"""
try: try:
heartbeat = self.last_seen + timedelta(minutes=int(settings.STATION_HEARTBEAT_TIME)) heartbeat = self.last_seen + timedelta(minutes=int(settings.STATION_HEARTBEAT_TIME))
return heartbeat > now() return heartbeat > now()
@ -195,16 +201,19 @@ class Station(models.Model):
@property @property
def is_offline(self): def is_offline(self):
"""Return true if station is offline"""
return not self.is_online return not self.is_online
@property @property
def is_testing(self): def is_testing(self):
"""Return true if station is online and in testing mode"""
if self.is_online: if self.is_online:
if self.status == 1: if self.status == 1:
return True return True
return False return False
def state(self): def state(self):
"""Return the station status in html format"""
if not self.status: if not self.status:
return format_html('<span style="color:red;">Offline</span>') return format_html('<span style="color:red;">Offline</span>')
if self.status == 1: if self.status == 1:
@ -213,6 +222,7 @@ class Station(models.Model):
@property @property
def success_rate(self): 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)) rate = cache.get('station-{0}-rate'.format(self.id))
if not rate: if not rate:
observations = self.observations.exclude(testing=True).exclude(vetted_status="unknown") observations = self.observations.exclude(testing=True).exclude(vetted_status="unknown")
@ -228,16 +238,19 @@ class Station(models.Model):
@property @property
def observations_count(self): def observations_count(self):
"""Return the number of station's observations"""
count = self.observations.all().count() count = self.observations.all().count()
return count return count
@property @property
def observations_future_count(self): def observations_future_count(self):
"""Return the number of future station's observations"""
count = self.observations.is_future().count() count = self.observations.is_future().count()
return count return count
@property @property
def apikey(self): def apikey(self):
"""Return station owner API key"""
try: try:
token = Token.objects.get(user=self.owner) token = Token.objects.get(user=self.owner)
except Token.DoesNotExist: except Token.DoesNotExist:
@ -252,6 +265,7 @@ post_save.connect(_station_post_save, sender=Station)
class StationStatusLog(models.Model): class StationStatusLog(models.Model):
"""Model for keeping Status log for Station."""
station = models.ForeignKey( station = models.ForeignKey(
Station, related_name='station_logs', on_delete=models.CASCADE, null=True, blank=True 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'] ordering = ['norad_cat_id']
def get_image(self): def get_image(self):
"""Return the station image or the default if doesn't exist one"""
if self.image: if self.image:
return self.image return self.image
return settings.SATELLITE_DEFAULT_IMAGE return settings.SATELLITE_DEFAULT_IMAGE
@ -290,6 +305,7 @@ class Satellite(models.Model):
class Tle(models.Model): class Tle(models.Model):
"""Model for TLEs."""
tle0 = models.CharField(max_length=100, blank=True) tle0 = models.CharField(max_length=100, blank=True)
tle1 = models.CharField(max_length=200, blank=True) tle1 = models.CharField(max_length=200, blank=True)
tle2 = models.CharField(max_length=200, blank=True) tle2 = models.CharField(max_length=200, blank=True)
@ -307,6 +323,7 @@ class Tle(models.Model):
@property @property
def str_array(self): def str_array(self):
"""Return TLE in string array format"""
# tle fields are unicode, pyephem and others expect python strings # tle fields are unicode, pyephem and others expect python strings
return [str(self.tle0), str(self.tle1), str(self.tle2)] return [str(self.tle0), str(self.tle1), str(self.tle2)]
@ -395,34 +412,41 @@ class Observation(models.Model):
@property @property
def is_past(self): def is_past(self):
"""Return true if observation is in the past (end time is in the past)"""
return self.end < now() return self.end < now()
@property @property
def is_future(self): def is_future(self):
"""Return true if observation is in the future (end time is in the future)"""
return self.end > now() return self.end > now()
@property @property
def is_started(self): def is_started(self):
"""Return true if observation has started (start time is in the past)"""
return self.start < now() return self.start < now()
# this payload has been vetted good/bad/failed by someone # this payload has been vetted good/bad/failed by someone
@property @property
def is_vetted(self): def is_vetted(self):
"""Return true if observation is vetted"""
return not self.vetted_status == 'unknown' return not self.vetted_status == 'unknown'
# this payload has been vetted as good by someone # this payload has been vetted as good by someone
@property @property
def is_good(self): def is_good(self):
"""Return true if observation is vetted as good"""
return self.vetted_status == 'good' return self.vetted_status == 'good'
# this payload has been vetted as bad by someone # this payload has been vetted as bad by someone
@property @property
def is_bad(self): def is_bad(self):
"""Return true if observation is vetted as bad"""
return self.vetted_status == 'bad' return self.vetted_status == 'bad'
# this payload has been vetted as failed by someone # this payload has been vetted as failed by someone
@property @property
def is_failed(self): def is_failed(self):
"""Return true if observation is vetted as failed"""
return self.vetted_status == 'failed' return self.vetted_status == 'failed'
@property @property
@ -458,6 +482,7 @@ class Observation(models.Model):
@property @property
def audio_url(self): def audio_url(self):
"""Return url for observation's audio file"""
if self.has_audio: if self.has_audio:
if self.archive_url: if self.archive_url:
try: try:
@ -479,11 +504,13 @@ class Observation(models.Model):
return str(self.id) return str(self.id)
def get_absolute_url(self): def get_absolute_url(self):
"""Return absolute url of the model object"""
return reverse('base:observation_view', kwargs={'observation_id': self.id}) return reverse('base:observation_view', kwargs={'observation_id': self.id})
@receiver(models.signals.post_delete, sender=Observation) @receiver(models.signals.post_delete, sender=Observation)
def observation_remove_files(sender, instance, **kwargs): # pylint: disable=W0613 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 instance.payload:
if os.path.isfile(instance.payload.path): if os.path.isfile(instance.payload.path):
os.remove(instance.payload.path) os.remove(instance.payload.path)
@ -496,6 +523,7 @@ post_save.connect(_observation_post_save, sender=Observation)
class DemodData(models.Model): class DemodData(models.Model):
"""Model for DemodData."""
observation = models.ForeignKey( observation = models.ForeignKey(
Observation, related_name='demoddata', on_delete=models.CASCADE Observation, related_name='demoddata', on_delete=models.CASCADE
) )
@ -503,6 +531,7 @@ class DemodData(models.Model):
copied_to_db = models.BooleanField(default=False) copied_to_db = models.BooleanField(default=False)
def is_image(self): def is_image(self):
"""Return true if data file is an image"""
with open(self.payload_demod.path) as file_path: with open(self.payload_demod.path) as file_path:
try: try:
Image.open(file_path) Image.open(file_path)
@ -512,6 +541,7 @@ class DemodData(models.Model):
return True return True
def display_payload(self): def display_payload(self):
"""Return the content of the data file"""
with open(self.payload_demod.path) as file_path: with open(self.payload_demod.path) as file_path:
payload = file_path.read() payload = file_path.read()
try: try:
@ -523,6 +553,7 @@ class DemodData(models.Model):
@receiver(models.signals.post_delete, sender=DemodData) @receiver(models.signals.post_delete, sender=DemodData)
def demoddata_remove_files(sender, instance, **kwargs): # pylint: disable=W0613 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 instance.payload_demod:
if os.path.isfile(instance.payload_demod.path): if os.path.isfile(instance.payload_demod.path):
os.remove(instance.payload_demod.path) os.remove(instance.payload_demod.path)

View File

@ -1,7 +1,9 @@
"""SatNOGS Network base permissions"""
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
class UserNoPermissionError(Exception): class UserNoPermissionError(Exception):
"""Error when user has not persmission"""
pass pass
@ -56,6 +58,7 @@ def schedule_station_perms(user, station):
def check_schedule_perms_per_station(user, station_list): def check_schedule_perms_per_station(user, station_list):
"""Checks if user has permissions to schedule on stations"""
stations_without_permissions = [ stations_without_permissions = [
int(s.id) for s in station_list if not schedule_station_perms(user, s) int(s.id) for s in station_list if not schedule_station_perms(user, s)
] ]

View File

@ -1,3 +1,4 @@
"""SatNOGS Network scheduling functions"""
import math import math
from datetime import timedelta from datetime import timedelta
@ -12,6 +13,7 @@ from network.base.validators import NegativeElevationError, \
def get_altitude(observer, satellite, date): def get_altitude(observer, satellite, date):
"""Returns altitude of satellite in a specific date for a specific observer"""
observer = observer.copy() observer = observer.copy()
satellite = satellite.copy() satellite = satellite.copy()
observer.date = date observer.date = date
@ -20,6 +22,7 @@ def get_altitude(observer, satellite, date):
def get_azimuth(observer, satellite, date): def get_azimuth(observer, satellite, date):
"""Returns azimuth of satellite in a specific date for a specific observer"""
observer = observer.copy() observer = observer.copy()
satellite = satellite.copy() satellite = satellite.copy()
observer.date = date observer.date = date
@ -28,10 +31,12 @@ def get_azimuth(observer, satellite, date):
def over_min_duration(duration): def over_min_duration(duration):
"""Returns if duration is bigger than the minimum one set in settings"""
return duration > settings.OBSERVATION_DURATION_MIN return duration > settings.OBSERVATION_DURATION_MIN
def max_altitude_in_window(observer, satellite, pass_tca, window_start, window_end): 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 # In this case this is an overlapped observation
# re-calculate altitude and start/end azimuth # re-calculate altitude and start/end azimuth
if window_start > pass_tca: if window_start > pass_tca:
@ -86,6 +91,7 @@ def create_station_window(
window_start, window_end, azr, azs, altitude, tle, valid_duration, overlapped, window_start, window_end, azr, azs, altitude, tle, valid_duration, overlapped,
overlap_ratio=0 overlap_ratio=0
): ):
"""Creates an observation window"""
return { return {
'start': window_start.strftime("%Y-%m-%d %H:%M:%S.%f"), 'start': window_start.strftime("%Y-%m-%d %H:%M:%S.%f"),
'end': window_end.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): 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) 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 # Convert output of pyephems.next_pass into processible values
pass_start = make_aware(ephem.Date(rise_time).datetime(), utc) 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): 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()) scheduled_obs = Observation.objects.filter(ground_station=station).filter(end__gt=now())
window = resolve_overlaps(scheduled_obs, start, end) 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): def get_available_stations(stations, downlink, user):
"""Returns stations for scheduling filtered by a specific downlink and user's permissions"""
available_stations = [] available_stations = []
for station in stations: for station in stations:
if not schedule_station_perms(user, station): if not schedule_station_perms(user, station):

View File

@ -1,3 +1,4 @@
"""Module for calculating and keep in cache satellite and transmitter statistics"""
import math import math
from django.core.cache import cache from django.core.cache import cache
@ -8,6 +9,7 @@ from network.base.models import Observation
def transmitter_stats_by_uuid(uuid): def transmitter_stats_by_uuid(uuid):
"""Calculate and put in cache transmitter statistics"""
stats = cache.get('tr-{0}-stats'.format(uuid)) stats = cache.get('tr-{0}-stats'.format(uuid))
if stats is None: if stats is None:
# Sum - Case - When should be replaced with Count and filter when we move to Django 2.* # 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): def satellite_stats_by_transmitter_list(transmitter_list):
"""Calculate satellite statistics"""
total_count = 0 total_count = 0
unvetted_count = 0 unvetted_count = 0
future_count = 0 future_count = 0

View File

@ -98,6 +98,7 @@ def fetch_data():
@APP.task(ignore_result=True) @APP.task(ignore_result=True)
def archive_audio(obs_id): def archive_audio(obs_id):
"""Upload audio of observation in archive.org"""
obs = Observation.objects.get(id=obs_id) obs = Observation.objects.get(id=obs_id)
suffix = '-{0}'.format(settings.ENVIRONMENT) suffix = '-{0}'.format(settings.ENVIRONMENT)
if settings.ENVIRONMENT == 'production': if settings.ENVIRONMENT == 'production':
@ -216,6 +217,7 @@ def notify_for_stations_without_results():
@APP.task(ignore_result=True) @APP.task(ignore_result=True)
def stations_cache_rates(): def stations_cache_rates():
"""Cache the success rate of the stations"""
stations = Station.objects.all() stations = Station.objects.all()
for station in stations: for station in stations:
observations = station.observations.exclude(testing=True).exclude(vetted_status="unknown") observations = station.observations.exclude(testing=True).exclude(vetted_status="unknown")

View File

@ -1,3 +1,4 @@
"""Django template tags for SatNOGS Network"""
from django import template from django import template
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -6,6 +7,7 @@ register = template.Library()
@register.simple_tag @register.simple_tag
def active(request, urls): def active(request, urls):
"""Returns if this is an active URL"""
if request.path in (reverse(url) for url in urls.split()): if request.path in (reverse(url) for url in urls.split()):
return 'active' return 'active'
return None return None
@ -13,11 +15,13 @@ def active(request, urls):
@register.simple_tag @register.simple_tag
def drifted_frq(frq, drift): def drifted_frq(frq, drift):
"""Returns drifred frequency"""
return int(round(frq + ((frq * drift) / float(pow(10, 9))))) return int(round(frq + ((frq * drift) / float(pow(10, 9)))))
@register.filter @register.filter
def frq(value): def frq(value):
"""Returns Hz formatted frequency html string"""
try: try:
to_format = float(value) to_format = float(value)
except (TypeError, ValueError): except (TypeError, ValueError):
@ -29,6 +33,7 @@ def frq(value):
@register.filter @register.filter
def percentagerest(value): def percentagerest(value):
"""Returns the rest of percentage from a given (percentage) value"""
try: try:
return 100 - value return 100 - value
except (TypeError, ValueError): except (TypeError, ValueError):
@ -37,6 +42,7 @@ def percentagerest(value):
@register.filter @register.filter
def sortdemoddata(demoddata): def sortdemoddata(demoddata):
"""Returns a date sorted list of DemodData"""
try: try:
return sorted(list(demoddata), key=lambda x: str(x.payload_demod).split('/', 2)[2:]) return sorted(list(demoddata), key=lambda x: str(x.payload_demod).split('/', 2)[2:])
except (TypeError, ValueError): except (TypeError, ValueError):

View File

@ -1,3 +1,4 @@
"""Tag to replace field in GET request"""
from django import template from django import template
register = template.Library() register = template.Library()

View File

@ -1,3 +1,4 @@
"""SatNOGS Network base test suites"""
import random import random
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -20,6 +21,7 @@ OBSERVATION_STATUS_IDS = [c[0] for c in OBSERVATION_STATUSES]
def generate_payload(): def generate_payload():
"""Create data payloads"""
payload = '{0:b}'.format(random.randint(500000000, 510000000)) payload = '{0:b}'.format(random.randint(500000000, 510000000))
digits = 1824 digits = 1824
while digits: while digits:
@ -30,6 +32,7 @@ def generate_payload():
def generate_payload_name(): def generate_payload_name():
"""Create payload names"""
filename = datetime.strftime( filename = datetime.strftime(
fuzzy.FuzzyDateTime(now() - timedelta(days=10), now()).fuzz(), '%Y%m%dT%H%M%SZ' 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 @factory.post_generation
def antennas(self, create, extracted, **kwargs): # pylint: disable=W0613 def antennas(self, create, extracted, **kwargs): # pylint: disable=W0613
"""Generate antennas"""
if not create: if not create:
return return
@ -128,6 +132,7 @@ class ObservationFactory(factory.django.DjangoModelFactory):
class DemodDataFactory(factory.django.DjangoModelFactory): class DemodDataFactory(factory.django.DjangoModelFactory):
"""DemodData model factory."""
observation = factory.Iterator(Observation.objects.all()) observation = factory.Iterator(Observation.objects.all())
payload_demod = factory.django.FileField() payload_demod = factory.django.FileField()
@ -141,6 +146,7 @@ class HomeViewTest(TestCase):
Simple test to make sure the home page is working Simple test to make sure the home page is working
""" """
def test_home_page(self): def test_home_page(self):
"""Test for string in home page"""
response = self.client.get('/') response = self.client.get('/')
self.assertContains(response, 'Crowd-sourced satellite operations') self.assertContains(response, 'Crowd-sourced satellite operations')
@ -151,6 +157,7 @@ class AboutViewTest(TestCase):
Simple test to make sure the about page is working Simple test to make sure the about page is working
""" """
def test_about_page(self): def test_about_page(self):
"""Test for string in about page"""
response = self.client.get('/about/') response = self.client.get('/about/')
self.assertContains(response, 'SatNOGS Network is a global management interface') self.assertContains(response, 'SatNOGS Network is a global management interface')
@ -168,6 +175,7 @@ class StationListViewTest(TestCase):
self.stations.append(StationFactory()) self.stations.append(StationFactory())
def test_station_list(self): def test_station_list(self):
"""Test for owners and station names in station page"""
response = self.client.get('/stations/') response = self.client.get('/stations/')
for station in self.stations: for station in self.stations:
self.assertContains(response, station.owner) self.assertContains(response, station.owner)
@ -213,23 +221,27 @@ class ObservationsListViewTest(TestCase):
self.observations.append(obs) self.observations.append(obs)
def test_observations_list(self): def test_observations_list(self):
"""Test for transmitter modes of each observation in observations page"""
response = self.client.get('/observations/') response = self.client.get('/observations/')
for observation in self.observations: for observation in self.observations:
self.assertContains(response, observation.transmitter_mode) self.assertContains(response, observation.transmitter_mode)
def test_observations_list_select_bad(self): 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') response = self.client.get('/observations/?bad=1')
for observation in self.observations_bad: for observation in self.observations_bad:
self.assertContains(response, observation.transmitter_mode) self.assertContains(response, observation.transmitter_mode)
def test_observations_list_select_good(self): 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') response = self.client.get('/observations/?good=1')
for observation in self.observations_good: for observation in self.observations_good:
self.assertContains(response, observation.transmitter_mode) self.assertContains(response, observation.transmitter_mode)
def test_observations_list_select_unvetted(self): 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') response = self.client.get('/observations/?unvetted=1')
for observation in self.observations_unvetted: for observation in self.observations_unvetted:
@ -243,6 +255,7 @@ class NotFoundErrorTest(TestCase):
client = Client() client = Client()
def test_404_not_found(self): 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') response = self.client.get('/blah')
self.assertEquals(response.status_code, 404) self.assertEquals(response.status_code, 404)
@ -254,6 +267,7 @@ class RobotsViewTest(TestCase):
client = Client() client = Client()
def test_robots(self): def test_robots(self):
"""Test for "Disallow" string in response for requesting robots.txt"""
response = self.client.get('/robots.txt') response = self.client.get('/robots.txt')
self.assertContains(response, 'Disallow: /') self.assertContains(response, 'Disallow: /')
@ -280,6 +294,7 @@ class ObservationViewTest(TestCase):
self.observation = ObservationFactory() self.observation = ObservationFactory()
def test_observation(self): def test_observation(self):
"""Test for observer and transmitter mode in observation page"""
response = self.client.get('/observations/%d/' % self.observation.id) response = self.client.get('/observations/%d/' % self.observation.id)
self.assertContains(response, self.observation.author.username) self.assertContains(response, self.observation.author.username)
self.assertContains(response, self.observation.transmitter_mode) self.assertContains(response, self.observation.transmitter_mode)
@ -363,6 +378,7 @@ class StationViewTest(TestCase):
self.station = StationFactory() self.station = StationFactory()
def test_observation(self): def test_observation(self):
"""Test for owner, elevation and min horizon in station page"""
response = self.client.get('/stations/%d/' % self.station.id) response = self.client.get('/stations/%d/' % self.station.id)
self.assertContains(response, self.station.owner.username) self.assertContains(response, self.station.owner.username)
self.assertContains(response, self.station.alt) self.assertContains(response, self.station.alt)
@ -386,6 +402,7 @@ class StationDeleteTest(TestCase):
self.station.save() self.station.save()
def test_station_delete(self): def test_station_delete(self):
"""Test deletion of station"""
response = self.client.get('/stations/%d/delete/' % self.station.id) response = self.client.get('/stations/%d/delete/' % self.station.id)
self.assertRedirects(response, '/users/%s/' % self.user.username) self.assertRedirects(response, '/users/%s/' % self.user.username)
with self.assertRaises(Station.DoesNotExist): with self.assertRaises(Station.DoesNotExist):
@ -407,6 +424,7 @@ class SettingsSiteViewTest(TestCase):
self.client.force_login(self.user) self.client.force_login(self.user)
def test_get(self): def test_get(self):
"""Test for "Fetch Data" in Settings Site page"""
response = self.client.get('/settings_site/') response = self.client.get('/settings_site/')
self.assertContains(response, 'Fetch Data') self.assertContains(response, 'Fetch Data')
@ -429,4 +447,5 @@ class ObservationModelTest(TestCase):
self.observation.save() self.observation.save()
def test_is_passed(self): def test_is_passed(self):
"""Test for observation be in past"""
self.assertTrue(self.observation.is_past) self.assertTrue(self.observation.is_past)

View File

@ -1,3 +1,4 @@
"""Django base URL routings for SatNOGS Network"""
from django.conf.urls import url from django.conf.urls import url
from django.views.generic import TemplateView from django.views.generic import TemplateView

View File

@ -1,3 +1,4 @@
"""Miscellaneous functions for SatNOGS Network"""
import csv import csv
import urllib import urllib
import urllib2 import urllib2
@ -13,6 +14,7 @@ from network.base.models import DemodData
def export_as_csv(modeladmin, request, queryset): def export_as_csv(modeladmin, request, queryset):
"""Exports admin panel table in csv format"""
if not request.user.is_staff: if not request.user.is_staff:
raise PermissionDenied raise PermissionDenied
opts = modeladmin.model._meta opts = modeladmin.model._meta
@ -54,6 +56,7 @@ def export_as_csv(modeladmin, request, queryset):
def export_station_status(self, request, queryset): def export_station_status(self, request, queryset):
"""Exports status of selected stations in csv format"""
meta = self.model._meta meta = self.model._meta
field_names = ["id", "status"] field_names = ["id", "status"]
@ -69,7 +72,7 @@ def export_station_status(self, request, queryset):
def demod_to_db(frame_id): 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) frame = DemodData.objects.get(id=frame_id)
obs = frame.observation obs = frame.observation
sat = obs.satellite sat = obs.satellite

View File

@ -1,3 +1,4 @@
"""Django base validators for SatNOGS Network"""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.conf import settings from django.conf import settings
@ -5,22 +6,27 @@ from django.utils.timezone import make_aware, utc
class ObservationOverlapError(Exception): class ObservationOverlapError(Exception):
"""Error when observation overlaps with already scheduled one"""
pass pass
class OutOfRangeError(Exception): class OutOfRangeError(Exception):
"""Error when transmitter is our of station's antenna frequency range"""
pass pass
class NegativeElevationError(Exception): class NegativeElevationError(Exception):
"""Error when satellite doesn't raise above station's horizon"""
pass pass
class SinglePassError(Exception): class SinglePassError(Exception):
"""Error when between given start and end datetimes there are more than one satellite passes"""
pass pass
def check_start_datetime(start): def check_start_datetime(start):
"""Validate start datetime"""
if start < make_aware(datetime.now(), utc): if start < make_aware(datetime.now(), utc):
raise ValueError("Start datetime should be in the future!") raise ValueError("Start datetime should be in the future!")
if start < make_aware(datetime.now() + timedelta(minutes=settings.OBSERVATION_DATE_MIN_START), 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): def check_end_datetime(end):
"""Validate end datetime"""
if end < make_aware(datetime.now(), utc): if end < make_aware(datetime.now(), utc):
raise ValueError("End datetime should be in the future!") raise ValueError("End datetime should be in the future!")
max_duration = settings.OBSERVATION_DATE_MIN_START + settings.OBSERVATION_DATE_MAX_RANGE 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): def check_start_end_datetimes(start, end):
"""Validate the pair of start and end datetimes"""
if start > end: if start > end:
raise ValueError("End datetime should be after Start datetime!") raise ValueError("End datetime should be after Start datetime!")
if (end - start) < timedelta(seconds=settings.OBSERVATION_DURATION_MIN): 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): 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: if transmitter['downlink_low'] is not None:
return antenna.frequency <= transmitter['downlink_low'] <= antenna.frequency_max return antenna.frequency <= transmitter['downlink_low'] <= antenna.frequency_max
return False return False
def is_transmitter_in_station_range(transmitter, station): 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(): for gs_antenna in station.antenna.all():
if downlink_low_is_in_range(gs_antenna, transmitter): if downlink_low_is_in_range(gs_antenna, transmitter):
return True return True
@ -68,6 +78,7 @@ def is_transmitter_in_station_range(transmitter, station):
def check_transmitter_station_pairs(transmitter_station_list): def check_transmitter_station_pairs(transmitter_station_list):
"""Validate the pairs of transmitter and stations"""
out_of_range_pairs = [ out_of_range_pairs = [
(str(pair[0]['uuid']), int(pair[1].id)) for pair in transmitter_station_list (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]) 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): def check_overlaps(stations_dict):
"""Check for overlaps among requested observations"""
for station in stations_dict.keys(): for station in stations_dict.keys():
periods = stations_dict[station] periods = stations_dict[station]
total_periods = len(periods) total_periods = len(periods)

View File

@ -1,3 +1,4 @@
"""Django base views for SatNOGS Network"""
import urllib2 import urllib2
from collections import defaultdict from collections import defaultdict
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -38,6 +39,7 @@ from network.users.models import User
class StationSerializer(serializers.ModelSerializer): class StationSerializer(serializers.ModelSerializer):
"""Django model Serializer for Station model"""
status_display = serializers.SerializerMethodField() status_display = serializers.SerializerMethodField()
class Meta: class Meta:
@ -45,6 +47,7 @@ class StationSerializer(serializers.ModelSerializer):
fields = ('name', 'lat', 'lng', 'id', 'status', 'status_display') fields = ('name', 'lat', 'lng', 'id', 'status', 'status_display')
def get_status_display(self, obj): def get_status_display(self, obj):
"""Returns the station status"""
try: try:
return obj.get_status_display() return obj.get_status_display()
except AttributeError: except AttributeError:
@ -52,6 +55,7 @@ class StationSerializer(serializers.ModelSerializer):
class StationAllView(viewsets.ReadOnlyModelViewSet): class StationAllView(viewsets.ReadOnlyModelViewSet):
"""Station View of all non offline stations"""
queryset = Station.objects.exclude(status=0) queryset = Station.objects.exclude(status=0)
serializer_class = StationSerializer serializer_class = StationSerializer
@ -67,6 +71,7 @@ def index(request):
def robots(request): def robots(request):
"""Returns response for robots.txt requests"""
data = render(request, 'robots.txt', {'environment': settings.ENVIRONMENT}) data = render(request, 'robots.txt', {'environment': settings.ENVIRONMENT})
response = HttpResponse(data, content_type='text/plain; charset=utf-8') response = HttpResponse(data, content_type='text/plain; charset=utf-8')
return response return response
@ -221,6 +226,7 @@ class ObservationListView(ListView):
def observation_new_post(request): def observation_new_post(request):
"""Handles POST requests for creating one or more new observations."""
ObservationFormSet = formset_factory( # pylint: disable=C0103 ObservationFormSet = formset_factory( # pylint: disable=C0103
ObservationForm, formset=BaseObservationFormSet, min_num=1, validate_min=True ObservationForm, formset=BaseObservationFormSet, min_num=1, validate_min=True
) )
@ -348,6 +354,7 @@ def observation_new(request):
@ajax_required @ajax_required
def prediction_windows(request): def prediction_windows(request):
"""Calculates and returns passes of satellites over stations"""
sat_norad_id = request.POST['satellite'] sat_norad_id = request.POST['satellite']
transmitter = request.POST['transmitter'] transmitter = request.POST['transmitter']
start = request.POST['start'] start = request.POST['start']
@ -505,6 +512,7 @@ def observation_delete(request, observation_id):
@login_required @login_required
@ajax_required @ajax_required
def observation_vet(request, observation_id): def observation_vet(request, observation_id):
"""Handles request for vetting an observation"""
try: try:
observation = Observation.objects.get(id=observation_id) observation = Observation.objects.get(id=observation_id)
except Observation.DoesNotExist: except Observation.DoesNotExist:
@ -828,6 +836,7 @@ def station_delete(request, station_id):
def transmitters_with_stats(transmitters_list): def transmitters_with_stats(transmitters_list):
"""Returns a list of transmitters with their statistics"""
transmitters_with_stats_list = [] transmitters_with_stats_list = []
for transmitter in transmitters_list: for transmitter in transmitters_list:
transmitter_stats = transmitter_stats_by_uuid(transmitter['uuid']) transmitter_stats = transmitter_stats_by_uuid(transmitter['uuid'])
@ -837,6 +846,7 @@ def transmitters_with_stats(transmitters_list):
def satellite_view(request, norad_id): def satellite_view(request, norad_id):
"""Returns a satellite JSON object with information and statistics"""
try: try:
sat = Satellite.objects.get(norad_cat_id=norad_id) sat = Satellite.objects.get(norad_cat_id=norad_id)
except Satellite.DoesNotExist: except Satellite.DoesNotExist:
@ -867,6 +877,7 @@ def satellite_view(request, norad_id):
def transmitters_view(request): def transmitters_view(request):
"""Returns a transmitter JSON object with information and statistics"""
norad_id = request.POST['satellite'] norad_id = request.POST['satellite']
station_id = request.POST.get('station_id', None) station_id = request.POST.get('station_id', None)
try: try:

View File

@ -1,3 +1,4 @@
"""SatNOGS Network celery task workers"""
from __future__ import absolute_import from __future__ import absolute_import
import os import os
@ -21,6 +22,7 @@ APP.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
@APP.on_after_finalize.connect @APP.on_after_finalize.connect
def setup_periodic_tasks(sender, **kwargs): # pylint: disable=W0613 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 ( from network.base.tasks import (
update_all_tle, fetch_data, clean_observations, station_status_update, update_all_tle, fetch_data, clean_observations, station_status_update,
stations_cache_rates, notify_for_stations_without_results, sync_to_db stations_cache_rates, notify_for_stations_without_results, sync_to_db

View File

@ -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 import sentry_sdk
from decouple import Csv, config from decouple import Csv, config
from dj_database_url import parse as db_url from dj_database_url import parse as db_url

View File

@ -43,7 +43,7 @@
<h4 class="modal-title" id="myModalLabel">Join SatNGOS Network</h4> <h4 class="modal-title" id="myModalLabel">Join SatNGOS Network</h4>
</div> </div>
<div class="modal-body"> <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 operations remotely. An observer is able to take advantage of the full network of
SatNOGS ground stations around the world. SatNOGS ground stations around the world.
<h3>Observations</h3> <h3>Observations</h3>

View File

@ -1,3 +1,4 @@
"""Base Django URL mapping for SatNOGS Network"""
from allauth import urls as allauth_urls from allauth import urls as allauth_urls
from avatar import urls as avatar_urls from avatar import urls as avatar_urls
from django.conf import settings from django.conf import settings

View File

@ -1,3 +1,4 @@
"""Define functions and settings for the django admin users interface"""
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm from django.contrib.auth.forms import UserChangeForm, UserCreationForm
@ -7,6 +8,7 @@ from network.users.models import User
class HasStationListFilter(admin.SimpleListFilter): class HasStationListFilter(admin.SimpleListFilter):
"""Filter users by checking if the own a station or not"""
title = 'having station' title = 'having station'
parameter_name = 'has_station' parameter_name = 'has_station'
@ -25,6 +27,7 @@ class HasStationListFilter(admin.SimpleListFilter):
class UserAdmin(AuthUserAdmin): class UserAdmin(AuthUserAdmin):
"""Class of AuthUserAdmin"""
create_form_class = UserCreationForm create_form_class = UserCreationForm
update_form_class = UserChangeForm update_form_class = UserChangeForm
list_filter = AuthUserAdmin.list_filter + (HasStationListFilter, ) list_filter = AuthUserAdmin.list_filter + (HasStationListFilter, )

View File

@ -1,9 +1,11 @@
"""Django users forms for SatNOGS Network"""
from django import forms from django import forms
from network.users.models import User from network.users.models import User
class UserForm(forms.ModelForm): class UserForm(forms.ModelForm):
"""Model Form class for User objects"""
class Meta: class Meta:
model = User model = User
fields = ("first_name", "last_name") fields = ("first_name", "last_name")

View File

@ -1,3 +1,4 @@
"""Django database users model for SatNOGS Network"""
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core.validators import MaxLengthValidator from django.core.validators import MaxLengthValidator
from django.db import models 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 def gen_token(sender, instance, created, **kwargs): # pylint: disable=W0613
"""Generate token for user"""
try: try:
Token.objects.get(user=instance) Token.objects.get(user=instance)
except Token.DoesNotExist: except Token.DoesNotExist:
@ -19,6 +21,7 @@ class User(AbstractUser):
@property @property
def displayname(self): def displayname(self):
"""Return the display name of user"""
if self.get_full_name(): if self.get_full_name():
return self.get_full_name() return self.get_full_name()
return self.username return self.username

View File

@ -1,3 +1,4 @@
"""SatNOGS Network users test suites"""
import datetime import datetime
import factory import factory
@ -40,5 +41,6 @@ class UserViewTest(TestCase):
self.client.force_login(self.user) self.client.force_login(self.user)
def test_view_user(self): def test_view_user(self):
"""Test of user view"""
response = self.client.get('/users/%s/' % self.user.username) response = self.client.get('/users/%s/' % self.user.username)
self.assertContains(response, self.user.username) self.assertContains(response, self.user.username)

View File

@ -1,3 +1,4 @@
"""Django users URL routings for SatNOGS Network"""
from django.conf.urls import url from django.conf.urls import url
from network.users import views from network.users import views

View File

@ -1,3 +1,4 @@
"""Django users views for SatNOGS Network"""
from braces.views import LoginRequiredMixin from braces.views import LoginRequiredMixin
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Count from django.db.models import Count
@ -13,6 +14,7 @@ from network.users.models import User
class UserRedirectView(LoginRequiredMixin, RedirectView): class UserRedirectView(LoginRequiredMixin, RedirectView):
"""View for user redirect"""
permanent = False permanent = False
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
@ -20,7 +22,7 @@ class UserRedirectView(LoginRequiredMixin, RedirectView):
class UserUpdateView(LoginRequiredMixin, UpdateView): class UserUpdateView(LoginRequiredMixin, UpdateView):
"""View for user update"""
form_class = UserForm form_class = UserForm
model = User model = User

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
"""WSGI module for SatNOGS Network"""
import os import os
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application