diff --git a/.travis.yml b/.travis.yml index 8236818..0d42737 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,6 +42,11 @@ jobs: - docker-compose -f docker/docker-compose.yml up -d script: - docker-compose -f docker/docker-compose.yml exec bite python manage.py test + - stage: django + install: + - pip3 -q install flake8 + script: + - flake8 bite --exclude migrations,settings.py - <<: *iot-simulator env: IOT_TL=http - <<: *iot-simulator diff --git a/bite/api/views.py b/bite/api/views.py index 77c2722..4c3bae7 100644 --- a/bite/api/views.py +++ b/bite/api/views.py @@ -26,16 +26,3 @@ from api.serializers import DeviceSerializer class APISubscribe(ModelViewSet): queryset = Device.objects.all() serializer_class = DeviceSerializer - - # def post(self, request): - # serializer = DeviceSerializer(data=request.data) - # if serializer.is_valid(): - # serializer.save() - # return Response(serializer.data, status=status.HTTP_201_CREATED) - # return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - # def get(self, request): - # devices = Device.objects.all() - # import pdb; pdb.set_trace() - # serializer = DeviceSerializer(devices) - # return Response(serializer.serial) diff --git a/bite/bite/templates/swagger.html b/bite/bite/templates/swagger.html new file mode 100644 index 0000000..b421d7b --- /dev/null +++ b/bite/bite/templates/swagger.html @@ -0,0 +1,24 @@ + + + + Swagger + + + + + +
+ + + + diff --git a/bite/bite/urls.py b/bite/bite/urls.py index 61d2561..ba76354 100644 --- a/bite/bite/urls.py +++ b/bite/bite/urls.py @@ -34,6 +34,7 @@ Including another URLconf """ from django.contrib import admin +from django.conf import settings from django.urls import include, path from api import urls as api_urls @@ -45,3 +46,19 @@ urlpatterns = [ path('api/', include(api_urls)), path('telemetry/', include(telemetry_urls)), ] + +if settings.DEBUG: + from django.views.generic import TemplateView + from rest_framework.schemas import get_schema_view + + urlpatterns += [ + path('swagger/', TemplateView.as_view( + template_name='swagger.html', + extra_context={'schema_url': 'openapi-schema'} + ), name='swagger'), + path('openapi', get_schema_view( + title="BITE - A Basic/IoT/Example", + description="BITE API for IoT", + version="1.0.0" + ), name='openapi-schema'), + ] diff --git a/bite/telemetry/serializers.py b/bite/telemetry/serializers.py index c12fb9c..1ef6c13 100644 --- a/bite/telemetry/serializers.py +++ b/bite/telemetry/serializers.py @@ -22,6 +22,17 @@ from api.models import Device from telemetry.models import Telemetry +class TelemetryStatsSerializer(serializers.Serializer): + count_samples = serializers.IntegerField() + first_sample = serializers.DateTimeField() + last_sample = serializers.DateTimeField() + + +class TelemetrySummarySerializer(serializers.Serializer): + device = serializers.CharField() + stats = TelemetryStatsSerializer() + + class TelemetrySerializer(serializers.ModelSerializer): device = serializers.SlugRelatedField( slug_field='serial', diff --git a/bite/telemetry/urls.py b/bite/telemetry/urls.py index a1889c3..ce36725 100644 --- a/bite/telemetry/urls.py +++ b/bite/telemetry/urls.py @@ -33,14 +33,15 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.urls import path -from telemetry.views import TelemetryView, TelemetryLatest, TelemetryRange +from telemetry.views import (TelemetryView, TelemetrySummaryView, + TelemetryLatest, TelemetryRange) urlpatterns = [ path('', TelemetryView.as_view({'post': 'create'}), name='telemetry'), path('/', - TelemetryView.as_view({'get': 'list'}), + TelemetrySummaryView.as_view(), name='device-telemetry'), path('/last/', TelemetryLatest.as_view({'get': 'retrieve'}), diff --git a/bite/telemetry/views.py b/bite/telemetry/views.py index fc10bba..9abb7e3 100644 --- a/bite/telemetry/views.py +++ b/bite/telemetry/views.py @@ -19,10 +19,12 @@ from datetime import datetime from django.http import Http404 +from rest_framework.views import APIView from rest_framework.viewsets import ModelViewSet from telemetry.models import Telemetry -from telemetry.serializers import TelemetrySerializer +from telemetry.serializers import (TelemetrySerializer, + TelemetrySummarySerializer) from rest_framework.response import Response @@ -39,15 +41,36 @@ class TelemetryView(ModelViewSet): return Response(serializer.data) +class TelemetrySummaryView(APIView): + def get(self, request, device, format=None): + count = Telemetry.objects.filter(device__serial=device).count() + if count == 0: + raise Http404 + first = Telemetry.objects.filter( + device__serial=device).order_by('-time')[:1][0] + last = Telemetry.objects.filter( + device__serial=device).order_by('time')[:1][0] + data = { + 'device': device, + 'stats': { + 'count_samples': count, + 'first_sample': first.time, + 'last_sample': last.time} + } + serializer = TelemetrySummarySerializer(data) + return Response(serializer.data) + + class TelemetryRange(ModelViewSet): queryset = Telemetry.objects.all() serializer_class = TelemetrySerializer lookup_field = 'device' def list(self, request, device, time_from, time_to=None): + time_to = datetime.now() if time_to is None else time_to queryset = Telemetry.objects.filter( device__serial=device, - time__range=[time_from, datetime.now()]) + time__range=[time_from, time_to]) if not queryset: raise Http404 serializer = TelemetrySerializer(queryset, many=True) @@ -61,7 +84,7 @@ class TelemetryLatest(ModelViewSet): def retrieve(self, request, device=None): queryset = Telemetry.objects.filter( - device__serial=device).order_by('-time') + device__serial=device).order_by('-time')[:1] if not queryset: raise Http404 serializer = TelemetrySerializer(queryset[0]) diff --git a/docker/django/Dockerfile b/docker/django/Dockerfile index 346f59c..bf423fb 100644 --- a/docker/django/Dockerfile +++ b/docker/django/Dockerfile @@ -35,6 +35,7 @@ COPY --chown=1000:1000 bite /srv/app/bite COPY --chown=1000:1000 requirements.txt /srv/app/bite/requirements.txt RUN pip3 install -r /srv/app/bite/requirements.txt \ + && python3 -m compileall -q /srv/app/bite \ && mkdir -p /srv/appdata/bite/static \ && chown -R 1000:1000 /srv/appdata/bite diff --git a/docker/ingress/nginx.conf b/docker/ingress/nginx.conf index ab6f103..3e9674c 100644 --- a/docker/ingress/nginx.conf +++ b/docker/ingress/nginx.conf @@ -48,13 +48,13 @@ http { '' close; } - upstream bite { + upstream django { # We point to the Docker 'service' instead of directly to the container # Docker does then a DNS round-robin internally server bite:8000; } - upstream broker { + upstream mqtt { # We point to the Docker 'service' instead of directly to the container # Docker does then a DNS round-robin internally server broker:9001; @@ -69,7 +69,7 @@ http { keepalive_timeout 60 60; location / { - proxy_pass http://bite; + proxy_pass http://django; proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; @@ -85,7 +85,7 @@ http { } location /mqtt { - proxy_pass http://broker; + proxy_pass http://mqtt; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; diff --git a/requirements.txt b/requirements.txt index f2c9891..2b4c38d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,7 @@ django-health-check psycopg2-binary paho-mqtt==1.5.0 asyncio-mqtt==0.5.0 +PyYAML +uritemplate pygments gunicorn