diff --git a/.pylintrc b/.pylintrc index 9c289cd..2b990f7 100644 --- a/.pylintrc +++ b/.pylintrc @@ -5,7 +5,6 @@ ignored-argument-names=args|kwargs [MESSAGES CONTROL] disable= - C0111, C0412, E1101, E1121, diff --git a/auth0login/admin.py b/auth0login/admin.py index a3846a1..c998e5e 100644 --- a/auth0login/admin.py +++ b/auth0login/admin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +"""SatNOGS Network Auth0 login module admin class""" from __future__ import unicode_literals # from django.contrib import admin diff --git a/auth0login/apps.py b/auth0login/apps.py index 513c748..798f8f8 100644 --- a/auth0login/apps.py +++ b/auth0login/apps.py @@ -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' diff --git a/auth0login/auth0backend.py b/auth0login/auth0backend.py index b4e3a29..79757ba 100644 --- a/auth0login/auth0backend.py +++ b/auth0login/auth0backend.py @@ -1,3 +1,4 @@ +"""SatNOGS Network Auth0 login module auth backend""" import requests from social_core.backends.oauth import BaseOAuth2 diff --git a/auth0login/models.py b/auth0login/models.py index 1e8e4e1..3fcb4f0 100644 --- a/auth0login/models.py +++ b/auth0login/models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +"""SatNOGS Network Auth0 login module models""" from __future__ import unicode_literals # from django.db import models diff --git a/auth0login/tests.py b/auth0login/tests.py index c2de5b3..e81eb05 100644 --- a/auth0login/tests.py +++ b/auth0login/tests.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +"""SatNOGS Network Auth0 login module test suites""" from __future__ import unicode_literals # from django.test import TestCase diff --git a/auth0login/urls.py b/auth0login/urls.py index 6f9e6ff..4601b16 100644 --- a/auth0login/urls.py +++ b/auth0login/urls.py @@ -1,3 +1,4 @@ +"""SatNOGS Network Auth0 login module URL routers""" from django.conf.urls import include, url from . import views diff --git a/auth0login/views.py b/auth0login/views.py index a08b706..fd3ee4d 100644 --- a/auth0login/views.py +++ b/auth0login/views.py @@ -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') diff --git a/network/__init__.py b/network/__init__.py index 9372f02..f5aac52 100644 --- a/network/__init__.py +++ b/network/__init__.py @@ -1,3 +1,4 @@ +"""The core django app for SatNOGS Network""" from __future__ import absolute_import from ._version import get_versions diff --git a/network/api/filters.py b/network/api/filters.py index 74bca0a..9938da9 100644 --- a/network/api/filters.py +++ b/network/api/filters.py @@ -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'] diff --git a/network/api/perms.py b/network/api/perms.py index 78fdced..e65616d 100644 --- a/network/api/perms.py +++ b/network/api/perms.py @@ -1,3 +1,4 @@ +"""SatNOGS Network API permissions, django rest framework""" from rest_framework import permissions from network.base.perms import schedule_perms diff --git a/network/api/serializers.py b/network/api/serializers.py index 4dbed53..6227f4f 100644 --- a/network/api/serializers.py +++ b/network/api/serializers.py @@ -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]) diff --git a/network/api/tests.py b/network/api/tests.py index ed9ab81..62d4ff3 100644 --- a/network/api/tests.py +++ b/network/api/tests.py @@ -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 = [ diff --git a/network/api/urls.py b/network/api/urls.py index 3fe92bd..a93967a 100644 --- a/network/api/urls.py +++ b/network/api/urls.py @@ -1,3 +1,4 @@ +"""SatNOGS Network django rest framework API url routings""" from rest_framework import routers from network.api import views diff --git a/network/api/views.py b/network/api/views.py index 8ee92d2..19973de 100644 --- a/network/api/views.py +++ b/network/api/views.py @@ -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(): diff --git a/network/base/admin.py b/network/base/admin.py index 3f25bfd..ceda452 100644 --- a/network/base/admin.py +++ b/network/base/admin.py @@ -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') diff --git a/network/base/context_processors.py b/network/base/context_processors.py index 775e1fa..1f8748d 100644 --- a/network/base/context_processors.py +++ b/network/base/context_processors.py @@ -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() diff --git a/network/base/db_api.py b/network/base/db_api.py index 9bf7f79..73f0036 100644 --- a/network/base/db_api.py +++ b/network/base/db_api.py @@ -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: diff --git a/network/base/decorators.py b/network/base/decorators.py index a4c7d25..7625f4f 100644 --- a/network/base/decorators.py +++ b/network/base/decorators.py @@ -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) diff --git a/network/base/forms.py b/network/base/forms.py index e523dac..af07e99 100644 --- a/network/base/forms.py +++ b/network/base/forms.py @@ -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) diff --git a/network/base/management/commands/fetch_data.py b/network/base/management/commands/fetch_data.py index 9ac9b83..eece342 100644 --- a/network/base/management/commands/fetch_data.py +++ b/network/base/management/commands/fetch_data.py @@ -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 diff --git a/network/base/management/commands/fetch_tle.py b/network/base/management/commands/fetch_tle.py index 08df265..0117e33 100644 --- a/network/base/management/commands/fetch_tle.py +++ b/network/base/management/commands/fetch_tle.py @@ -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' diff --git a/network/base/management/commands/initialize.py b/network/base/management/commands/initialize.py index afd83a9..edb18e3 100644 --- a/network/base/management/commands/initialize.py +++ b/network/base/management/commands/initialize.py @@ -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): diff --git a/network/base/management/commands/update_all_tle.py b/network/base/management/commands/update_all_tle.py index 8914780..8d6ef70 100644 --- a/network/base/management/commands/update_all_tle.py +++ b/network/base/management/commands/update_all_tle.py @@ -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): diff --git a/network/base/management/commands/update_station_last_seen.py b/network/base/management/commands/update_station_last_seen.py index 273aba4..78a4871 100644 --- a/network/base/management/commands/update_station_last_seen.py +++ b/network/base/management/commands/update_station_last_seen.py @@ -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 = '' help = 'Updates Last_Seen Timestamp for given Stations' diff --git a/network/base/managers.py b/network/base/managers.py index 8d94d8e..a843e7a 100644 --- a/network/base/managers.py +++ b/network/base/managers.py @@ -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()) diff --git a/network/base/models.py b/network/base/models.py index 7fa3994..b468a0a 100644 --- a/network/base/models.py +++ b/network/base/models.py @@ -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('Offline') 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) diff --git a/network/base/perms.py b/network/base/perms.py index 778166f..3f29ed7 100644 --- a/network/base/perms.py +++ b/network/base/perms.py @@ -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) ] diff --git a/network/base/scheduling.py b/network/base/scheduling.py index ac990b3..5b0a1b5 100644 --- a/network/base/scheduling.py +++ b/network/base/scheduling.py @@ -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): diff --git a/network/base/stats.py b/network/base/stats.py index a8bef41..8819d43 100644 --- a/network/base/stats.py +++ b/network/base/stats.py @@ -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 diff --git a/network/base/tasks.py b/network/base/tasks.py index f557a40..2ad4f1f 100644 --- a/network/base/tasks.py +++ b/network/base/tasks.py @@ -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") diff --git a/network/base/templatetags/tags.py b/network/base/templatetags/tags.py index 5312c64..a6c7f5a 100644 --- a/network/base/templatetags/tags.py +++ b/network/base/templatetags/tags.py @@ -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): diff --git a/network/base/templatetags/url_replace.py b/network/base/templatetags/url_replace.py index 644ae10..fbe1808 100644 --- a/network/base/templatetags/url_replace.py +++ b/network/base/templatetags/url_replace.py @@ -1,3 +1,4 @@ +"""Tag to replace field in GET request""" from django import template register = template.Library() diff --git a/network/base/tests.py b/network/base/tests.py index eb4d16a..80cb960 100644 --- a/network/base/tests.py +++ b/network/base/tests.py @@ -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) diff --git a/network/base/urls.py b/network/base/urls.py index 5a700a1..27e5089 100644 --- a/network/base/urls.py +++ b/network/base/urls.py @@ -1,3 +1,4 @@ +"""Django base URL routings for SatNOGS Network""" from django.conf.urls import url from django.views.generic import TemplateView diff --git a/network/base/utils.py b/network/base/utils.py index cfe56e0..0b6af56 100644 --- a/network/base/utils.py +++ b/network/base/utils.py @@ -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 diff --git a/network/base/validators.py b/network/base/validators.py index 7d28d2c..2a6d47b 100644 --- a/network/base/validators.py +++ b/network/base/validators.py @@ -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) diff --git a/network/base/views.py b/network/base/views.py index 751dd1d..e3161a4 100644 --- a/network/base/views.py +++ b/network/base/views.py @@ -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: diff --git a/network/celery.py b/network/celery.py index 0ed5aee..9df1579 100644 --- a/network/celery.py +++ b/network/celery.py @@ -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 diff --git a/network/settings.py b/network/settings.py index fb5a37f..ffc1210 100644 --- a/network/settings.py +++ b/network/settings.py @@ -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 diff --git a/network/templates/base/home.html b/network/templates/base/home.html index 593922c..6bd3285 100644 --- a/network/templates/base/home.html +++ b/network/templates/base/home.html @@ -43,7 +43,7 @@