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 -y install epel-release
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 pip install --upgrade pip

View File

@ -1,6 +1,7 @@
FROM fedora:latest
RUN dnf upgrade -y libsolv
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 npm install -g eslint stylelint

View File

@ -21,7 +21,7 @@ class ObservationSerializer(serializers.ModelSerializer):
model = Observation
fields = ('id', 'start', 'end', 'ground_station', 'transmitter',
'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',
'transmitter', 'norad_cat_id', 'station_name',
'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 = (
('unknown', 'Unknown'),
('verified', 'Verified'),
('data_not_verified', 'Has Data, Not Verified'),
('no_data', 'No Data'),
('good', 'Good'),
('bad', 'Bad'),
('failed', 'Failed'),
)
SATELLITE_STATUS = ['alive', 'dead', 're-entered']
@ -122,10 +122,10 @@ class Station(models.Model):
@property
def success_rate(self):
observations = self.observations.all().count()
success = self.observations.exclude(payload='').count()
observations = self.observations.all()
success = observations.filter(id__in=(o.id for o in observations if o.has_audio)).count()
if observations:
return int(100 * (float(success) / float(observations)))
return int(100 * (float(success) / float(observations.count())))
else:
return False
@ -198,14 +198,14 @@ class Satellite(models.Model):
return Observation.objects.filter(satellite=self).count()
@property
def verified_count(self):
def good_count(self):
data = Observation.objects.filter(satellite=self)
return data.filter(vetted_status='verified').count()
return data.filter(vetted_status='good').count()
@property
def empty_count(self):
def bad_count(self):
data = Observation.objects.filter(satellite=self)
return data.filter(vetted_status='no_data').count()
return data.filter(vetted_status='bad').count()
@property
def unknown_count(self):
@ -215,14 +215,14 @@ class Satellite(models.Model):
@property
def success_rate(self):
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):
return 0
@property
def empty_rate(self):
def bad_rate(self):
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):
return 0
@ -308,20 +308,25 @@ class Observation(models.Model):
def is_future(self):
return self.end > now()
# this payload has been vetted good/bad by someone
# this payload has been vetted good/bad/failed 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'
def is_good(self):
return self.vetted_status == 'good'
# this payload has been vetted as bad by someone
@property
def is_no_data(self):
return self.vetted_status == 'no_data'
def is_bad(self):
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
def has_audio(self):

View File

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

View File

@ -241,11 +241,11 @@ class ObservationsListViewTest(TestCase):
for x in xrange(1, 10):
self.stations.append(StationFactory())
for x in xrange(1, 10):
obs = ObservationFactory(vetted_status='no_data')
obs = ObservationFactory(vetted_status='bad')
self.observations_bad.append(obs)
self.observations.append(obs)
for x in xrange(1, 10):
obs = ObservationFactory(vetted_status='verified')
obs = ObservationFactory(vetted_status='good')
self.observations_good.append(obs)
self.observations.append(obs)
for x in xrange(1, 10):
@ -434,9 +434,9 @@ class SettingsSiteViewTest(TestCase):
@pytest.mark.django_db(transaction=True)
class ObservationVerifyViewtest(TestCase):
class ObservationVetViewtest(TestCase):
"""
Test vetting data as good
Test vetting data
"""
client = Client()
user = None
@ -456,48 +456,39 @@ class ObservationVerifyViewtest(TestCase):
self.transmitters.append(TransmitterFactory())
for x in xrange(1, 10):
self.stations.append(StationFactory())
self.observation = ObservationFactory()
for x in xrange(1, 5):
self.observations.append(ObservationFactory(vetted_status='unknown'))
def test_get_observation_vet_good(self):
response = self.client.get('/observation_vet_good/%d/' % self.observation.id)
self.assertRedirects(response, '/observations/%d/' % self.observation.id)
observation = Observation.objects.get(id=self.observation.id)
obs = self.observations[0]
response = self.client.get('/observation_vet/%d/good/' % 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, 'verified')
@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()
self.assertEqual(observation.vetted_status, 'good')
def test_get_observation_vet_bad(self):
response = self.client.get('/observation_vet_bad/%d/' % self.observation.id)
self.assertRedirects(response, '/observations/%d/' % self.observation.id)
observation = Observation.objects.get(id=self.observation.id)
obs = self.observations[1]
response = self.client.get('/observation_vet/%d/bad/' % 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, '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)

View File

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

View File

@ -159,7 +159,7 @@ class ObservationListView(ListView):
if not bad:
observations = observations.exclude(vetted_status='no_data')
if not good:
observations = observations.exclude(vetted_status='verified')
observations = observations.exclude(vetted_status='good')
if not unvetted:
observations = observations.exclude(vetted_status='unknown',
id__in=(o.id for
@ -493,30 +493,18 @@ def observation_delete(request, id):
@login_required
def observation_vet_good(request, id):
def observation_vet(request, id, status):
observation = get_object_or_404(Observation, id=id)
can_vet = vet_perms(request.user, observation)
if can_vet:
observation.vetted_status = 'verified'
if status in ['good', 'bad', 'failed', 'unknown'] and can_vet:
observation.vetted_status = status
observation.vetted_user = request.user
observation.vetted_datetime = datetime.today()
observation.vetted_datetime = now()
observation.save(update_fields=['vetted_status', 'vetted_user', 'vetted_datetime'])
messages.success(request, 'Observation vetted successfully.')
else:
messages.error(request, 'Permission denied.')
return redirect(reverse('base:observation_view', kwargs={'id': observation.id}))
@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.')
if not status == 'unknown':
messages.success(request, 'Observation vetted successfully. [<a href="{0}">Undo</a>]'
.format(reverse('base:observation_vet',
kwargs={'id': observation.id, 'status': 'unknown'})))
else:
messages.error(request, 'Permission denied.')
return redirect(reverse('base:observation_view', kwargs={'id': observation.id}))
@ -642,10 +630,10 @@ def pass_predictions(request, id):
'id': str(satellite.id),
'success_rate': str(satellite.success_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),
'verified_count': str(satellite.verified_count),
'empty_count': str(satellite.empty_count),
'good_count': str(satellite.good_count),
'bad_count': str(satellite.bad_count),
'unknown_count': str(satellite.unknown_count),
'norad_cat_id': str(satellite.norad_cat_id),
'tr': tr.datetime(), # Rise time
@ -720,8 +708,8 @@ def satellite_view(request, id):
'names': sat.names,
'image': sat.image,
'success_rate': sat.success_rate,
'verified_count': sat.verified_count,
'empty_count': sat.empty_count,
'good_count': sat.good_count,
'bad_count': sat.bad_count,
'unknown_count': sat.unknown_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
==================== */
@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;
}
@import 'fonts';
@import 'common';
@import 'vetted';
#main-navbar {
margin-top: 2%;
@ -418,10 +284,6 @@ span.datetime-time {
font-size: 12px;
}
.label-warning {
background-color: darkorange;
}
#timeline2 .axis {
transform: translate(0, 30px);
-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('#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-verified').text(data.verified_count);
modal.find('.satellite-good').text(data.good_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) {
modal.find('.satellite-img-full').attr('src', data.image);
} else {

View File

@ -11,7 +11,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% compress 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 %}
{% endblock %}
{% endcompress %}

View File

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

View File

@ -74,40 +74,39 @@
<div class="front-line">
<span class="label label-default">Status</span>
<span class="front-data">
{% if observation.is_verified %}
<span class="label label-xs label-success" aria-hidden="true"
{% if observation.is_vetted %}
<span class="label label-xs label-{{ observation.vetted_status }}" aria-hidden="true"
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>
{% 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>
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_future %}
<span class="label label-xs label-info" aria-hidden="true"
data-toggle="tooltip" data-placement="right"
title="This observation is in the future">Pending</span>
{% 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"
title="This observation needs vetting">Unvetted</span>
{% if observation.is_past %}
{% if not observation.is_vetted and can_vet %}
<div class="pull-right">
<a href="https://wiki.satnogs.org/Operation#Rating_observations" target="_blank">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
</a>
<a href="{% url 'base:observation_vet_good' id=observation.id %}">
<button id="good-data" type="button" class="btn btn-xs btn-success">
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
</button>
</a>
<a href="{% url 'base:observation_vet_bad' id=observation.id %}">
<button id="bad-data" type="button" class="btn btn-xs btn-danger">
<span class="glyphicon glyphicon-thumbs-down" aria-hidden="true"></span>
</button>
</a>
</div>
{% endif %}
{% if can_vet %}
<div class="pull-right">
<a href="https://wiki.satnogs.org/Operation#Rating_observations" target="_blank">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
</a>
<a href="{% url 'base:observation_vet' id=observation.id status='good' %}">
<button id="good-data" type="button" class="btn btn-xs btn-success" title="good" data-toggle="tooltip">
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
</button>
</a>
<a href="{% url 'base:observation_vet' id=observation.id status='bad' %}">
<button id="bad-data" type="button" class="btn btn-xs btn-danger" title="bad" data-toggle="tooltip">
<span class="glyphicon glyphicon-thumbs-down" aria-hidden="true"></span>
</button>
</a>
<a href="{% url 'base:observation_vet' id=observation.id status='failed' %}">
<button id="bad-failed" type="button" class="btn btn-xs btn-failed" title="failed" data-toggle="tooltip">
<span class="glyphicon glyphicon-minus" aria-hidden="true"></span>
</button>
</a>
</div>
{% endif %}
{% endif %}
</span>

View File

@ -27,16 +27,16 @@
<div class="form-group col-md-3">
<label for="data-selector">Data</label>
<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
</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
</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
</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
</label>
</div>
@ -100,17 +100,13 @@
{% if observation.id in scheduled %}class="bg-info"{% endif %}>
<td>
<a href="{% url 'base:observation_view' id=observation.id %}" class="obs-link">
{% if observation.is_verified %}
<span class="label label-success" title="There is known good data in this observation"
{% if observation.is_vetted %}
<span class="label label-{{observation.vetted_status }}">{{ observation.id }}</span>
{% elif observation.is_future %}
<span class="label label-info" title="This observation is in the future"
{% elif not observation.is_vetted %}
<span class="label label-warning" title="There is data that needs vetting in this observation"
<span class="label label-future">{{ observation.id }}</span>
{% else %}
<span class="label label-danger" title="No good data in this observation"
{% endif %}>
{{ observation.id }}
</span>
<span class="label label-unknown">{{ observation.id }}</span>
{% endif %}
</a>
</td>
<td>

View File

@ -8,16 +8,19 @@
<div class="modal-body">
<h4>The ID number of each observation is listed below, colored as such:</h4>
<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>
<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>
<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>
<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>
</div>
<div class="modal-footer">

View File

@ -27,9 +27,9 @@
<span class="satellite-success"></span>
</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-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>
</ul>
</div>

View File

@ -123,6 +123,8 @@
label-success" title="There is known good data in this observation"
{% elif observation.is_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 %}
label-warning" title="There is data that needs vetting in this observation"
{% else %}