1
0
Fork 0

Merge pull request #300 from faulteh/delete-obs-button

[Fixes #296] Delete observation button for admin when no data
merge-requests/308/head
Nikos Roussos 2017-02-11 16:03:33 +02:00 committed by GitHub
commit 50fdc96d4b
5 changed files with 103 additions and 14 deletions

View File

@ -1,3 +1,4 @@
import os
from datetime import datetime, timedelta
from shortuuidfield import ShortUUIDField
@ -220,10 +221,15 @@ class Observation(models.Model):
return self.end > now()
@property
def is_deletable(self):
def is_deletable_before_start(self):
deletion = self.start - timedelta(minutes=int(settings.OBSERVATION_MAX_DELETION_RANGE))
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
@property
def has_submitted_data(self):
@ -281,6 +287,17 @@ class Data(models.Model):
def is_no_data(self):
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:
ordering = ['-start', '-end']

View File

@ -10,6 +10,7 @@ from django.utils.timezone import now
from django.db import transaction
from django.test import TestCase, Client
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,
Rig, Mode, Antenna, Satellite, Tle, Station, Transmitter,
@ -329,8 +330,12 @@ class ObservationViewTest(TestCase):
observation = None
satellites = []
transmitters = []
user = None
def setUp(self):
self.user = UserFactory()
self.user.user_permissions.add(
Permission.objects.get(codename='delete_observation'))
for x in xrange(1, 10):
self.satellites.append(SatelliteFactory())
for x in xrange(1, 10):
@ -341,6 +346,12 @@ class ObservationViewTest(TestCase):
response = self.client.get('/observations/%d/' % self.observation.id)
self.assertContains(response, self.observation.author.username)
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)
@ -366,9 +377,25 @@ class ObservationDeleteTest(TestCase):
# observations in progress cannot be deleted
self.observation.start = datetime.now() + timedelta(
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()
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)
self.assertRedirects(response, '/observations/')
response = self.client.get('/observations/')
@ -537,6 +564,8 @@ class ObservationModelTest(TestCase):
observation = None
satellites = []
transmitters = []
user = None
admin = None
def setUp(self):
for x in xrange(1, 10):
@ -553,6 +582,22 @@ class ObservationModelTest(TestCase):
def test_is_passed(self):
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)
class DataModelTest(TestCase):
@ -560,6 +605,7 @@ class DataModelTest(TestCase):
Test various properties of the Observation Model
"""
data = None
data2 = None
satellites = []
transmitters = []
@ -572,9 +618,13 @@ class DataModelTest(TestCase):
self.data.end = now()
self.data.vetted_status = 'no_data'
self.data.save()
self.data2 = DataFactory(payload=None)
def test_is_no_data(self):
self.assertTrue(self.data.is_no_data)
def test_is_passed(self):
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
resultset = []
for ob in observations:
if ob.has_unvetted_data > 0:
print ob.has_unvetted_data
if bad and ob.has_no_data:
resultset.append(ob)
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):
"""View for single observation page."""
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
# staff, observation requestors, and station owners
is_vetting_user = False
if request.user.is_authenticated():
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:
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':
discuss_slug = 'https://community.satnogs.org/t/observation-{0}-{1}-{2}' \
.format(observation.id, slugify(observation.satellite.name),
@ -413,12 +426,14 @@ def observation_view(request, id):
has_comments = False
return render(request, 'base/observation_view.html',
{'observation': observation, 'data': data, 'has_comments': has_comments,
'discuss_url': discuss_url, 'discuss_slug': discuss_slug,
'is_vetting_user': is_vetting_user})
{'observation': observation, 'dataset': dataset,
'has_comments': has_comments, 'discuss_url': discuss_url,
'discuss_slug': discuss_slug, 'is_vetting_user': is_vetting_user,
'is_deletable': is_deletable})
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
@ -426,7 +441,14 @@ def observation_delete(request, id):
"""View for deleting observation."""
me = request.user
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()
messages.success(request, 'Observation deleted successfully.')
else:

View File

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

View File

@ -20,7 +20,7 @@
</div>
<div class="col-md-6 text-right">
<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>
{% endif %}
</h2>
@ -101,7 +101,7 @@
</div>
</div>
{% for data in data %}
{% for data in dataset %}
<div class="panel panel-default observation-data" id="{{ data.id }}"
data-start="{{ data.start|date:"U" }}"
data-end="{{ data.end|date:"U" }}"
@ -231,7 +231,6 @@
</button>
</a>
{% endif %}
<span class="pull-right hidden-xs">
<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" }}