2017-02-09 05:33:44 -07:00
|
|
|
import os
|
2016-01-26 00:55:55 -07:00
|
|
|
from datetime import datetime, timedelta
|
2017-08-27 05:36:56 -06:00
|
|
|
from PIL import Image
|
2015-05-09 03:50:07 -06:00
|
|
|
from shortuuidfield import ShortUUIDField
|
2015-05-06 02:53:48 -06:00
|
|
|
|
2017-08-22 13:07:23 -06:00
|
|
|
from django.conf import settings
|
2014-09-01 14:21:53 -06:00
|
|
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
2017-08-22 13:07:23 -06:00
|
|
|
from django.dispatch import receiver
|
2014-09-01 14:21:53 -06:00
|
|
|
from django.db import models
|
2017-09-11 04:33:49 -06:00
|
|
|
from django.urls import reverse
|
2015-05-06 04:06:19 -06:00
|
|
|
from django.utils.html import format_html
|
2017-08-22 13:07:23 -06:00
|
|
|
from django.utils.timezone import now
|
2014-09-01 14:21:53 -06:00
|
|
|
|
2014-12-19 06:06:58 -07:00
|
|
|
from network.users.models import User
|
2015-09-09 02:12:36 -06:00
|
|
|
from network.base.helpers import get_apikey
|
2014-09-01 14:21:53 -06:00
|
|
|
|
|
|
|
|
2015-09-09 02:12:36 -06:00
|
|
|
RIG_TYPES = ['Radio', 'SDR']
|
2014-09-01 14:21:53 -06:00
|
|
|
ANTENNA_BANDS = ['HF', 'VHF', 'UHF', 'L', 'S', 'C', 'X', 'KU']
|
|
|
|
ANTENNA_TYPES = (
|
|
|
|
('dipole', 'Dipole'),
|
|
|
|
('yagi', 'Yagi'),
|
|
|
|
('helical', 'Helical'),
|
|
|
|
('parabolic', 'Parabolic'),
|
2015-10-11 08:06:44 -06:00
|
|
|
('vertical', 'Verical'),
|
2017-05-19 13:44:22 -06:00
|
|
|
('turnstile', 'Turnstile'),
|
|
|
|
('quadrafilar', 'Quadrafilar'),
|
|
|
|
('eggbeater', 'Eggbeater'),
|
|
|
|
('lindenblad', 'Lindenblad'),
|
2014-09-01 14:21:53 -06:00
|
|
|
)
|
Initial data vetting/verification system
Model change (with migration 0006) adds 3 fields to Data:
vetted_status (charfield with options for data status, default "unknown")
vetted_user (who vetted the data)
vetted_datetime (when it was vetted)
In addition, various boolean functions are added for the Data model
to check statuses. More functions are added to the Observation model
to check status of verification within an observation as well, assuming
multiple data entries in an Observation. With these, I also changed
"has_data" to "has_submitted_data" to be more specific alongside the
others.
For UX, we add a green check sign or red removal sign to the data
header in Observation view (along with green/red datetime in the footer)
if a data is verified good or bad, respectively. If there is an unknown
status, the data header is given a thumbs-up and thumbs-down button to
verify the data good or bad. These icons are only offered to is_staff,
the observation requestor, and any station owner in the observation.
These buttons trigger new URLs/functions in view:
data_verify(id)
data_mark_bad(id)
Returning the user back to the originating Observation page.
In the observation lists I changed the coloring of the ID button to be:
Future: light blue (same)
No uploaded data and/or all vetted bad data: red
Some or all unvetted data with no verified good data: orange
Some or all verified good data: green
These changes are reflected in the observations.html, home.html, and
user_detail.html templates.
solves satnogs/satnogs-network#171
2016-03-25 13:52:45 -06:00
|
|
|
OBSERVATION_STATUSES = (
|
|
|
|
('unknown', 'Unknown'),
|
|
|
|
('verified', 'Verified'),
|
|
|
|
('data_not_verified', 'Has Data, Not Verified'),
|
|
|
|
('no_data', 'No Data'),
|
|
|
|
)
|
2017-08-12 10:03:51 -06:00
|
|
|
SATELLITE_STATUS = ['alive', 'dead', 're-entered']
|
2015-08-14 07:55:43 -06:00
|
|
|
|
|
|
|
|
2015-09-09 02:12:36 -06:00
|
|
|
class Rig(models.Model):
|
2017-09-11 04:33:49 -06:00
|
|
|
"""Model for Rig types."""
|
2015-09-09 02:12:36 -06:00
|
|
|
name = models.CharField(choices=zip(RIG_TYPES, RIG_TYPES), max_length=10)
|
|
|
|
rictld_number = models.PositiveIntegerField(blank=True, null=True)
|
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
return '{0}: {1}'.format(self.name, self.rictld_number)
|
|
|
|
|
|
|
|
|
2015-08-14 07:55:43 -06:00
|
|
|
class Mode(models.Model):
|
|
|
|
name = models.CharField(max_length=10, unique=True)
|
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
return self.name
|
2014-09-01 14:21:53 -06:00
|
|
|
|
|
|
|
|
|
|
|
class Antenna(models.Model):
|
|
|
|
"""Model for antennas tracked with SatNOGS."""
|
2017-03-20 09:36:54 -06:00
|
|
|
frequency = models.PositiveIntegerField()
|
|
|
|
frequency_max = models.PositiveIntegerField()
|
2014-09-01 14:21:53 -06:00
|
|
|
band = models.CharField(choices=zip(ANTENNA_BANDS, ANTENNA_BANDS),
|
|
|
|
max_length=5)
|
|
|
|
antenna_type = models.CharField(choices=ANTENNA_TYPES, max_length=15)
|
|
|
|
|
2014-12-13 11:21:05 -07:00
|
|
|
def __unicode__(self):
|
2017-03-19 06:52:49 -06:00
|
|
|
return '{0} - {1} - {2} - {3}'.format(self.band, self.antenna_type,
|
|
|
|
self.frequency,
|
|
|
|
self.frequency_max)
|
2014-12-13 11:21:05 -07:00
|
|
|
|
2014-09-01 14:21:53 -06:00
|
|
|
|
|
|
|
class Station(models.Model):
|
|
|
|
"""Model for SatNOGS ground stations."""
|
2017-09-11 04:33:49 -06:00
|
|
|
owner = models.ForeignKey(User, related_name="ground_stations",
|
|
|
|
on_delete=models.SET_NULL, null=True, blank=True)
|
2014-09-01 14:21:53 -06:00
|
|
|
name = models.CharField(max_length=45)
|
2015-02-05 17:35:27 -07:00
|
|
|
image = models.ImageField(upload_to='ground_stations', blank=True)
|
2014-12-03 10:58:23 -07:00
|
|
|
alt = models.PositiveIntegerField(help_text='In meters above ground')
|
2014-09-01 14:21:53 -06:00
|
|
|
lat = models.FloatField(validators=[MaxValueValidator(90),
|
|
|
|
MinValueValidator(-90)])
|
|
|
|
lng = models.FloatField(validators=[MaxValueValidator(180),
|
|
|
|
MinValueValidator(-180)])
|
2015-04-15 06:46:33 -06:00
|
|
|
qthlocator = models.CharField(max_length=255, blank=True)
|
|
|
|
location = models.CharField(max_length=255, blank=True)
|
|
|
|
antenna = models.ManyToManyField(Antenna, blank=True,
|
2016-05-06 03:00:44 -06:00
|
|
|
help_text=('If you want to add a new Antenna contact '
|
|
|
|
'<a href="https://community.satnogs.org/" '
|
|
|
|
'target="_blank">SatNOGS Team</a>'))
|
2014-09-17 12:30:30 -06:00
|
|
|
featured_date = models.DateField(null=True, blank=True)
|
2014-12-01 13:20:38 -07:00
|
|
|
created = models.DateTimeField(auto_now_add=True)
|
2017-08-13 07:01:18 -06:00
|
|
|
active = models.BooleanField(default=True)
|
2015-05-06 02:53:48 -06:00
|
|
|
last_seen = models.DateTimeField(null=True, blank=True)
|
2016-03-18 19:59:59 -06:00
|
|
|
horizon = models.PositiveIntegerField(help_text='In degrees above 0', default=10)
|
2015-09-09 02:12:36 -06:00
|
|
|
uuid = models.CharField(db_index=True, max_length=100, blank=True)
|
2017-09-11 04:33:49 -06:00
|
|
|
rig = models.ForeignKey(Rig, related_name='ground_stations',
|
|
|
|
on_delete=models.SET_NULL, null=True, blank=True)
|
2017-08-12 19:39:06 -06:00
|
|
|
description = models.TextField(max_length=500, blank=True)
|
2014-09-17 12:30:30 -06:00
|
|
|
|
2015-05-06 04:06:19 -06:00
|
|
|
class Meta:
|
|
|
|
ordering = ['-active', '-last_seen']
|
|
|
|
|
2015-02-05 17:35:27 -07:00
|
|
|
def get_image(self):
|
|
|
|
if self.image and hasattr(self.image, 'url'):
|
|
|
|
return self.image.url
|
|
|
|
else:
|
|
|
|
return settings.STATION_DEFAULT_IMAGE
|
|
|
|
|
2015-05-06 02:53:48 -06:00
|
|
|
@property
|
|
|
|
def online(self):
|
|
|
|
try:
|
2015-05-15 02:45:18 -06:00
|
|
|
heartbeat = self.last_seen + timedelta(minutes=int(settings.STATION_HEARTBEAT_TIME))
|
2015-05-06 02:53:48 -06:00
|
|
|
return self.active and heartbeat > now()
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
|
2015-05-06 04:06:19 -06:00
|
|
|
def state(self):
|
|
|
|
if self.online:
|
|
|
|
return format_html('<span style="color:green">Online</span>')
|
|
|
|
else:
|
|
|
|
return format_html('<span style="color:red">Offline</span>')
|
|
|
|
|
2015-08-11 03:19:10 -06:00
|
|
|
@property
|
|
|
|
def success_rate(self):
|
2017-09-11 04:33:49 -06:00
|
|
|
observations = self.observations.all().count()
|
|
|
|
success = self.observations.exclude(payload='').count()
|
2015-08-24 06:02:15 -06:00
|
|
|
if observations:
|
|
|
|
return int(100 * (float(success) / float(observations)))
|
|
|
|
else:
|
|
|
|
return False
|
2015-08-11 03:19:10 -06:00
|
|
|
|
2015-09-09 02:12:36 -06:00
|
|
|
@property
|
|
|
|
def apikey(self):
|
|
|
|
return get_apikey(user=self.owner)
|
|
|
|
|
2014-10-06 06:14:55 -06:00
|
|
|
def __unicode__(self):
|
|
|
|
return "%d - %s" % (self.pk, self.name)
|
|
|
|
|
2014-09-01 14:21:53 -06:00
|
|
|
|
|
|
|
class Satellite(models.Model):
|
|
|
|
"""Model for SatNOGS satellites."""
|
|
|
|
norad_cat_id = models.PositiveIntegerField()
|
|
|
|
name = models.CharField(max_length=45)
|
2015-08-14 07:55:43 -06:00
|
|
|
names = models.TextField(blank=True)
|
2016-05-06 02:27:24 -06:00
|
|
|
image = models.CharField(max_length=100, blank=True, null=True)
|
2016-05-07 10:19:41 -06:00
|
|
|
manual_tle = models.BooleanField(default=False)
|
2017-08-12 10:03:51 -06:00
|
|
|
status = models.CharField(choices=zip(SATELLITE_STATUS, SATELLITE_STATUS),
|
|
|
|
max_length=10, default='alive')
|
2014-09-14 08:42:52 -06:00
|
|
|
|
2015-05-06 04:06:19 -06:00
|
|
|
class Meta:
|
|
|
|
ordering = ['norad_cat_id']
|
|
|
|
|
2015-08-14 07:55:43 -06:00
|
|
|
def get_image(self):
|
2016-04-08 04:14:36 -06:00
|
|
|
if self.image:
|
|
|
|
return self.image
|
2015-08-14 07:55:43 -06:00
|
|
|
else:
|
|
|
|
return settings.SATELLITE_DEFAULT_IMAGE
|
|
|
|
|
2016-01-23 02:40:56 -07:00
|
|
|
@property
|
|
|
|
def latest_tle(self):
|
2016-01-25 08:04:49 -07:00
|
|
|
try:
|
|
|
|
latest_tle = Tle.objects.filter(satellite=self).latest('updated')
|
|
|
|
return latest_tle
|
|
|
|
except Tle.DoesNotExist:
|
|
|
|
return False
|
2016-01-23 02:40:56 -07:00
|
|
|
|
|
|
|
@property
|
2016-01-26 00:55:55 -07:00
|
|
|
def tle_no(self):
|
|
|
|
try:
|
|
|
|
line = self.latest_tle.tle1
|
|
|
|
return line[65:68]
|
|
|
|
except:
|
|
|
|
return False
|
2016-01-23 02:40:56 -07:00
|
|
|
|
|
|
|
@property
|
2016-01-26 00:55:55 -07:00
|
|
|
def tle_epoch(self):
|
|
|
|
try:
|
|
|
|
line = self.latest_tle.tle1
|
|
|
|
yd, s = line[18:32].split('.')
|
|
|
|
epoch = (datetime.strptime(yd, "%y%j") +
|
|
|
|
timedelta(seconds=float("." + s) * 24 * 60 * 60))
|
|
|
|
return epoch
|
|
|
|
except:
|
|
|
|
return False
|
2016-01-23 02:40:56 -07:00
|
|
|
|
2017-06-06 11:25:54 -06:00
|
|
|
@property
|
|
|
|
def data_count(self):
|
2017-09-11 04:33:49 -06:00
|
|
|
return Observation.objects.filter(satellite=self).count()
|
2017-06-06 11:25:54 -06:00
|
|
|
|
|
|
|
@property
|
|
|
|
def verified_count(self):
|
2017-09-11 04:33:49 -06:00
|
|
|
data = Observation.objects.filter(satellite=self)
|
2017-06-06 11:25:54 -06:00
|
|
|
return data.filter(vetted_status='verified').count()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def empty_count(self):
|
2017-09-11 04:33:49 -06:00
|
|
|
data = Observation.objects.filter(satellite=self)
|
2017-06-06 11:25:54 -06:00
|
|
|
return data.filter(vetted_status='no_data').count()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unknown_count(self):
|
2017-09-11 04:33:49 -06:00
|
|
|
data = Observation.objects.filter(satellite=self)
|
2017-06-06 11:25:54 -06:00
|
|
|
return data.filter(vetted_status='unknown').count()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def success_rate(self):
|
|
|
|
try:
|
|
|
|
return int(100 * (float(self.verified_count) / float(self.data_count)))
|
|
|
|
except:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
@property
|
|
|
|
def empty_rate(self):
|
|
|
|
try:
|
|
|
|
return int(100 * (float(self.empty_count) / float(self.data_count)))
|
|
|
|
except:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unknown_rate(self):
|
|
|
|
try:
|
|
|
|
return int(100 * (float(self.unknown_count) / float(self.data_count)))
|
|
|
|
except:
|
|
|
|
return 0
|
|
|
|
|
2014-09-17 10:53:25 -06:00
|
|
|
def __unicode__(self):
|
|
|
|
return self.name
|
|
|
|
|
2014-09-14 08:42:52 -06:00
|
|
|
|
2016-01-22 10:48:07 -07:00
|
|
|
class Tle(models.Model):
|
|
|
|
tle0 = models.CharField(max_length=100, blank=True)
|
|
|
|
tle1 = models.CharField(max_length=200, blank=True)
|
|
|
|
tle2 = models.CharField(max_length=200, blank=True)
|
|
|
|
updated = models.DateTimeField(auto_now=True, blank=True)
|
2017-09-11 04:33:49 -06:00
|
|
|
satellite = models.ForeignKey(Satellite, related_name='tles',
|
|
|
|
on_delete=models.CASCADE, null=True, blank=True)
|
2016-01-22 10:48:07 -07:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
ordering = ['tle0']
|
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
return self.tle0
|
|
|
|
|
|
|
|
|
2015-07-23 09:18:01 -06:00
|
|
|
class Transmitter(models.Model):
|
2014-09-14 08:42:52 -06:00
|
|
|
"""Model for antennas transponders."""
|
2015-05-09 03:50:07 -06:00
|
|
|
uuid = ShortUUIDField(db_index=True)
|
2014-09-14 08:42:52 -06:00
|
|
|
description = models.TextField()
|
2014-10-16 06:07:07 -06:00
|
|
|
alive = models.BooleanField(default=True)
|
2014-10-27 09:47:32 -06:00
|
|
|
uplink_low = models.PositiveIntegerField(blank=True, null=True)
|
|
|
|
uplink_high = models.PositiveIntegerField(blank=True, null=True)
|
|
|
|
downlink_low = models.PositiveIntegerField(blank=True, null=True)
|
|
|
|
downlink_high = models.PositiveIntegerField(blank=True, null=True)
|
2015-08-14 07:55:43 -06:00
|
|
|
mode = models.ForeignKey(Mode, related_name='transmitters', blank=True,
|
|
|
|
null=True, on_delete=models.SET_NULL)
|
2014-10-15 09:02:47 -06:00
|
|
|
invert = models.BooleanField(default=False)
|
2015-05-06 02:53:48 -06:00
|
|
|
baud = models.FloatField(validators=[MinValueValidator(0)], null=True, blank=True)
|
2017-09-11 04:33:49 -06:00
|
|
|
satellite = models.ForeignKey(Satellite, related_name='transmitters',
|
|
|
|
on_delete=models.CASCADE, null=True, blank=True)
|
2014-09-01 14:21:53 -06:00
|
|
|
|
2014-10-06 06:14:55 -06:00
|
|
|
def __unicode__(self):
|
|
|
|
return self.description
|
|
|
|
|
2014-09-01 14:21:53 -06:00
|
|
|
|
|
|
|
class Observation(models.Model):
|
|
|
|
"""Model for SatNOGS observations."""
|
2017-09-11 04:33:49 -06:00
|
|
|
satellite = models.ForeignKey(Satellite, related_name='observations',
|
|
|
|
on_delete=models.SET_NULL, null=True, blank=True)
|
|
|
|
transmitter = models.ForeignKey(Transmitter, related_name='observations',
|
|
|
|
on_delete=models.SET_NULL, null=True, blank=True)
|
|
|
|
tle = models.ForeignKey(Tle, related_name='observations',
|
|
|
|
on_delete=models.SET_NULL, null=True, blank=True)
|
|
|
|
author = models.ForeignKey(User, related_name='observations',
|
|
|
|
on_delete=models.SET_NULL, null=True, blank=True)
|
2014-09-01 14:21:53 -06:00
|
|
|
start = models.DateTimeField()
|
|
|
|
end = models.DateTimeField()
|
2017-09-11 04:33:49 -06:00
|
|
|
ground_station = models.ForeignKey(Station, related_name='observations',
|
|
|
|
on_delete=models.SET_NULL, null=True, blank=True)
|
|
|
|
payload = models.FileField(upload_to='data_payloads', blank=True, null=True)
|
|
|
|
waterfall = models.ImageField(upload_to='data_waterfalls', blank=True, null=True)
|
|
|
|
vetted_datetime = models.DateTimeField(null=True, blank=True)
|
|
|
|
vetted_user = models.ForeignKey(User, related_name='observations_vetted',
|
|
|
|
on_delete=models.SET_NULL, null=True, blank=True)
|
|
|
|
vetted_status = models.CharField(choices=OBSERVATION_STATUSES,
|
|
|
|
max_length=20, default='unknown')
|
|
|
|
rise_azimuth = models.FloatField(blank=True, null=True)
|
|
|
|
max_altitude = models.FloatField(blank=True, null=True)
|
|
|
|
set_azimuth = models.FloatField(blank=True, null=True)
|
2015-05-06 04:06:19 -06:00
|
|
|
|
2014-09-08 11:36:12 -06:00
|
|
|
@property
|
|
|
|
def is_past(self):
|
|
|
|
return self.end < now()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_future(self):
|
|
|
|
return self.end > now()
|
|
|
|
|
2015-08-14 12:58:54 -06:00
|
|
|
@property
|
2017-02-09 05:33:44 -07:00
|
|
|
def is_deletable_before_start(self):
|
2015-08-14 12:58:54 -06:00
|
|
|
deletion = self.start - timedelta(minutes=int(settings.OBSERVATION_MAX_DELETION_RANGE))
|
|
|
|
return deletion > now()
|
|
|
|
|
2017-02-09 05:33:44 -07:00
|
|
|
@property
|
|
|
|
def is_deletable_after_end(self):
|
|
|
|
deletion = self.end + timedelta(minutes=int(settings.OBSERVATION_MIN_DELETION_RANGE))
|
|
|
|
return deletion < now()
|
|
|
|
|
Initial data vetting/verification system
Model change (with migration 0006) adds 3 fields to Data:
vetted_status (charfield with options for data status, default "unknown")
vetted_user (who vetted the data)
vetted_datetime (when it was vetted)
In addition, various boolean functions are added for the Data model
to check statuses. More functions are added to the Observation model
to check status of verification within an observation as well, assuming
multiple data entries in an Observation. With these, I also changed
"has_data" to "has_submitted_data" to be more specific alongside the
others.
For UX, we add a green check sign or red removal sign to the data
header in Observation view (along with green/red datetime in the footer)
if a data is verified good or bad, respectively. If there is an unknown
status, the data header is given a thumbs-up and thumbs-down button to
verify the data good or bad. These icons are only offered to is_staff,
the observation requestor, and any station owner in the observation.
These buttons trigger new URLs/functions in view:
data_verify(id)
data_mark_bad(id)
Returning the user back to the originating Observation page.
In the observation lists I changed the coloring of the ID button to be:
Future: light blue (same)
No uploaded data and/or all vetted bad data: red
Some or all unvetted data with no verified good data: orange
Some or all verified good data: green
These changes are reflected in the observations.html, home.html, and
user_detail.html templates.
solves satnogs/satnogs-network#171
2016-03-25 13:52:45 -06:00
|
|
|
# this payload has been vetted good/bad by someone
|
|
|
|
@property
|
|
|
|
def is_vetted(self):
|
|
|
|
return not self.vetted_status == 'unknown'
|
|
|
|
|
|
|
|
# this payload has been vetted as good by someone
|
|
|
|
@property
|
|
|
|
def is_verified(self):
|
|
|
|
return self.vetted_status == 'verified'
|
|
|
|
|
|
|
|
# this payload has been vetted as bad by someone
|
|
|
|
@property
|
|
|
|
def is_no_data(self):
|
|
|
|
return self.vetted_status == 'no_data'
|
|
|
|
|
2017-02-09 05:33:44 -07:00
|
|
|
@property
|
|
|
|
def payload_exists(self):
|
|
|
|
""" Run some checks on the payload for existence of data """
|
|
|
|
if self.payload is None:
|
|
|
|
return False
|
|
|
|
if not os.path.isfile(os.path.join(settings.MEDIA_ROOT, self.payload.name)):
|
|
|
|
return False
|
|
|
|
if self.payload.size == 0:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2015-05-06 04:06:19 -06:00
|
|
|
class Meta:
|
|
|
|
ordering = ['-start', '-end']
|
2016-04-25 08:54:06 -06:00
|
|
|
|
2017-09-11 04:33:49 -06:00
|
|
|
def __unicode__(self):
|
|
|
|
return str(self.id)
|
|
|
|
|
|
|
|
def get_absolute_url(self):
|
|
|
|
return reverse('base:observation_view', kwargs={'id': self.id})
|
|
|
|
|
2016-04-25 08:54:06 -06:00
|
|
|
|
2017-09-11 04:33:49 -06:00
|
|
|
@receiver(models.signals.post_delete, sender=Observation)
|
|
|
|
def observation_remove_files(sender, instance, **kwargs):
|
2017-08-22 13:07:23 -06:00
|
|
|
if instance.payload:
|
|
|
|
if os.path.isfile(instance.payload.path):
|
|
|
|
os.remove(instance.payload.path)
|
|
|
|
if instance.waterfall:
|
|
|
|
if os.path.isfile(instance.waterfall.path):
|
|
|
|
os.remove(instance.waterfall.path)
|
|
|
|
|
|
|
|
|
2016-04-25 08:54:06 -06:00
|
|
|
class DemodData(models.Model):
|
2017-09-11 04:33:49 -06:00
|
|
|
observation = models.ForeignKey(Observation, related_name='demoddata',
|
|
|
|
on_delete=models.CASCADE, blank=True, null=True)
|
2016-04-25 08:54:06 -06:00
|
|
|
payload_demod = models.FileField(upload_to='data_payloads', blank=True, null=True)
|
2017-02-10 11:10:19 -07:00
|
|
|
|
2017-08-27 05:36:56 -06:00
|
|
|
def is_image(self):
|
|
|
|
with open(self.payload_demod.path) as fp:
|
|
|
|
try:
|
|
|
|
Image.open(fp)
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
2017-02-10 11:10:19 -07:00
|
|
|
def display_payload(self):
|
|
|
|
with open(self.payload_demod.path) as fp:
|
2017-09-26 11:16:41 -06:00
|
|
|
return unicode(fp.read(), errors='replace')
|
2017-08-22 13:07:23 -06:00
|
|
|
|
|
|
|
|
|
|
|
@receiver(models.signals.post_delete, sender=DemodData)
|
|
|
|
def demoddata_remove_files(sender, instance, **kwargs):
|
|
|
|
if instance.payload_demod:
|
|
|
|
if os.path.isfile(instance.payload_demod.path):
|
|
|
|
os.remove(instance.payload_demod.path)
|