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]
disable=
C0111,
C0412,
E1101,
E1121,

View File

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

View File

@ -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'

View File

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

View File

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

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
"""SatNOGS Network Auth0 login module test suites"""
from __future__ import unicode_literals
# 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 . import views

View File

@ -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')

View File

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

View File

@ -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']

View File

@ -1,3 +1,4 @@
"""SatNOGS Network API permissions, django rest framework"""
from rest_framework import permissions
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 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])

View File

@ -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 = [

View File

@ -1,3 +1,4 @@
"""SatNOGS Network django rest framework API url routings"""
from rest_framework import routers
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.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():

View File

@ -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')

View File

@ -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()

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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'

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.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):

View File

@ -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):

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

View File

@ -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())

View File

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

View File

@ -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)
]

View File

@ -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):

View File

@ -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

View File

@ -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")

View File

@ -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):

View File

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

View File

@ -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)

View File

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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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

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
from decouple import Csv, config
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>
</div>
<div class="modal-body">
SatNOGS network is a global management interface to facilitate multiple ground station
SatNOGS Network is a global management interface to facilitate multiple ground station
operations remotely. An observer is able to take advantage of the full network of
SatNOGS ground stations around the world.
<h3>Observations</h3>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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