From 9456b38ee3d2229fe529e0c7238d7d2a2b5f9546 Mon Sep 17 00:00:00 2001 From: khadija Date: Fri, 29 Jan 2021 15:56:00 +0300 Subject: [PATCH 01/10] update node last_notify --- sensorsafrica/api/v1/serializers.py | 60 +++++++++++++++++++++++++---- sensorsafrica/api/v1/views.py | 13 ++++++- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/sensorsafrica/api/v1/serializers.py b/sensorsafrica/api/v1/serializers.py index 252d45d..1d6124a 100644 --- a/sensorsafrica/api/v1/serializers.py +++ b/sensorsafrica/api/v1/serializers.py @@ -4,20 +4,15 @@ SensorDataValue, SensorLocation ) - +from feinstaub.sensors.serializers import ( + SensorDataValueSerializer +) class SensorLocationSerializer(serializers.ModelSerializer): class Meta: model = SensorLocation fields = '__all__' - -class SensorDataValueSerializer(serializers.ModelSerializer): - class Meta: - model = SensorDataValue - fields = ['value_type', 'value'] - - class SensorDataSerializer(serializers.ModelSerializer): sensordatavalues = SensorDataValueSerializer(many=True) location = SensorLocationSerializer() @@ -25,3 +20,52 @@ class SensorDataSerializer(serializers.ModelSerializer): class Meta: model = SensorData fields = ['location', 'timestamp', 'sensordatavalues'] + +class PostSensorDataSerializer(serializers.ModelSerializer): + sensordatavalues = SensorDataValueSerializer(many=True) + sensor = serializers.IntegerField(required=False, + source='sensor.pk') + + class Meta: + model = SensorData + fields = ('sensor', 'sampling_rate', 'timestamp', 'sensordatavalues', 'software_version') + read_only = ('location') + + def create(self, validated_data): + # custom create, because of nested list of sensordatavalues + + sensordatavalues = validated_data.pop('sensordatavalues', []) + if not sensordatavalues: + raise exceptions.ValidationError('sensordatavalues was empty. Nothing to save.') + + # use sensor from authenticator + successful_authenticator = self.context['request'].successful_authenticator + if not successful_authenticator: + raise exceptions.NotAuthenticated + + node, pin = successful_authenticator.authenticate(self.context['request']) + if node.sensors.count() == 1: + sensors_qs = node.sensors.all() + else: + sensors_qs = node.sensors.filter(pin=pin) + sensor_id = sensors_qs.values_list('pk', flat=True).first() + + if not sensor_id: + raise exceptions.ValidationError('sensor could not be selected.') + validated_data['sensor_id'] = sensor_id + + # set location based on current location of sensor + validated_data['location'] = node.location + sd = SensorData.objects.create(**validated_data) + + SensorDataValue.objects.bulk_create( + SensorDataValue( + sensordata_id=sd.pk, + **value, + ) + for value in sensordatavalues + ) + node.last_notify = sd.timestamp + node.save() + + return sd diff --git a/sensorsafrica/api/v1/views.py b/sensorsafrica/api/v1/views.py index 53d9921..83fce62 100644 --- a/sensorsafrica/api/v1/views.py +++ b/sensorsafrica/api/v1/views.py @@ -16,8 +16,9 @@ from feinstaub.sensors.models import Node, SensorData from feinstaub.sensors.serializers import NodeSerializer, NowSerializer from feinstaub.sensors.views import StandardResultsSetPagination +from feinstaub.sensors.authentication import NodeUidAuthentication -from .serializers import SensorDataSerializer +from .serializers import SensorDataSerializer, PostSensorDataSerializer class FilterView(mixins.ListModelMixin, viewsets.GenericViewSet): @@ -81,6 +82,15 @@ def get_queryset(self): sensor__public=True, modified__range=[startdate, now] ) +class PostSensorDataView(mixins.CreateModelMixin, + viewsets.GenericViewSet): + """ This endpoint is to POST data from the sensor to the api. + """ + authentication_classes = (NodeUidAuthentication,) + permission_classes = tuple() + serializer_class = PostSensorDataSerializer + queryset = SensorData.objects.all() + class SensorDataView(mixins.ListModelMixin, viewsets.GenericViewSet): serializer_class = SensorDataSerializer @@ -94,3 +104,4 @@ def get_queryset(self): .only("sensor", "timestamp") .prefetch_related("sensordatavalues") ) + From d5c716fa2cce277c110ba798577311acfd166d59 Mon Sep 17 00:00:00 2001 From: khadija Date: Fri, 29 Jan 2021 17:00:19 +0300 Subject: [PATCH 02/10] update nodeserializer --- sensorsafrica/api/v1/router.py | 2 +- sensorsafrica/api/v1/serializers.py | 8 ++++++++ sensorsafrica/api/v1/views.py | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/sensorsafrica/api/v1/router.py b/sensorsafrica/api/v1/router.py index 88901a3..b90b3b0 100644 --- a/sensorsafrica/api/v1/router.py +++ b/sensorsafrica/api/v1/router.py @@ -1,7 +1,6 @@ # The base version is entirely based on feinstaub from feinstaub.main.views import UsersView from feinstaub.sensors.views import ( - PostSensorDataView, SensorView, StatisticsView, SensorDataView, @@ -11,6 +10,7 @@ FilterView, NodeView, NowView, + PostSensorDataView, SensorDataView as SensorsAfricaSensorDataView, ) diff --git a/sensorsafrica/api/v1/serializers.py b/sensorsafrica/api/v1/serializers.py index 1d6124a..feaae97 100644 --- a/sensorsafrica/api/v1/serializers.py +++ b/sensorsafrica/api/v1/serializers.py @@ -8,6 +8,14 @@ SensorDataValueSerializer ) +class NodeSerializer(serializers.ModelSerializer): + sensors = NestedSensorSerializer(many=True) + location = NestedSensorLocationSerializer() + + class Meta: + model = Node + fields = ('id', 'sensors', 'uid', 'owner', 'location', 'last_notify') + class SensorLocationSerializer(serializers.ModelSerializer): class Meta: model = SensorLocation diff --git a/sensorsafrica/api/v1/views.py b/sensorsafrica/api/v1/views.py index 83fce62..13b3ef7 100644 --- a/sensorsafrica/api/v1/views.py +++ b/sensorsafrica/api/v1/views.py @@ -14,11 +14,11 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly from feinstaub.sensors.models import Node, SensorData -from feinstaub.sensors.serializers import NodeSerializer, NowSerializer +from feinstaub.sensors.serializers import NowSerializer from feinstaub.sensors.views import StandardResultsSetPagination from feinstaub.sensors.authentication import NodeUidAuthentication -from .serializers import SensorDataSerializer, PostSensorDataSerializer +from .serializers import NodeSerializer, SensorDataSerializer, PostSensorDataSerializer class FilterView(mixins.ListModelMixin, viewsets.GenericViewSet): From 7c3861dbf2150ece3367c829fa17e02c061625e3 Mon Sep 17 00:00:00 2001 From: khadija Date: Fri, 29 Jan 2021 17:03:49 +0300 Subject: [PATCH 03/10] import from feinstaub --- sensorsafrica/api/v1/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sensorsafrica/api/v1/serializers.py b/sensorsafrica/api/v1/serializers.py index feaae97..ce2eeb1 100644 --- a/sensorsafrica/api/v1/serializers.py +++ b/sensorsafrica/api/v1/serializers.py @@ -1,10 +1,13 @@ from rest_framework import serializers from feinstaub.sensors.models import ( + Node, SensorData, SensorDataValue, SensorLocation ) from feinstaub.sensors.serializers import ( + NestedSensorLocationSerializer, + NestedSensorSerializer, SensorDataValueSerializer ) From 1a0dd5acd92273e07ef421682b10ec3cc278123f Mon Sep 17 00:00:00 2001 From: khadija Date: Fri, 29 Jan 2021 17:21:55 +0300 Subject: [PATCH 04/10] class naming --- sensorsafrica/api/v1/serializers.py | 10 +++++++--- sensorsafrica/api/v1/views.py | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/sensorsafrica/api/v1/serializers.py b/sensorsafrica/api/v1/serializers.py index ce2eeb1..539f2ba 100644 --- a/sensorsafrica/api/v1/serializers.py +++ b/sensorsafrica/api/v1/serializers.py @@ -7,8 +7,7 @@ ) from feinstaub.sensors.serializers import ( NestedSensorLocationSerializer, - NestedSensorSerializer, - SensorDataValueSerializer + NestedSensorSerializer ) class NodeSerializer(serializers.ModelSerializer): @@ -24,6 +23,11 @@ class Meta: model = SensorLocation fields = '__all__' +class SensorDataValueSerializer(serializers.ModelSerializer): + class Meta: + model = SensorDataValue + fields = ['value_type', 'value'] + class SensorDataSerializer(serializers.ModelSerializer): sensordatavalues = SensorDataValueSerializer(many=True) location = SensorLocationSerializer() @@ -32,7 +36,7 @@ class Meta: model = SensorData fields = ['location', 'timestamp', 'sensordatavalues'] -class PostSensorDataSerializer(serializers.ModelSerializer): +class VerboseSensorDataSerializer(serializers.ModelSerializer): sensordatavalues = SensorDataValueSerializer(many=True) sensor = serializers.IntegerField(required=False, source='sensor.pk') diff --git a/sensorsafrica/api/v1/views.py b/sensorsafrica/api/v1/views.py index 13b3ef7..7e52284 100644 --- a/sensorsafrica/api/v1/views.py +++ b/sensorsafrica/api/v1/views.py @@ -18,7 +18,7 @@ from feinstaub.sensors.views import StandardResultsSetPagination from feinstaub.sensors.authentication import NodeUidAuthentication -from .serializers import NodeSerializer, SensorDataSerializer, PostSensorDataSerializer +from .serializers import NodeSerializer, SensorDataSerializer, VerboseSensorDataSerializer class FilterView(mixins.ListModelMixin, viewsets.GenericViewSet): @@ -88,7 +88,7 @@ class PostSensorDataView(mixins.CreateModelMixin, """ authentication_classes = (NodeUidAuthentication,) permission_classes = tuple() - serializer_class = PostSensorDataSerializer + serializer_class = VerboseSensorDataSerializer queryset = SensorData.objects.all() From ab92a504def04377c2ca19e4fc3b4bc94fe1833a Mon Sep 17 00:00:00 2001 From: khadija Date: Fri, 29 Jan 2021 19:55:59 +0300 Subject: [PATCH 05/10] code refactor --- sensorsafrica/api/v1/serializers.py | 45 +++++------------------------ 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/sensorsafrica/api/v1/serializers.py b/sensorsafrica/api/v1/serializers.py index 539f2ba..a4424dd 100644 --- a/sensorsafrica/api/v1/serializers.py +++ b/sensorsafrica/api/v1/serializers.py @@ -7,7 +7,8 @@ ) from feinstaub.sensors.serializers import ( NestedSensorLocationSerializer, - NestedSensorSerializer + NestedSensorSerializer, + SensorDataSerializer as PostSensorDataSerializer ) class NodeSerializer(serializers.ModelSerializer): @@ -23,11 +24,13 @@ class Meta: model = SensorLocation fields = '__all__' + class SensorDataValueSerializer(serializers.ModelSerializer): class Meta: model = SensorDataValue fields = ['value_type', 'value'] + class SensorDataSerializer(serializers.ModelSerializer): sensordatavalues = SensorDataValueSerializer(many=True) location = SensorLocationSerializer() @@ -36,50 +39,16 @@ class Meta: model = SensorData fields = ['location', 'timestamp', 'sensordatavalues'] -class VerboseSensorDataSerializer(serializers.ModelSerializer): - sensordatavalues = SensorDataValueSerializer(many=True) - sensor = serializers.IntegerField(required=False, - source='sensor.pk') - - class Meta: - model = SensorData - fields = ('sensor', 'sampling_rate', 'timestamp', 'sensordatavalues', 'software_version') - read_only = ('location') +class VerboseSensorDataSerializer(PostSensorDataSerializer): def create(self, validated_data): - # custom create, because of nested list of sensordatavalues - - sensordatavalues = validated_data.pop('sensordatavalues', []) - if not sensordatavalues: - raise exceptions.ValidationError('sensordatavalues was empty. Nothing to save.') - - # use sensor from authenticator + sd = super().create(validated_data) + # use sensor from authenticator successful_authenticator = self.context['request'].successful_authenticator if not successful_authenticator: raise exceptions.NotAuthenticated node, pin = successful_authenticator.authenticate(self.context['request']) - if node.sensors.count() == 1: - sensors_qs = node.sensors.all() - else: - sensors_qs = node.sensors.filter(pin=pin) - sensor_id = sensors_qs.values_list('pk', flat=True).first() - - if not sensor_id: - raise exceptions.ValidationError('sensor could not be selected.') - validated_data['sensor_id'] = sensor_id - - # set location based on current location of sensor - validated_data['location'] = node.location - sd = SensorData.objects.create(**validated_data) - - SensorDataValue.objects.bulk_create( - SensorDataValue( - sensordata_id=sd.pk, - **value, - ) - for value in sensordatavalues - ) node.last_notify = sd.timestamp node.save() From 7e7572064b8ec9085aaf7856214aa02f8e0f214d Mon Sep 17 00:00:00 2001 From: khadija Date: Fri, 29 Jan 2021 20:00:04 +0300 Subject: [PATCH 06/10] remove repeating line --- sensorsafrica/api/v1/serializers.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/sensorsafrica/api/v1/serializers.py b/sensorsafrica/api/v1/serializers.py index a4424dd..aa143a7 100644 --- a/sensorsafrica/api/v1/serializers.py +++ b/sensorsafrica/api/v1/serializers.py @@ -45,9 +45,6 @@ def create(self, validated_data): sd = super().create(validated_data) # use sensor from authenticator successful_authenticator = self.context['request'].successful_authenticator - if not successful_authenticator: - raise exceptions.NotAuthenticated - node, pin = successful_authenticator.authenticate(self.context['request']) node.last_notify = sd.timestamp node.save() From 039516f3cd4d7e43da9ff9c84246b0ba65a09bed Mon Sep 17 00:00:00 2001 From: khadija Date: Fri, 29 Jan 2021 20:00:50 +0300 Subject: [PATCH 07/10] comment line --- sensorsafrica/api/v1/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensorsafrica/api/v1/serializers.py b/sensorsafrica/api/v1/serializers.py index aa143a7..020c562 100644 --- a/sensorsafrica/api/v1/serializers.py +++ b/sensorsafrica/api/v1/serializers.py @@ -43,7 +43,7 @@ class VerboseSensorDataSerializer(PostSensorDataSerializer): def create(self, validated_data): sd = super().create(validated_data) - # use sensor from authenticator + # use node from authenticator successful_authenticator = self.context['request'].successful_authenticator node, pin = successful_authenticator.authenticate(self.context['request']) node.last_notify = sd.timestamp From d2b74d157b6d5830e15559e6f7192051367a5735 Mon Sep 17 00:00:00 2001 From: Khadija Mahanga Date: Mon, 1 Feb 2021 11:20:04 +0300 Subject: [PATCH 08/10] Update sensorsafrica/api/v1/serializers.py Co-authored-by: _ Kilemensi --- sensorsafrica/api/v1/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensorsafrica/api/v1/serializers.py b/sensorsafrica/api/v1/serializers.py index 020c562..d97187d 100644 --- a/sensorsafrica/api/v1/serializers.py +++ b/sensorsafrica/api/v1/serializers.py @@ -39,7 +39,7 @@ class Meta: model = SensorData fields = ['location', 'timestamp', 'sensordatavalues'] -class VerboseSensorDataSerializer(PostSensorDataSerializer): +class LastNotifySensorDataSerializer(PostSensorDataSerializer): def create(self, validated_data): sd = super().create(validated_data) From 89aebf28bfdd5785665daf63666b5000dad3198c Mon Sep 17 00:00:00 2001 From: khadija Date: Mon, 1 Feb 2021 11:35:49 +0300 Subject: [PATCH 09/10] rename verbose to lastnotify --- sensorsafrica/api/v1/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sensorsafrica/api/v1/views.py b/sensorsafrica/api/v1/views.py index 7e52284..6f18475 100644 --- a/sensorsafrica/api/v1/views.py +++ b/sensorsafrica/api/v1/views.py @@ -18,7 +18,7 @@ from feinstaub.sensors.views import StandardResultsSetPagination from feinstaub.sensors.authentication import NodeUidAuthentication -from .serializers import NodeSerializer, SensorDataSerializer, VerboseSensorDataSerializer +from .serializers import LastNotifySensorDataSerializer, NodeSerializer, SensorDataSerializer class FilterView(mixins.ListModelMixin, viewsets.GenericViewSet): @@ -88,7 +88,7 @@ class PostSensorDataView(mixins.CreateModelMixin, """ authentication_classes = (NodeUidAuthentication,) permission_classes = tuple() - serializer_class = VerboseSensorDataSerializer + serializer_class = LastNotifySensorDataSerializer queryset = SensorData.objects.all() From 9f0cf2eb0b072a4f6ddd3d6ea98df5dd670cd695 Mon Sep 17 00:00:00 2001 From: khadija Date: Mon, 1 Feb 2021 12:10:24 +0300 Subject: [PATCH 10/10] update only when current notify is less than sensordata timestamp --- sensorsafrica/api/v1/serializers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sensorsafrica/api/v1/serializers.py b/sensorsafrica/api/v1/serializers.py index d97187d..1aba22d 100644 --- a/sensorsafrica/api/v1/serializers.py +++ b/sensorsafrica/api/v1/serializers.py @@ -46,7 +46,11 @@ def create(self, validated_data): # use node from authenticator successful_authenticator = self.context['request'].successful_authenticator node, pin = successful_authenticator.authenticate(self.context['request']) - node.last_notify = sd.timestamp - node.save() + + #sometimes we post historical data (eg: from other network) + #this means we have to update last_notify only if current timestamp is greater than what's there + if node.last_notify is None or node.last_notify < sd.timestamp: + node.last_notify = sd.timestamp + node.save() return sd