1
0
Fork 0

Refactor vetted status

* Renamed verified to good
* Renamed no_data to bad
* Add failed status
* Remove unused data_not_verified
* Expose vetted status to API
* Simplify vetted status template markup
* Simplify vetted methods
* Utilize sass for vetted statuses
* Add option to undo vetting
merge-requests/451/head
Nikos Roussos 2017-12-24 21:17:37 +02:00
parent 334958e225
commit b1eebfe0ae
No known key found for this signature in database
GPG Key ID: BADFF1767BA7C8E1
23 changed files with 396 additions and 290 deletions

View File

@ -2,7 +2,7 @@ FROM centos:7
RUN yum makecache RUN yum makecache
RUN yum -y install epel-release RUN yum -y install epel-release
RUN yum -y install python python-pip python-devel git gcc libjpeg-turbo-devel \ RUN yum -y install python python-pip python-devel git gcc libjpeg-turbo-devel \
libxml2-devel libxslt-devel mysql-devel mysql ruby-sass libxml2-devel libxslt-devel mysql-devel mysql rubygem-sass
RUN yum -y clean all RUN yum -y clean all
RUN pip install --upgrade pip RUN pip install --upgrade pip

View File

@ -1,6 +1,7 @@
FROM fedora:latest FROM fedora:latest
RUN dnf upgrade -y libsolv
RUN dnf -y install python python-pip python-devel git gcc libjpeg-turbo-devel findutils \ RUN dnf -y install python python-pip python-devel git gcc libjpeg-turbo-devel findutils \
libxml2-devel libxslt-devel mysql-devel mysql npm redhat-rpm-config ruby-sass libxml2-devel libxslt-devel mysql-devel mysql npm redhat-rpm-config rubygem-sass
RUN dnf -y clean all RUN dnf -y clean all
RUN npm install -g eslint stylelint RUN npm install -g eslint stylelint

View File

@ -21,7 +21,7 @@ class ObservationSerializer(serializers.ModelSerializer):
model = Observation model = Observation
fields = ('id', 'start', 'end', 'ground_station', 'transmitter', fields = ('id', 'start', 'end', 'ground_station', 'transmitter',
'norad_cat_id', 'payload', 'waterfall', 'demoddata', 'station_name', 'norad_cat_id', 'payload', 'waterfall', 'demoddata', 'station_name',
'station_lat', 'station_lng') 'station_lat', 'station_lng', 'vetted_status')
read_only_fields = ['id', 'start', 'end', 'observation', 'ground_station', read_only_fields = ['id', 'start', 'end', 'observation', 'ground_station',
'transmitter', 'norad_cat_id', 'station_name', 'transmitter', 'norad_cat_id', 'station_name',
'station_lat', 'station_lng'] 'station_lat', 'station_lng']

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2017-12-24 17:01
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0029_auto_20171216_1712'),
]
operations = [
migrations.AlterField(
model_name='observation',
name='vetted_status',
field=models.CharField(choices=[(b'unknown', b'Unknown'), (b'verified', b'Verified'), (b'no_data', b'No Data'), (b'good', b'Good'), (b'bad', b'Bad'), (b'failed', b'Failed')], default=b'unknown', max_length=20),
),
]

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2017-12-23 23:58
from __future__ import unicode_literals
from django.db import migrations
def move_vetted(apps, schema_editor):
Observation = apps.get_model('base', 'Observation')
for obs in Observation.objects.all():
if obs.vetted_status == 'verified':
obs.vetted_status='good'
if obs.vetted_status == 'no_data':
obs.vetted_status='bad'
obs.save()
class Migration(migrations.Migration):
dependencies = [
('base', '0030_auto_20171224_1701'),
]
operations = [
migrations.RunPython(move_vetted),
]

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2017-12-24 17:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0031_migrate_vetted'),
]
operations = [
migrations.AlterField(
model_name='observation',
name='vetted_status',
field=models.CharField(choices=[(b'unknown', b'Unknown'), (b'good', b'Good'), (b'bad', b'Bad'), (b'failed', b'Failed')], default=b'unknown', max_length=20),
),
]

View File

@ -33,9 +33,9 @@ ANTENNA_TYPES = (
) )
OBSERVATION_STATUSES = ( OBSERVATION_STATUSES = (
('unknown', 'Unknown'), ('unknown', 'Unknown'),
('verified', 'Verified'), ('good', 'Good'),
('data_not_verified', 'Has Data, Not Verified'), ('bad', 'Bad'),
('no_data', 'No Data'), ('failed', 'Failed'),
) )
SATELLITE_STATUS = ['alive', 'dead', 're-entered'] SATELLITE_STATUS = ['alive', 'dead', 're-entered']
@ -122,10 +122,10 @@ class Station(models.Model):
@property @property
def success_rate(self): def success_rate(self):
observations = self.observations.all().count() observations = self.observations.all()
success = self.observations.exclude(payload='').count() success = observations.filter(id__in=(o.id for o in observations if o.has_audio)).count()
if observations: if observations:
return int(100 * (float(success) / float(observations))) return int(100 * (float(success) / float(observations.count())))
else: else:
return False return False
@ -198,14 +198,14 @@ class Satellite(models.Model):
return Observation.objects.filter(satellite=self).count() return Observation.objects.filter(satellite=self).count()
@property @property
def verified_count(self): def good_count(self):
data = Observation.objects.filter(satellite=self) data = Observation.objects.filter(satellite=self)
return data.filter(vetted_status='verified').count() return data.filter(vetted_status='good').count()
@property @property
def empty_count(self): def bad_count(self):
data = Observation.objects.filter(satellite=self) data = Observation.objects.filter(satellite=self)
return data.filter(vetted_status='no_data').count() return data.filter(vetted_status='bad').count()
@property @property
def unknown_count(self): def unknown_count(self):
@ -215,14 +215,14 @@ class Satellite(models.Model):
@property @property
def success_rate(self): def success_rate(self):
try: try:
return int(100 * (float(self.verified_count) / float(self.data_count))) return int(100 * (float(self.good_count) / float(self.data_count)))
except (ZeroDivisionError, TypeError): except (ZeroDivisionError, TypeError):
return 0 return 0
@property @property
def empty_rate(self): def bad_rate(self):
try: try:
return int(100 * (float(self.empty_count) / float(self.data_count))) return int(100 * (float(self.bad_count) / float(self.data_count)))
except (ZeroDivisionError, TypeError): except (ZeroDivisionError, TypeError):
return 0 return 0
@ -308,20 +308,25 @@ class Observation(models.Model):
def is_future(self): def is_future(self):
return self.end > now() return self.end > now()
# this payload has been vetted good/bad by someone # this payload has been vetted good/bad/failed by someone
@property @property
def is_vetted(self): def is_vetted(self):
return not self.vetted_status == 'unknown' return not self.vetted_status == 'unknown'
# this payload has been vetted as good by someone # this payload has been vetted as good by someone
@property @property
def is_verified(self): def is_good(self):
return self.vetted_status == 'verified' return self.vetted_status == 'good'
# this payload has been vetted as bad by someone # this payload has been vetted as bad by someone
@property @property
def is_no_data(self): def is_bad(self):
return self.vetted_status == 'no_data' return self.vetted_status == 'bad'
# this payload has been vetted as failed by someone
@property
def is_failed(self):
return self.vetted_status == 'failed'
@property @property
def has_audio(self): def has_audio(self):

View File

@ -142,6 +142,6 @@ def clean_observations():
observations = Observation.objects.filter(end__lt=threshold) observations = Observation.objects.filter(end__lt=threshold)
for obs in observations: for obs in observations:
if settings.ENVIRONMENT == 'stage': if settings.ENVIRONMENT == 'stage':
if not obs.is_verified: if not obs.is_good:
obs.delete() obs.delete()
archive_audio.delay(obs.id) archive_audio.delay(obs.id)

View File

@ -241,11 +241,11 @@ class ObservationsListViewTest(TestCase):
for x in xrange(1, 10): for x in xrange(1, 10):
self.stations.append(StationFactory()) self.stations.append(StationFactory())
for x in xrange(1, 10): for x in xrange(1, 10):
obs = ObservationFactory(vetted_status='no_data') obs = ObservationFactory(vetted_status='bad')
self.observations_bad.append(obs) self.observations_bad.append(obs)
self.observations.append(obs) self.observations.append(obs)
for x in xrange(1, 10): for x in xrange(1, 10):
obs = ObservationFactory(vetted_status='verified') obs = ObservationFactory(vetted_status='good')
self.observations_good.append(obs) self.observations_good.append(obs)
self.observations.append(obs) self.observations.append(obs)
for x in xrange(1, 10): for x in xrange(1, 10):
@ -434,9 +434,9 @@ class SettingsSiteViewTest(TestCase):
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
class ObservationVerifyViewtest(TestCase): class ObservationVetViewtest(TestCase):
""" """
Test vetting data as good Test vetting data
""" """
client = Client() client = Client()
user = None user = None
@ -456,48 +456,39 @@ class ObservationVerifyViewtest(TestCase):
self.transmitters.append(TransmitterFactory()) self.transmitters.append(TransmitterFactory())
for x in xrange(1, 10): for x in xrange(1, 10):
self.stations.append(StationFactory()) self.stations.append(StationFactory())
for x in xrange(1, 5):
self.observation = ObservationFactory() self.observations.append(ObservationFactory(vetted_status='unknown'))
def test_get_observation_vet_good(self): def test_get_observation_vet_good(self):
response = self.client.get('/observation_vet_good/%d/' % self.observation.id) obs = self.observations[0]
self.assertRedirects(response, '/observations/%d/' % self.observation.id) response = self.client.get('/observation_vet/%d/good/' % obs.id)
observation = Observation.objects.get(id=self.observation.id) self.assertRedirects(response, '/observations/%d/' % obs.id)
observation = Observation.objects.get(id=obs.id)
self.assertEqual(observation.vetted_user.username, self.user.username) self.assertEqual(observation.vetted_user.username, self.user.username)
self.assertEqual(observation.vetted_status, 'verified') self.assertEqual(observation.vetted_status, 'good')
@pytest.mark.django_db(transaction=True)
class ObservationMarkBadViewtest(TestCase):
"""
Test vetting data as bad
"""
client = Client()
user = None
satellites = []
stations = []
transmitters = []
def setUp(self):
self.user = UserFactory()
g = Group.objects.get(name='Moderators')
g.user_set.add(self.user)
self.client.force_login(self.user)
for x in xrange(1, 10):
self.satellites.append(SatelliteFactory())
for x in xrange(1, 10):
self.transmitters.append(TransmitterFactory())
for x in xrange(1, 10):
self.stations.append(StationFactory())
self.observation = ObservationFactory()
def test_get_observation_vet_bad(self): def test_get_observation_vet_bad(self):
response = self.client.get('/observation_vet_bad/%d/' % self.observation.id) obs = self.observations[1]
self.assertRedirects(response, '/observations/%d/' % self.observation.id) response = self.client.get('/observation_vet/%d/bad/' % obs.id)
observation = Observation.objects.get(id=self.observation.id) self.assertRedirects(response, '/observations/%d/' % obs.id)
observation = Observation.objects.get(id=obs.id)
self.assertEqual(observation.vetted_user.username, self.user.username) self.assertEqual(observation.vetted_user.username, self.user.username)
self.assertEqual(observation.vetted_status, 'no_data') self.assertEqual(observation.vetted_status, 'bad')
def test_get_observation_vet_failed(self):
obs = self.observations[2]
response = self.client.get('/observation_vet/%d/failed/' % obs.id)
self.assertRedirects(response, '/observations/%d/' % obs.id)
observation = Observation.objects.get(id=obs.id)
self.assertEqual(observation.vetted_user.username, self.user.username)
self.assertEqual(observation.vetted_status, 'failed')
def test_get_observation_undo_vet(self):
obs = self.observations[0]
response = self.client.get('/observation_vet/%d/unknown/' % obs.id)
self.assertRedirects(response, '/observations/%d/' % obs.id)
observation = Observation.objects.get(id=obs.id)
self.assertEqual(observation.vetted_status, 'unknown')
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)

View File

@ -24,10 +24,8 @@ base_urlpatterns = ([
views.prediction_windows, name='prediction_windows'), views.prediction_windows, name='prediction_windows'),
url(r'^pass_predictions/(?P<id>[\w.@+-]+)/$', url(r'^pass_predictions/(?P<id>[\w.@+-]+)/$',
views.pass_predictions, name='pass_predictions'), views.pass_predictions, name='pass_predictions'),
url(r'^observation_vet_good/(?P<id>[0-9]+)/$', views.observation_vet_good, url(r'^observation_vet/(?P<id>[0-9]+)/(?P<status>[a-z]+)/$', views.observation_vet,
name='observation_vet_good'), name='observation_vet'),
url(r'^observation_vet_bad/(?P<id>[0-9]+)/$', views.observation_vet_bad,
name='observation_vet_bad'),
# Stations # Stations
url(r'^stations/$', views.stations_list, name='stations_list'), url(r'^stations/$', views.stations_list, name='stations_list'),

View File

@ -159,7 +159,7 @@ class ObservationListView(ListView):
if not bad: if not bad:
observations = observations.exclude(vetted_status='no_data') observations = observations.exclude(vetted_status='no_data')
if not good: if not good:
observations = observations.exclude(vetted_status='verified') observations = observations.exclude(vetted_status='good')
if not unvetted: if not unvetted:
observations = observations.exclude(vetted_status='unknown', observations = observations.exclude(vetted_status='unknown',
id__in=(o.id for id__in=(o.id for
@ -493,30 +493,18 @@ def observation_delete(request, id):
@login_required @login_required
def observation_vet_good(request, id): def observation_vet(request, id, status):
observation = get_object_or_404(Observation, id=id) observation = get_object_or_404(Observation, id=id)
can_vet = vet_perms(request.user, observation) can_vet = vet_perms(request.user, observation)
if can_vet: if status in ['good', 'bad', 'failed', 'unknown'] and can_vet:
observation.vetted_status = 'verified' observation.vetted_status = status
observation.vetted_user = request.user observation.vetted_user = request.user
observation.vetted_datetime = datetime.today() observation.vetted_datetime = now()
observation.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime']) observation.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
messages.success(request, 'Observation vetted successfully.') if not status == 'unknown':
else: messages.success(request, 'Observation vetted successfully. [<a href="{0}">Undo</a>]'
messages.error(request, 'Permission denied.') .format(reverse('base:observation_vet',
return redirect(reverse('base:observation_view', kwargs={'id': observation.id})) kwargs={'id': observation.id, 'status': 'unknown'})))
@login_required
def observation_vet_bad(request, id):
observation = get_object_or_404(Observation, id=id)
can_vet = vet_perms(request.user, observation)
if can_vet:
observation.vetted_status = 'no_data'
observation.vetted_user = request.user
observation.vetted_datetime = datetime.today()
observation.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
messages.success(request, 'Observation vetted successfully.')
else: else:
messages.error(request, 'Permission denied.') messages.error(request, 'Permission denied.')
return redirect(reverse('base:observation_view', kwargs={'id': observation.id})) return redirect(reverse('base:observation_view', kwargs={'id': observation.id}))
@ -642,10 +630,10 @@ def pass_predictions(request, id):
'id': str(satellite.id), 'id': str(satellite.id),
'success_rate': str(satellite.success_rate), 'success_rate': str(satellite.success_rate),
'unknown_rate': str(satellite.unknown_rate), 'unknown_rate': str(satellite.unknown_rate),
'empty_rate': str(satellite.empty_rate), 'bad_rate': str(satellite.bad_rate),
'data_count': str(satellite.data_count), 'data_count': str(satellite.data_count),
'verified_count': str(satellite.verified_count), 'good_count': str(satellite.good_count),
'empty_count': str(satellite.empty_count), 'bad_count': str(satellite.bad_count),
'unknown_count': str(satellite.unknown_count), 'unknown_count': str(satellite.unknown_count),
'norad_cat_id': str(satellite.norad_cat_id), 'norad_cat_id': str(satellite.norad_cat_id),
'tr': tr.datetime(), # Rise time 'tr': tr.datetime(), # Rise time
@ -720,8 +708,8 @@ def satellite_view(request, id):
'names': sat.names, 'names': sat.names,
'image': sat.image, 'image': sat.image,
'success_rate': sat.success_rate, 'success_rate': sat.success_rate,
'verified_count': sat.verified_count, 'good_count': sat.good_count,
'empty_count': sat.empty_count, 'bad_count': sat.bad_count,
'unknown_count': sat.unknown_count, 'unknown_count': sat.unknown_count,
'data_count': sat.data_count, 'data_count': sat.data_count,
} }

View File

@ -0,0 +1,87 @@
body {
font-size: 14px;
line-height: 1.3;
font-family: ClearSans;
}
a:hover {
text-decoration: none;
}
.alert-debug {
color: black;
background-color: white;
border-color: #d6e9c6;
}
.alert-info {
color: #3a87ad;
background-color: #d9edf7;
border-color: #bce8f1;
}
.alert-success {
color: #468847;
background-color: #dff0d8;
border-color: #d6e9c6;
}
.alert-warning {
color: black;
background-color: orange;
border-color: #d6e9c6;
}
.alert-error {
color: #b94a48;
background-color: #f2dede;
border-color: #eed3d7;
}
.error {
margin-top: 40px auto 0 auto;
width: 500px;
text-align: center;
}
.help-block {
margin-bottom: 0;
}
.form-group {
margin-left: 0;
margin-right: 0;
}
.bottom-details {
margin-top: 10px;
}
.img-avatar {
border: 2px solid white;
border-radius: 50px;
float: left;
margin: 0 auto;
margin-top: -7px;
margin-right: 5px;
}
.form-avatar {
margin-bottom: 10px;
}
.progress {
margin-bottom: 0;
}
footer {
margin-bottom: 10px;
}
.footer-options {
padding: 10px;
}
.navbar-default {
background-color: white;
}

View File

@ -0,0 +1,43 @@
@font-face {
font-family: ClearSans;
src: url('../fonts/ClearSans-Regular.eot');
src: url('../fonts/staticClearSans-Regular.eot?#iefix') format('embedded-opentype'),
url('../fonts/ClearSans-Regular.woff') format('woff'),
url('../fonts/ClearSans-Regular.ttf') format('truetype'),
url('../fonts/ClearSans-Regular.svg#open_sans') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: ClearSans;
src: url('../fonts/ClearSans-Bold.eot');
src: url('../fonts/ClearSans-Bold.eot?#iefix') format('embedded-opentype'),
url('../fonts/ClearSans-Bold.woff') format('woff'),
url('../fonts/ClearSans-Bold.ttf') format('truetype'),
url('../fonts/ClearSans-Bold.svg#open_sansbold') format('svg');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: ClearSans;
src: url('../fonts/ClearSans-BoldItalic.eot');
src: url('../fonts/ClearSans-BoldItalic.eot?#iefix') format('embedded-opentype'),
url('../fonts/ClearSans-BoldItalic.woff') format('woff'),
url('../fonts/ClearSans-BoldItalic.ttf') format('truetype'),
url('../fonts/ClearSans-BoldItalic.svg#open_sansbold_italic') format('svg');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: ClearSans;
src: url('../fonts/ClearSans-Italic.eot');
src: url('../fonts/ClearSans-Italic.eot?#iefix') format('embedded-opentype'),
url('../fonts/ClearSans-Italic.woff') format('woff'),
url('../fonts/ClearSans-Italic.ttf') format('truetype'),
url('../fonts/ClearSans-Italic.svg#open_sansitalic') format('svg');
font-weight: normal;
font-style: italic;
}

View File

@ -0,0 +1,69 @@
.label-good {
background-color: #5cb85c;
color: white;
}
.btn-good {
@extend .label-good;
&:hover {
background-color: #449d44;
color: white;
}
}
.label-bad {
background-color: #d9534f;
color: white;
}
.btn-bad {
@extend .label-bad;
&:hover {
background-color: #c9302c;
color: white;
}
}
.label-failed {
background-color: black;
color: white;
}
.btn-failed {
@extend .label-failed;
&:hover {
background-color: #333;
color: white;
}
}
.label-unknown {
background-color: darkorange;
color: white;
}
.btn-unknown {
@extend .label-unknown;
&:hover {
background-color: orange;
color: white;
}
}
.label-future {
background-color: #5bc0de;
color: white;
}
.btn-future {
@extend .label-future;
&:hover {
background-color: #4ba0be;
color: white;
}
}

View File

@ -1,140 +1,6 @@
/* Fonts @import 'fonts';
==================== */ @import 'common';
@import 'vetted';
@font-face {
font-family: ClearSans;
src: url('../fonts/ClearSans-Regular.eot');
src: url('../fonts/ClearSans-Regular.eot?#iefix') format('embedded-opentype'),
url('../fonts/ClearSans-Regular.woff') format('woff'),
url('../fonts/ClearSans-Regular.ttf') format('truetype'),
url('../fonts/ClearSans-Regular.svg#open_sans') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: ClearSans;
src: url('../fonts/ClearSans-Bold.eot');
src: url('../fonts/ClearSans-Bold.eot?#iefix') format('embedded-opentype'),
url('../fonts/ClearSans-Bold.woff') format('woff'),
url('../fonts/ClearSans-Bold.ttf') format('truetype'),
url('../fonts/ClearSans-Bold.svg#open_sansbold') format('svg');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: ClearSans;
src: url('../fonts/ClearSans-BoldItalic.eot');
src: url('../fonts/ClearSans-BoldItalic.eot?#iefix') format('embedded-opentype'),
url('../fonts/ClearSans-BoldItalic.woff') format('woff'),
url('../fonts/ClearSans-BoldItalic.ttf') format('truetype'),
url('../fonts/ClearSans-BoldItalic.svg#open_sansbold_italic') format('svg');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: ClearSans;
src: url('../fonts/ClearSans-Italic.eot');
src: url('../fonts/ClearSans-Italic.eot?#iefix') format('embedded-opentype'),
url('../fonts/ClearSans-Italic.woff') format('woff'),
url('../fonts/ClearSans-Italic.ttf') format('truetype'),
url('../fonts/ClearSans-Italic.svg#open_sansitalic') format('svg');
font-weight: normal;
font-style: italic;
}
/* Generic
==================== */
body {
font-size: 14px;
line-height: 1.3;
font-family: ClearSans;
}
a:hover {
text-decoration: none;
}
.alert-debug {
color: black;
background-color: white;
border-color: #d6e9c6;
}
.alert-info {
color: #3a87ad;
background-color: #d9edf7;
border-color: #bce8f1;
}
.alert-success {
color: #468847;
background-color: #dff0d8;
border-color: #d6e9c6;
}
.alert-warning {
color: black;
background-color: orange;
border-color: #d6e9c6;
}
.alert-error {
color: #b94a48;
background-color: #f2dede;
border-color: #eed3d7;
}
.error {
margin-top: 40px auto 0 auto;
width: 500px;
text-align: center;
}
.help-block {
margin-bottom: 0;
}
.form-group {
margin-left: 0;
margin-right: 0;
}
.bottom-details {
margin-top: 10px;
}
.img-avatar {
border: 2px solid white;
border-radius: 50px;
float: left;
margin: 0 auto;
margin-top: -7px;
margin-right: 5px;
}
.form-avatar {
margin-bottom: 10px;
}
.progress {
margin-bottom: 0;
}
footer {
margin-bottom: 10px;
}
.footer-options {
padding: 10px;
}
.navbar-default {
background-color: white;
}
#main-navbar { #main-navbar {
margin-top: 2%; margin-top: 2%;
@ -418,10 +284,6 @@ span.datetime-time {
font-size: 12px; font-size: 12px;
} }
.label-warning {
background-color: darkorange;
}
#timeline2 .axis { #timeline2 .axis {
transform: translate(0, 30px); transform: translate(0, 30px);
-ms-transform: translate(0, 30px); /* IE 9 */ -ms-transform: translate(0, 30px); /* IE 9 */

View File

@ -17,9 +17,9 @@ $(document).ready(function() {
modal.find('#new-obs-link').attr('href', '/observations/new/?norad=' + satlink.data('id')); modal.find('#new-obs-link').attr('href', '/observations/new/?norad=' + satlink.data('id'));
modal.find('#old-obs-link').attr('href', '/observations/?norad=' + satlink.data('id')); modal.find('#old-obs-link').attr('href', '/observations/?norad=' + satlink.data('id'));
modal.find('.satellite-success').text(data.success_rate + '% success on ' + data.data_count + ' observations'); modal.find('.satellite-success').text(data.success_rate + '% success on ' + data.data_count + ' observations');
modal.find('.satellite-verified').text(data.verified_count); modal.find('.satellite-good').text(data.good_count);
modal.find('.satellite-unknown').text(data.unknown_count); modal.find('.satellite-unknown').text(data.unknown_count);
modal.find('.satellite-empty').text(data.empty_count); modal.find('.satellite-bad').text(data.bad_count);
if (data.image) { if (data.image) {
modal.find('.satellite-img-full').attr('src', data.image); modal.find('.satellite-img-full').attr('src', data.image);
} else { } else {

View File

@ -11,7 +11,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
{% compress css %} {% compress css %}
<link rel="stylesheet" href="{% static 'lib/bootstrap/dist/css/bootstrap.min.css' %}"> <link rel="stylesheet" href="{% static 'lib/bootstrap/dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/app.css' %}"> <link rel="stylesheet" type="text/scss" href="{% static 'css/app.scss' %}">
{% block css %} {% block css %}
{% endblock %} {% endblock %}
{% endcompress %} {% endcompress %}

View File

@ -122,17 +122,13 @@
<tr> <tr>
<td> <td>
<a href="{% url 'base:observation_view' id=observation.id %}"> <a href="{% url 'base:observation_view' id=observation.id %}">
{% if observation.is_verified %} {% if observation.is_vetted %}
<span class="label label-success" title="There is known good data in this observation" <span class="label label-{{observation.vetted_status }}">{{ observation.id }}</span>
{% elif observation.is_future %} {% elif observation.is_future %}
<span class="label label-info" title="This observation is in the future" <span class="label label-future">{{ observation.id }}</span>
{% elif not observation.is_vetted %}
<span class="label label-warning" title="There is data that needs vetting in this observation"
{% else %} {% else %}
<span class="label label-danger" title="No good data in this observation" <span class="label label-unknown">{{ observation.id }}</span>
{% endif %}> {% endif %}
{{ observation.id }}
</span>
</a> </a>
</td> </td>
<td> <td>

View File

@ -74,40 +74,39 @@
<div class="front-line"> <div class="front-line">
<span class="label label-default">Status</span> <span class="label label-default">Status</span>
<span class="front-data"> <span class="front-data">
{% if observation.is_verified %} {% if observation.is_vetted %}
<span class="label label-xs label-success" aria-hidden="true" <span class="label label-xs label-{{ observation.vetted_status }}" aria-hidden="true"
data-toggle="tooltip" data-placement="right" data-toggle="tooltip" data-placement="right"
title="Vetted good on {{ observation.vetted_datetime|date:"Y-m-d H:i:s" }} by {{ observation.vetted_user.displayname }}">Good</span> title="Vetted {{ observation.vetted_status }} on {{ observation.vetted_datetime|date:"Y-m-d H:i:s" }} by {{ observation.vetted_user.displayname }}">{{ observation.get_vetted_status_display }}</span>
{% elif observation.is_no_data %}
<span class="label label-xs label-danger" aria-hidden="true"
data-toggle="tooltip" data-placement="right"
title="Vetted bad on {{ observation.vetted_datetime|date:"Y-m-d H:i:s" }} by {{ observation.vetted_user.displayname }}">Bad</span>
{% elif observation.is_future %} {% elif observation.is_future %}
<span class="label label-xs label-info" aria-hidden="true" <span class="label label-xs label-info" aria-hidden="true"
data-toggle="tooltip" data-placement="right" data-toggle="tooltip" data-placement="right"
title="This observation is in the future">Pending</span> title="This observation is in the future">Pending</span>
{% else %} {% else %}
<span class="label label-xs label-warning" aria-hidden="true" <span class="label label-xs label-{{ observation.vetted_status }}" aria-hidden="true"
data-toggle="tooltip" data-placement="right" data-toggle="tooltip" data-placement="right"
title="This observation needs vetting">Unvetted</span> title="This observation needs vetting">Unvetted</span>
{% if observation.is_past %} {% if can_vet %}
{% if not observation.is_vetted and can_vet %} <div class="pull-right">
<div class="pull-right"> <a href="https://wiki.satnogs.org/Operation#Rating_observations" target="_blank">
<a href="https://wiki.satnogs.org/Operation#Rating_observations" target="_blank"> <span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> </a>
</a> <a href="{% url 'base:observation_vet' id=observation.id status='good' %}">
<a href="{% url 'base:observation_vet_good' id=observation.id %}"> <button id="good-data" type="button" class="btn btn-xs btn-success" title="good" data-toggle="tooltip">
<button id="good-data" type="button" class="btn btn-xs btn-success"> <span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> </button>
</button> </a>
</a> <a href="{% url 'base:observation_vet' id=observation.id status='bad' %}">
<a href="{% url 'base:observation_vet_bad' id=observation.id %}"> <button id="bad-data" type="button" class="btn btn-xs btn-danger" title="bad" data-toggle="tooltip">
<button id="bad-data" type="button" class="btn btn-xs btn-danger"> <span class="glyphicon glyphicon-thumbs-down" aria-hidden="true"></span>
<span class="glyphicon glyphicon-thumbs-down" aria-hidden="true"></span> </button>
</button> </a>
</a> <a href="{% url 'base:observation_vet' id=observation.id status='failed' %}">
</div> <button id="bad-failed" type="button" class="btn btn-xs btn-failed" title="failed" data-toggle="tooltip">
{% endif %} <span class="glyphicon glyphicon-minus" aria-hidden="true"></span>
</button>
</a>
</div>
{% endif %} {% endif %}
{% endif %} {% endif %}
</span> </span>

View File

@ -27,16 +27,16 @@
<div class="form-group col-md-3"> <div class="form-group col-md-3">
<label for="data-selector">Data</label> <label for="data-selector">Data</label>
<div id="data-selector" class="btn-group" data-toggle="buttons"> <div id="data-selector" class="btn-group" data-toggle="buttons">
<label class="btn btn-info btn-sm {% if future == '1' %}active{% endif %}" aria-expanded="true" aria-controls="future"> <label class="btn btn-future btn-sm {% if future == '1' %}active{% endif %}" aria-expanded="true" aria-controls="future">
<input type="checkbox" name="future" {% if future == '1' %}checked{% endif %} autocomplete="off">Future <input type="checkbox" name="future" {% if future == '1' %}checked{% endif %} autocomplete="off">Future
</label> </label>
<label class="btn btn-success btn-sm {% if good == '1' %}active{% endif %}" aria-expanded="true" aria-controls="good"> <label class="btn btn-good btn-sm {% if good == '1' %}active{% endif %}" aria-expanded="true" aria-controls="good">
<input type="checkbox" name="good" {% if good == '1' %}checked{% endif %} autocomplete="off">Good <input type="checkbox" name="good" {% if good == '1' %}checked{% endif %} autocomplete="off">Good
</label> </label>
<label class="btn btn-danger btn-sm {% if bad == '1' %}active{% endif %}" aria-expanded="true" aria-controls="bad"> <label class="btn btn-bad btn-sm {% if bad == '1' %}active{% endif %}" aria-expanded="true" aria-controls="bad">
<input type="checkbox" name="bad" {% if bad == '1' %}checked{% endif %} autocomplete="off">Bad <input type="checkbox" name="bad" {% if bad == '1' %}checked{% endif %} autocomplete="off">Bad
</label> </label>
<label class="btn btn-warning btn-sm {% if unvetted == '1' %}active{% endif %}" aria-expanded="true" aria-controls="unvetted"> <label class="btn btn-unknown btn-sm {% if unvetted == '1' %}active{% endif %}" aria-expanded="true" aria-controls="unvetted">
<input type="checkbox" name="unvetted" {% if unvetted == '1' %}checked{% endif %} autocomplete="off">Unvetted <input type="checkbox" name="unvetted" {% if unvetted == '1' %}checked{% endif %} autocomplete="off">Unvetted
</label> </label>
</div> </div>
@ -100,17 +100,13 @@
{% if observation.id in scheduled %}class="bg-info"{% endif %}> {% if observation.id in scheduled %}class="bg-info"{% endif %}>
<td> <td>
<a href="{% url 'base:observation_view' id=observation.id %}" class="obs-link"> <a href="{% url 'base:observation_view' id=observation.id %}" class="obs-link">
{% if observation.is_verified %} {% if observation.is_vetted %}
<span class="label label-success" title="There is known good data in this observation" <span class="label label-{{observation.vetted_status }}">{{ observation.id }}</span>
{% elif observation.is_future %} {% elif observation.is_future %}
<span class="label label-info" title="This observation is in the future" <span class="label label-future">{{ observation.id }}</span>
{% elif not observation.is_vetted %}
<span class="label label-warning" title="There is data that needs vetting in this observation"
{% else %} {% else %}
<span class="label label-danger" title="No good data in this observation" <span class="label label-unknown">{{ observation.id }}</span>
{% endif %}> {% endif %}
{{ observation.id }}
</span>
</a> </a>
</td> </td>
<td> <td>

View File

@ -8,16 +8,19 @@
<div class="modal-body"> <div class="modal-body">
<h4>The ID number of each observation is listed below, colored as such:</h4> <h4>The ID number of each observation is listed below, colored as such:</h4>
<p> <p>
<span class="label label-info" title="This observation is in the future">43</span> This observation is in the future</span> <span class="label label-future" title="This observation is in the future">43</span> This observation is in the future</span>
</p> </p>
<p> <p>
<span class="label label-success" title="There is known good data in this observation">12</span> There is some known good data in this observation <span class="label label-good" title="There is known good data in this observation">12</span> There is some known good data in this observation
</p> </p>
<p> <p>
<span class="label label-warning" title="There is data that needs vetting in this observation">56</span> There is data that needs vetting in this observation <span class="label label-unknown" title="There is data that needs vetting in this observation">56</span> There is data that needs vetting in this observation
</p> </p>
<p> <p>
<span class="label label-danger" title="No good data in this observation">93</span> No good data in this observation <span class="label label-bad" title="No good data in this observation">78</span> No good data in this observation
</p>
<p>
<span class="label label-failed" title="This observation failed">93</span> Observation failed
</p> </p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@ -27,9 +27,9 @@
<span class="satellite-success"></span> <span class="satellite-success"></span>
</li> </li>
<li> <li>
<span class="label label-success satellite-verified" data-toggle="tooltip" data-placement="bottom" title="Successful observations"></span> <span class="label label-success satellite-good" data-toggle="tooltip" data-placement="bottom" title="Successful observations"></span>
<span class="label label-warning satellite-unknown" data-toggle="tooltip" data-placement="bottom" title="Unknown observations"></span> <span class="label label-warning satellite-unknown" data-toggle="tooltip" data-placement="bottom" title="Unknown observations"></span>
<span class="label label-danger satellite-empty" data-toggle="tooltip" data-placement="bottom" title="Empty observations"></span> <span class="label label-danger satellite-bad" data-toggle="tooltip" data-placement="bottom" title="Bad observations"></span>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -123,6 +123,8 @@
label-success" title="There is known good data in this observation" label-success" title="There is known good data in this observation"
{% elif observation.is_future %} {% elif observation.is_future %}
label-info" title="This observation is in the future" label-info" title="This observation is in the future"
{% elif observation.is_failed %}
label-default" title="This observation failed"
{% elif not observation.is_vetted %} {% elif not observation.is_vetted %}
label-warning" title="There is data that needs vetting in this observation" label-warning" title="There is data that needs vetting in this observation"
{% else %} {% else %}