Skip to content

Commit 69aeaa8

Browse files
[Fix] /v2/nodes (#38)
* cleanup * select valid nodes + valid prev and current locations + average 5 mins * remove unneeded seed * cached data command * celery task to cache data every 5 minutes * refactor * format cleanup * use database model instead of json file * add admin visibility * cleanup * cleanup
1 parent 05a3bad commit 69aeaa8

File tree

8 files changed

+139
-82
lines changed

8 files changed

+139
-82
lines changed

sensorsafrica/admin.py

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.contrib import admin
22

3-
from .api.models import SensorDataStat, City
3+
from .api.models import LastActiveNodes, SensorDataStat, City
44

55
from feinstaub.sensors.admin import (
66
SensorLocationAdmin,
@@ -14,44 +14,43 @@
1414
import django.utils.timezone
1515

1616

17-
def last_data_received_at(self, obj):
18-
then = (
19-
SensorData.objects.filter(location=obj)
20-
.values_list("timestamp", flat=True)
21-
.last()
22-
)
23-
now = datetime.datetime.now(django.utils.timezone.utc)
17+
@admin.register(LastActiveNodes)
18+
class LastActiveNodesAdmin(admin.ModelAdmin):
19+
readonly_fields = ["node", "location", "last_data_received_at"]
20+
list_display = ["node", "location", "received"]
21+
search_fields = ["node", "location", "last_data_received_at"]
22+
list_filter = ["node", "location", "last_data_received_at"]
2423

25-
if not then:
26-
return "Unknown"
24+
def received(self, obj):
25+
now = datetime.datetime.now(django.utils.timezone.utc)
2726

28-
return "( %s ) %s" % (
29-
timeago.format(then, now),
30-
SensorData.objects.filter(location=obj)
31-
.values_list("timestamp", flat=True)
32-
.last(),
33-
)
27+
if not obj.last_data_received_at:
28+
return "Unknown"
3429

30+
return "( %s ) %s" % (
31+
timeago.format(obj.last_data_received_at, now),
32+
obj.last_data_received_at,
33+
)
3534

36-
def latitude_and_longitude(self, obj):
37-
return "%s,%s" % (obj.latitude, obj.longitude)
35+
def get_actions(self, request):
36+
actions = super(LastActiveNodesAdmin, self).get_actions(request)
37+
del actions["delete_selected"]
38+
return actions
3839

40+
def has_add_permission(self, request):
41+
return False
42+
43+
def has_delete_permission(self, request, obj=None):
44+
return False
3945

40-
def node_UID(self, obj):
41-
return Node.objects.filter(location=obj).values_list("uid", flat=True).first()
46+
def save_model(self, request, obj, form, change):
47+
pass
4248

49+
def delete_model(self, request, obj):
50+
pass
4351

44-
SensorLocation._meta.verbose_name_plural = "Sensor Node Locations"
45-
SensorLocationAdmin.list_display = [
46-
"node_UID",
47-
"location",
48-
"city",
49-
"latitude_and_longitude",
50-
"last_data_received_at",
51-
]
52-
SensorLocationAdmin.last_data_received_at = last_data_received_at
53-
SensorLocationAdmin.latitude_and_longitude = latitude_and_longitude
54-
SensorLocationAdmin.node_UID = node_UID
52+
def save_related(self, request, form, formsets, change):
53+
pass
5554

5655

5756
@admin.register(SensorDataStat)

sensorsafrica/api/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,12 @@ def __str__(self):
4848
self.minimum,
4949
self.maximum,
5050
)
51+
52+
53+
class LastActiveNodes(TimeStampedModel):
54+
node = models.ForeignKey(Node)
55+
location = models.ForeignKey(SensorLocation)
56+
last_data_received_at = models.DateTimeField()
57+
58+
class Meta:
59+
unique_together = ['node', 'location']

sensorsafrica/api/v2/views.py

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import datetime
22
import pytz
3+
import json
34

45
from rest_framework.exceptions import ValidationError
56

7+
from django.conf import settings
68
from django.utils import timezone
79
from django.db.models import ExpressionWrapper, F, FloatField, Max, Min, Sum, Avg, Q
810
from django.db.models.functions import Cast, TruncDate
911
from rest_framework import mixins, pagination, viewsets
1012

11-
from ..models import SensorDataStat, City, Node
13+
from ..models import SensorDataStat, LastActiveNodes, City, Node
1214
from .serializers import SensorDataStatSerializer, CitySerializer
1315

1416
from feinstaub.sensors.views import StandardResultsSetPagination
@@ -205,36 +207,24 @@ class CityView(mixins.ListModelMixin, viewsets.GenericViewSet):
205207

206208

207209
class NodesView(viewsets.ViewSet):
208-
209-
# Cache requested url for each user for 1 hour
210-
@method_decorator(cache_page(3600))
211210
def list(self, request):
212211
nodes = []
213-
for location in SensorLocation.objects.iterator():
214-
Node.objects.filter(location=location)
215-
result = (
216-
SensorData.objects.filter(location=location)
217-
.values(
218-
"sensor__node__location",
219-
"sensor__node__location__location",
220-
"sensor__node__location__city",
221-
"sensor__node__location__longitude",
222-
"sensor__node__location__latitude",
223-
"timestamp",
224-
)
225-
.last()
226-
)
212+
for last_active_node in LastActiveNodes.objects.iterator():
213+
node = Node.objects.filter(
214+
Q(id=last_active_node.node.id), ~Q(sensors=None)
215+
).get()
216+
last_data_received_at = last_active_node.last_data_received_at
227217

218+
# last_data_received_at
228219
stats = []
229220
prev_location = None
230-
if result:
231-
last_data_datetime = result["timestamp"]
232-
last_24_hours = last_data_datetime - datetime.timedelta(hours=24)
221+
if last_data_received_at:
222+
last_5_mins = last_data_received_at - datetime.timedelta(minutes=5)
233223
stats = (
234224
SensorDataValue.objects.filter(
235-
Q(sensordata__location=location),
236-
Q(sensordata__timestamp__gte=last_24_hours),
237-
Q(sensordata__timestamp__lte=last_data_datetime),
225+
Q(sensordata__location=last_active_node.location.id),
226+
Q(sensordata__timestamp__gte=last_5_mins),
227+
Q(sensordata__timestamp__lte=last_data_received_at),
238228
# Ignore timestamp values
239229
~Q(value_type="timestamp"),
240230
# Match only valid float text
@@ -252,28 +242,31 @@ def list(self, request):
252242
)
253243
)
254244

255-
if result["sensor__node__location"] != location.id:
256-
prev_location = {
257-
"name": result["sensor__node__location__location"],
258-
"longitude": result["sensor__node__location__longitude"],
259-
"latitude": result["sensor__node__location__latitude"],
260-
"city": {
261-
"name": result["sensor__node__location__city"],
262-
"slug": slugify(result["sensor__node__location__city"]),
263-
},
264-
}
245+
if last_active_node.location.id != node.location.id:
246+
prev_location = {
247+
"name": node.location.location,
248+
"longitude": node.location.longitude,
249+
"latitude": node.location.latitude,
250+
"city": {
251+
"name": node.location.city,
252+
"slug": slugify(node.location.city),
253+
},
254+
}
265255

266256
nodes.append(
267257
{
268258
"node_moved": prev_location is not None,
269259
"prev_location": prev_location,
270260
"location": {
271-
"longitude": location.longitude,
272-
"latitude": location.latitude,
273-
"name": location.location,
274-
"city": {"name": location.city, "slug": slugify(location.city)},
261+
"name": last_active_node.location.location,
262+
"longitude": last_active_node.location.longitude,
263+
"latitude": last_active_node.location.latitude,
264+
"city": {
265+
"name": last_active_node.location.city,
266+
"slug": slugify(last_active_node.location.city),
267+
},
275268
},
276-
"last_data_received_at": last_data_datetime,
269+
"last_data_received_at": last_data_received_at,
277270
"stats": stats,
278271
}
279272
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from django.core.management import BaseCommand
2+
from django.core.cache import cache
3+
4+
from django.conf import settings
5+
6+
from django.db.models import Max
7+
8+
from sensorsafrica.api.models import Node, SensorLocation, LastActiveNodes
9+
from feinstaub.sensors.models import SensorData
10+
11+
12+
class Command(BaseCommand):
13+
help = ""
14+
15+
def handle(self, *args, **options):
16+
for data in (
17+
SensorData.objects.values("sensor__node", "location")
18+
.order_by("sensor__node__id", "location__id")
19+
.annotate(timestamp=Max("timestamp"))
20+
):
21+
LastActiveNodes.objects.update_or_create(
22+
node=Node(pk=data["sensor__node"]),
23+
location=SensorLocation(pk=data["location"]),
24+
last_data_received_at=data["timestamp"],
25+
)

sensorsafrica/management/commands/seed.py

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.18 on 2019-05-09 11:45
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
import django.db.models.deletion
7+
import django_extensions.db.fields
8+
9+
10+
class Migration(migrations.Migration):
11+
12+
dependencies = [
13+
('sensors', '0020_auto_20190314_1232'),
14+
('sensorsafrica', '0003_auto_20190222_1137'),
15+
]
16+
17+
operations = [
18+
migrations.CreateModel(
19+
name='LastActiveNodes',
20+
fields=[
21+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22+
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
23+
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
24+
('last_data_received_at', models.DateTimeField()),
25+
('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sensors.SensorLocation')),
26+
('node', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sensors.Node')),
27+
],
28+
),
29+
migrations.AlterUniqueTogether(
30+
name='lastactivenodes',
31+
unique_together=set([('node', 'location')]),
32+
),
33+
]

sensorsafrica/settings.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,11 @@
153153
# "archive-task": {
154154
# "task": "sensorsafrica.tasks.archive_data",
155155
# "schedule": crontab(hour="*", minute=0)
156-
# }
156+
# },
157+
"cache-lastactive-nodes-task": {
158+
"task": "sensorsafrica.tasks.cache_lastactive_nodes_data",
159+
"schedule": crontab(minute="*/5")
160+
},
157161
}
158162

159163

sensorsafrica/tasks.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ def calculate_data_statistics():
1010
@shared_task
1111
def archive_data():
1212
call_command("upload_to_ckan")
13+
14+
15+
@shared_task
16+
def cache_lastactive_nodes_data():
17+
call_command("cache_lastactive_nodes_data")

0 commit comments

Comments
 (0)