utils.py 6.61 KB
Newer Older
1
"""Miscellaneous functions for SatNOGS Network"""
Pierros Papadeas's avatar
Pierros Papadeas committed
2
import csv
3
from builtins import str
4
from datetime import datetime
5

6
import requests  # pylint: disable=C0412
7
from django.conf import settings
8
9
10
from django.contrib.admin.helpers import label_for_field
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
11
from django.utils.text import slugify
12
from requests.exceptions import RequestException
13

Pierros Papadeas's avatar
Pierros Papadeas committed
14

15
16
17
18
def populate_formset_error_messages(messages, request, formset):
    """Add errors to django messages framework by extracting them from formset)"""
    non_form_errors = formset.non_form_errors()
    if non_form_errors:
19
        messages.error(request, str(non_form_errors[0]))
20
21
22
23
24
25
26
27
        return
    for error in formset.errors:
        if error:
            for field in error:
                messages.error(request, str(error[field][0]))
        return


28
29
30
31
32
33
34
def bands_from_range(min_frequency, max_frequency):
    """Returns band names of the given frequency range based on
    https://www.itu.int/rec/R-REC-V.431-8-201508-I/en recommendation from ITU
    """
    if max_frequency < min_frequency:
        return []

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    frequency_bands = {
        'ULF': (300, 3000),
        'VLF': (3000, 30000),
        'LF': (30000, 300000),
        'MF': (300000, 3000000),
        'HF': (3000000, 30000000),
        'VHF': (30000000, 300000000),
        'UHF': (300000000, 1000000000),
        'L': (1000000000, 2000000000),
        'S': (2000000000, 4000000000),
        'C': (4000000000, 8000000000),
        'X': (8000000000, 12000000000),
        'Ku': (12000000000, 18000000000),
        'K': (18000000000, 27000000000),
        'Ka': (27000000000, 40000000000),
    }
51
52
53

    bands = []
    found_min = False
54
55

    for name, (min_freq, max_freq) in frequency_bands.items():
56
57
58
59
60
61
62
63
64
65
66
67
68
69
        if not found_min:
            if min_freq <= min_frequency <= max_freq:
                bands.append(name)
                if min_freq <= max_frequency <= max_freq:
                    return bands
                found_min = True
                continue
            continue
        bands.append(name)
        if min_freq < max_frequency <= max_freq:
            return bands
    return []


Pierros Papadeas's avatar
Pierros Papadeas committed
70
def export_as_csv(modeladmin, request, queryset):
71
    """Exports admin panel table in csv format"""
Pierros Papadeas's avatar
Pierros Papadeas committed
72
73
74
75
76
77
78
    if not request.user.is_staff:
        raise PermissionDenied
    field_names = modeladmin.list_display
    if 'action_checkbox' in field_names:
        field_names.remove('action_checkbox')

    response = HttpResponse(content_type="text/csv")
79
80
81
    response['Content-Disposition'] = 'attachment; filename={}.csv'.format(
        str(modeladmin.model._meta).replace('.', '_')
    )
Pierros Papadeas's avatar
Pierros Papadeas committed
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

    writer = csv.writer(response)
    headers = []
    for field_name in list(field_names):
        label = label_for_field(field_name, modeladmin.model, modeladmin)
        if label.islower():
            label = label.title()
        headers.append(label)
    writer.writerow(headers)
    for row in queryset:
        values = []
        for field in field_names:
            try:
                value = (getattr(row, field))
            except AttributeError:
                value = (getattr(modeladmin, field))
            if callable(value):
                try:
                    # get value from model
                    value = value()
102
                except TypeError:
Pierros Papadeas's avatar
Pierros Papadeas committed
103
104
105
106
                    # get value from modeladmin e.g: admin_method_1
                    value = value(row)
            if value is None:
                value = ''
107
            values.append(str(value).encode('utf-8'))
Pierros Papadeas's avatar
Pierros Papadeas committed
108
109
        writer.writerow(values)
    return response
110
111


112
def export_station_status(self, request, queryset):
113
    """Exports status of selected stations in csv format"""
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    meta = self.model._meta
    field_names = ["id", "status"]

    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
    writer = csv.writer(response)

    writer.writerow(field_names)
    for obj in queryset:
        writer.writerow([getattr(obj, field) for field in field_names])

    return response


128
def community_get_discussion_details(
129
    observation_id, satellite_name, norad_cat_id, observation_url
130
):
131
132
133
134
    """
    Return the details of a discussion of the observation (if existent) in the
    satnogs community (discourse)
    """
135
136

    discussion_url = ('https://community.libre.space/new-topic?title=Observation {0}: {1}'
137
                      ' ({2})&body=Regarding [Observation {0}]({3}) ...'
138
                      '&category=observations') \
139
        .format(observation_id, satellite_name, norad_cat_id, observation_url)
140
141
142
143
144

    discussion_slug = 'https://community.libre.space/t/observation-{0}-{1}-{2}' \
        .format(observation_id, slugify(satellite_name),
                norad_cat_id)

145
    try:
146
147
148
        response = requests.get(
            '{}.json'.format(discussion_slug), timeout=settings.COMMUNITY_TIMEOUT
        )
149
150
151
152
153
        response.raise_for_status()
        has_comments = (response.status_code == 200)
    except RequestException:
        # Community is unreachable
        has_comments = False
154
155

    return {'url': discussion_url, 'slug': discussion_slug, 'has_comments': has_comments}
156
157
158
159
160
161
162
163
164
165
166


def sync_demoddata_to_db(frame):
    """
    Task to send a frame from SatNOGS Network to SatNOGS DB

    Raises requests.exceptions.RequestException if sync fails."""
    obs = frame.observation
    sat = obs.satellite
    ground_station = obs.ground_station

167
168
    try:
        # need to abstract the timestamp from the filename. hacky..
169
170
171
172
        if frame.payload_demod:
            file_datetime = frame.payload_demod.name.split('/')[-1].split('_')[2]
        else:
            file_datetime = frame.demodulated_data.name.split('/')[-1].split('_')[2]
173
174
175
176
        frame_datetime = datetime.strptime(file_datetime, '%Y-%m-%dT%H-%M-%S')
        submit_datetime = datetime.strftime(frame_datetime, '%Y-%m-%dT%H:%M:%S.000Z')
    except ValueError:
        return
177
178
179
180
181
182
183

    # SiDS parameters
    params = {
        'noradID': sat.norad_cat_id,
        'source': ground_station.name,
        'timestamp': submit_datetime,
        'locator': 'longLat',
184
185
        'longitude': obs.station_lng,
        'latitude': obs.station_lat,
186
        'frame': frame.display_payload_hex().replace(' ', ''),
187
        'satnogs_network': 'True',  # NOT a part of SiDS
188
189
        'observation_id': obs.id,  # NOT a part of SiDS
        'station_id': obs.ground_station.id  # NOT a part of SiDS
190
191
192
193
194
195
196
197
    }

    telemetry_url = "{}telemetry/".format(settings.DB_API_ENDPOINT)

    response = requests.post(telemetry_url, data=params, timeout=settings.DB_API_TIMEOUT)
    response.raise_for_status()

    frame.copied_to_db = True
198
    frame.save(update_fields=['copied_to_db'])