1
0
Fork 0

[Fixes #296] Delete observation button for admin when no data

merge-requests/300/head
Scott Bragg 2017-02-09 23:33:44 +11:00
parent 560411c3d5
commit b62b5dd75d
5 changed files with 103 additions and 14 deletions

View File

@ -1,3 +1,4 @@
import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from shortuuidfield import ShortUUIDField from shortuuidfield import ShortUUIDField
@ -220,10 +221,15 @@ class Observation(models.Model):
return self.end > now() return self.end > now()
@property @property
def is_deletable(self): def is_deletable_before_start(self):
deletion = self.start - timedelta(minutes=int(settings.OBSERVATION_MAX_DELETION_RANGE)) deletion = self.start - timedelta(minutes=int(settings.OBSERVATION_MAX_DELETION_RANGE))
return deletion > now() return deletion > now()
@property
def is_deletable_after_end(self):
deletion = self.end + timedelta(minutes=int(settings.OBSERVATION_MIN_DELETION_RANGE))
return deletion < now()
# observation has at least 1 payload submitted, no verification taken into account # observation has at least 1 payload submitted, no verification taken into account
@property @property
def has_submitted_data(self): def has_submitted_data(self):
@ -281,6 +287,17 @@ class Data(models.Model):
def is_no_data(self): def is_no_data(self):
return self.vetted_status == 'no_data' return self.vetted_status == 'no_data'
@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
class Meta: class Meta:
ordering = ['-start', '-end'] ordering = ['-start', '-end']

View File

@ -10,6 +10,7 @@ from django.utils.timezone import now
from django.db import transaction from django.db import transaction
from django.test import TestCase, Client from django.test import TestCase, Client
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Permission
from network.base.models import (ANTENNA_BANDS, ANTENNA_TYPES, RIG_TYPES, OBSERVATION_STATUSES, from network.base.models import (ANTENNA_BANDS, ANTENNA_TYPES, RIG_TYPES, OBSERVATION_STATUSES,
Rig, Mode, Antenna, Satellite, Tle, Station, Transmitter, Rig, Mode, Antenna, Satellite, Tle, Station, Transmitter,
@ -323,8 +324,12 @@ class ObservationViewTest(TestCase):
observation = None observation = None
satellites = [] satellites = []
transmitters = [] transmitters = []
user = None
def setUp(self): def setUp(self):
self.user = UserFactory()
self.user.user_permissions.add(
Permission.objects.get(codename='delete_observation'))
for x in xrange(1, 10): for x in xrange(1, 10):
self.satellites.append(SatelliteFactory()) self.satellites.append(SatelliteFactory())
for x in xrange(1, 10): for x in xrange(1, 10):
@ -335,6 +340,12 @@ class ObservationViewTest(TestCase):
response = self.client.get('/observations/%d/' % self.observation.id) response = self.client.get('/observations/%d/' % self.observation.id)
self.assertContains(response, self.observation.author.username) self.assertContains(response, self.observation.author.username)
self.assertContains(response, self.observation.transmitter.mode.name) self.assertContains(response, self.observation.transmitter.mode.name)
self.assertNotContains(response, 'Delete Observation')
def test_observation_staff(self):
self.client.force_login(self.user)
response = self.client.get('/observations/%d/' % self.observation.id)
self.assertContains(response, 'Delete Observation')
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
@ -360,9 +371,25 @@ class ObservationDeleteTest(TestCase):
# observations in progress cannot be deleted # observations in progress cannot be deleted
self.observation.start = datetime.now() + timedelta( self.observation.start = datetime.now() + timedelta(
minutes=(2 * int(settings.OBSERVATION_MAX_DELETION_RANGE))) minutes=(2 * int(settings.OBSERVATION_MAX_DELETION_RANGE)))
self.observation.end = datetime.now() - timedelta(
minutes=(2 * int(settings.OBSERVATION_MIN_DELETION_RANGE)))
self.observation.save() self.observation.save()
def test_observation_delete(self): def test_observation_delete_author(self):
"""Deletion OK when user is the author of the observation"""
response = self.client.get('/observations/%d/delete/' % self.observation.id)
self.assertRedirects(response, '/observations/')
response = self.client.get('/observations/')
with self.assertRaises(Observation.DoesNotExist):
_lookup = Observation.objects.get(pk=self.observation.id) # noqa:F841
def test_observation_delete_staff(self):
"""Deletion OK when user is staff and there is no data"""
self.user = UserFactory()
self.user.user_permissions.add(
Permission.objects.get(codename='delete_observation'))
self.user.save()
self.client.force_login(self.user)
response = self.client.get('/observations/%d/delete/' % self.observation.id) response = self.client.get('/observations/%d/delete/' % self.observation.id)
self.assertRedirects(response, '/observations/') self.assertRedirects(response, '/observations/')
response = self.client.get('/observations/') response = self.client.get('/observations/')
@ -531,6 +558,8 @@ class ObservationModelTest(TestCase):
observation = None observation = None
satellites = [] satellites = []
transmitters = [] transmitters = []
user = None
admin = None
def setUp(self): def setUp(self):
for x in xrange(1, 10): for x in xrange(1, 10):
@ -547,6 +576,22 @@ class ObservationModelTest(TestCase):
def test_is_passed(self): def test_is_passed(self):
self.assertTrue(self.observation.is_past) self.assertTrue(self.observation.is_past)
def test_is_deletable_before_start(self):
self.observation.start = now() - timedelta(minutes=2)
self.observation.save()
self.assertFalse(self.observation.is_deletable_before_start)
self.observation.start = now() + timedelta(minutes=100)
self.observation.save()
self.assertTrue(self.observation.is_deletable_before_start)
def test_is_deletable_after_end(self):
self.observation.end = now()
self.observation.save()
self.assertFalse(self.observation.is_deletable_after_end)
self.observation.end = now() - timedelta(minutes=200)
self.observation.save()
self.assertTrue(self.observation.is_deletable_after_end)
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
class DataModelTest(TestCase): class DataModelTest(TestCase):
@ -554,6 +599,7 @@ class DataModelTest(TestCase):
Test various properties of the Observation Model Test various properties of the Observation Model
""" """
data = None data = None
data2 = None
satellites = [] satellites = []
transmitters = [] transmitters = []
@ -566,9 +612,13 @@ class DataModelTest(TestCase):
self.data.end = now() self.data.end = now()
self.data.vetted_status = 'no_data' self.data.vetted_status = 'no_data'
self.data.save() self.data.save()
self.data2 = DataFactory(payload=None)
def test_is_no_data(self): def test_is_no_data(self):
self.assertTrue(self.data.is_no_data) self.assertTrue(self.data.is_no_data)
def test_is_passed(self): def test_is_passed(self):
self.assertTrue(self.data.is_past) self.assertTrue(self.data.is_past)
def test_payload_exists(self):
self.assertFalse(self.data.payload_exists)

View File

@ -175,8 +175,6 @@ class ObservationListView(ListView):
# so cannot use queryset filtering # so cannot use queryset filtering
resultset = [] resultset = []
for ob in observations: for ob in observations:
if ob.has_unvetted_data > 0:
print ob.has_unvetted_data
if bad and ob.has_no_data: if bad and ob.has_no_data:
resultset.append(ob) resultset.append(ob)
elif good and ob.has_verified_data: elif good and ob.has_verified_data:
@ -384,17 +382,32 @@ def prediction_windows(request, sat_id, start_date, end_date, station_id=None):
def observation_view(request, id): def observation_view(request, id):
"""View for single observation page.""" """View for single observation page."""
observation = get_object_or_404(Observation, id=id) observation = get_object_or_404(Observation, id=id)
data = Data.objects.filter(observation=observation) dataset = Data.objects.filter(observation=observation)
# not all users will be able to vet data within an observation, allow # not all users will be able to vet data within an observation, allow
# staff, observation requestors, and station owners # staff, observation requestors, and station owners
is_vetting_user = False is_vetting_user = False
if request.user.is_authenticated(): if request.user.is_authenticated():
if request.user == observation.author or \ if request.user == observation.author or \
data.filter(ground_station__in=Station.objects.filter(owner=request.user)).count or \ dataset.filter(
ground_station__in=Station.objects.filter(owner=request.user)).count or \
request.user.is_staff: request.user.is_staff:
is_vetting_user = True is_vetting_user = True
# Determine if there is no valid payload file in the observation dataset
if request.user.has_perm('base.delete_observation'):
data_payload_exists = False
for data in dataset:
if data.payload_exists:
data_payload_exists = True
# This context flag will determine if a delete button appears for the observation.
is_deletable = False
if observation.author == request.user and observation.is_deletable_before_start:
is_deletable = True
if request.user.has_perm('base.delete_observation') and not data_payload_exists and \
observation.is_deletable_after_end:
is_deletable = True
if settings.ENVIRONMENT == 'production': if settings.ENVIRONMENT == 'production':
discuss_slug = 'https://community.satnogs.org/t/observation-{0}-{1}-{2}' \ discuss_slug = 'https://community.satnogs.org/t/observation-{0}-{1}-{2}' \
.format(observation.id, slugify(observation.satellite.name), .format(observation.id, slugify(observation.satellite.name),
@ -413,12 +426,14 @@ def observation_view(request, id):
has_comments = False has_comments = False
return render(request, 'base/observation_view.html', return render(request, 'base/observation_view.html',
{'observation': observation, 'data': data, 'has_comments': has_comments, {'observation': observation, 'dataset': dataset,
'discuss_url': discuss_url, 'discuss_slug': discuss_slug, 'has_comments': has_comments, 'discuss_url': discuss_url,
'is_vetting_user': is_vetting_user}) 'discuss_slug': discuss_slug, 'is_vetting_user': is_vetting_user,
'is_deletable': is_deletable})
return render(request, 'base/observation_view.html', return render(request, 'base/observation_view.html',
{'observation': observation, 'data': data, 'is_vetting_user': is_vetting_user}) {'observation': observation, 'dataset': dataset,
'is_vetting_user': is_vetting_user, 'is_deletable': is_deletable})
@login_required @login_required
@ -426,7 +441,14 @@ def observation_delete(request, id):
"""View for deleting observation.""" """View for deleting observation."""
me = request.user me = request.user
observation = get_object_or_404(Observation, id=id) observation = get_object_or_404(Observation, id=id)
if observation.author == me and observation.is_deletable: # Having non-existent data is also grounds for deletion if user is staff
data_payload_exists = False
for data in observation.data_set.all():
if data.payload_exists:
data_payload_exists = True
if (observation.author == me and observation.is_deletable_before_start) or \
(request.user.has_perm('base.delete_observation') and
not data_payload_exists and observation.is_deletable_after_end):
observation.delete() observation.delete()
messages.success(request, 'Observation deleted successfully.') messages.success(request, 'Observation deleted successfully.')
else: else:

View File

@ -220,6 +220,7 @@ DATE_MAX_RANGE = '480'
# Station heartbeat in minutes # Station heartbeat in minutes
STATION_HEARTBEAT_TIME = getenv('STATION_HEARTBEAT_TIME', 60) STATION_HEARTBEAT_TIME = getenv('STATION_HEARTBEAT_TIME', 60)
OBSERVATION_MAX_DELETION_RANGE = getenv('OBSERVATION_MAX_DELETION_RANGE', 10) OBSERVATION_MAX_DELETION_RANGE = getenv('OBSERVATION_MAX_DELETION_RANGE', 10)
OBSERVATION_MIN_DELETION_RANGE = getenv('OBSERVATION_MIN_DELETION_RANGE', 60)
# DB API # DB API
DB_API_ENDPOINT = getenv('DB_API_ENDPOINT', 'https://db.satnogs.org/api/') DB_API_ENDPOINT = getenv('DB_API_ENDPOINT', 'https://db.satnogs.org/api/')

View File

@ -20,7 +20,7 @@
</div> </div>
<div class="col-md-6 text-right"> <div class="col-md-6 text-right">
<h2> <h2>
{% if observation.author == request.user and observation.is_deletable %} {% if is_deletable %}
<a href="{% url 'base:observation_delete' id=observation.id %}" id="obs-delete" class="btn btn-danger">Delete Observation</a> <a href="{% url 'base:observation_delete' id=observation.id %}" id="obs-delete" class="btn btn-danger">Delete Observation</a>
{% endif %} {% endif %}
</h2> </h2>
@ -101,7 +101,7 @@
</div> </div>
</div> </div>
{% for data in data %} {% for data in dataset %}
<div class="panel panel-default observation-data" id="{{ data.id }}" <div class="panel panel-default observation-data" id="{{ data.id }}"
data-start="{{ data.start|date:"U" }}" data-start="{{ data.start|date:"U" }}"
data-end="{{ data.end|date:"U" }}" data-end="{{ data.end|date:"U" }}"
@ -186,7 +186,6 @@
<span class="glyphicon glyphicon-picture"></span> Waterfall <span class="glyphicon glyphicon-picture"></span> Waterfall
</button> </button>
{% endif %} {% endif %}
<span class="pull-right hidden-xs"> <span class="pull-right hidden-xs">
<span class="label label-default">Timeframe</span> <span class="label label-default">Timeframe</span>
{{ data.start|date:"Y-m-d H:i:s" }} - {{ data.end|date:"Y-m-d H:i:s" }} {{ data.start|date:"Y-m-d H:i:s" }} - {{ data.end|date:"Y-m-d H:i:s" }}