Merge pull request #300 from faulteh/delete-obs-button
[Fixes #296] Delete observation button for admin when no datamerge-requests/308/head
commit
50fdc96d4b
|
@ -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']
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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/')
|
||||
|
|
|
@ -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" }}
|
||||
|
|
Loading…
Reference in New Issue