From a821340ada02a74e66df706c90cd65d1ed6dcb73 Mon Sep 17 00:00:00 2001 From: Karim Kawambwa <4308339+karimkawambwa@users.noreply.github.com> Date: Thu, 21 Feb 2019 13:07:05 +0300 Subject: [PATCH] Release v2 fixes (#26) * add city and sensordata to admin * separate v1 and v2 * standardize pagination of results * case sensitive query * logs -> logst since logs might be reserverd * ensure list results stay as lists * refactor rename * plural name * reponse formatting --- Makefile | 4 +-- README.md | 2 +- sensorsafrica/{api => }/admin.py | 9 ++++- sensorsafrica/api/models.py | 3 ++ sensorsafrica/api/v2/router.py | 3 +- sensorsafrica/api/v2/views.py | 29 ++++++++-------- sensorsafrica/tests/test_city_view.py | 4 +-- .../tests/test_sensordatastats_view.py | 33 +++++++++++++++---- 8 files changed, 58 insertions(+), 29 deletions(-) rename sensorsafrica/{api => }/admin.py (81%) diff --git a/Makefile b/Makefile index 11d7d73..0b39255 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,8 @@ build: up: $(COMPOSE) up -d -logs: - $(COMPOSE) logs -f +log: + $(COMPOSE) logs -f compilescss: $(COMPOSE) exec api python manage.py compilescss diff --git a/README.md b/README.md index 9f51314..c4947fb 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Docker compose make commands: - `make build` - `make up` - run docker and detach -- `make logs` - tail logs +- `make log` - tail logs - `make test` - run test - `make migrate` - migrate database - `make createsuperuser` - create a super user for admin diff --git a/sensorsafrica/api/admin.py b/sensorsafrica/admin.py similarity index 81% rename from sensorsafrica/api/admin.py rename to sensorsafrica/admin.py index ee08d93..47f512e 100644 --- a/sensorsafrica/api/admin.py +++ b/sensorsafrica/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import SensorDataStat +from .api.models import SensorDataStat, City @admin.register(SensorDataStat) @@ -51,3 +51,10 @@ def delete_model(self, request, obj): def save_related(self, request, form, formsets, change): pass + + +@admin.register(City) +class CityAdmin(admin.ModelAdmin): + search_fields = ["slug", "name", "country"] + list_display = ["slug", "name", "country", "latitude", "longitude"] + list_filter = ["name", "country"] diff --git a/sensorsafrica/api/models.py b/sensorsafrica/api/models.py index 3986b2e..2594450 100644 --- a/sensorsafrica/api/models.py +++ b/sensorsafrica/api/models.py @@ -12,6 +12,9 @@ class City(TimeStampedModel): latitude = models.DecimalField(max_digits=14, decimal_places=11, null=True, blank=True) longitude = models.DecimalField(max_digits=14, decimal_places=11, null=True, blank=True) + class Meta: + verbose_name_plural = "Cities" + def save(self, *args, **kwargs): self.slug = slugify(self.name) return super(City, self).save(*args, **kwargs) diff --git a/sensorsafrica/api/v2/router.py b/sensorsafrica/api/v2/router.py index f2026b6..523a1d4 100644 --- a/sensorsafrica/api/v2/router.py +++ b/sensorsafrica/api/v2/router.py @@ -2,7 +2,6 @@ from django.conf.urls import url, include from .views import SensorDataStatView, CityView -from ..v1.router import api_urls data_router = routers.DefaultRouter() @@ -15,4 +14,4 @@ api_urls = [ url(r"data/(?P[air]+)/", include(data_router.urls)), url(r"cities/", include(city_router.urls)), -] + api_urls +] diff --git a/sensorsafrica/api/v2/views.py b/sensorsafrica/api/v2/views.py index 7d0de58..3138790 100644 --- a/sensorsafrica/api/v2/views.py +++ b/sensorsafrica/api/v2/views.py @@ -11,6 +11,8 @@ from ..models import SensorDataStat, City from .serializers import SensorDataStatSerializer, CitySerializer +from feinstaub.sensors.views import StandardResultsSetPagination + from rest_framework.response import Response value_types = {"air": ["P1", "P2", "humidity", "temperature"]} @@ -75,14 +77,12 @@ def get_paginated_response(self, data_stats): } ) - count = len(results.keys()) - values = list(results.values()) return Response( { "next": self.get_next_link(), "previous": self.get_previous_link(), - "count": count, - "results": values[0] if count == 1 else values, + "count": len(results.keys()), + "results": list(results.values()), } ) @@ -95,7 +95,7 @@ class SensorDataStatView(mixins.ListModelMixin, viewsets.GenericViewSet): def get_queryset(self): sensor_type = self.kwargs["sensor_type"] - city_slug = self.request.query_params.get("city", None) + city_slugs = self.request.query_params.get("city", None) from_date = self.request.query_params.get("from", None) to_date = self.request.query_params.get("to", None) @@ -110,17 +110,17 @@ def get_queryset(self): filter_value_types = value_types[sensor_type] if value_type_to_filter: - filter_value_types = set(value_type_to_filter.split(",")) & set( - value_types[sensor_type] + filter_value_types = set(value_type_to_filter.upper().split(",")) & set( + [x.upper() for x in value_types[sensor_type]] ) if not from_date and not to_date: - return self._retrieve_past_24hrs(city_slug, filter_value_types) + return self._retrieve_past_24hrs(city_slugs, filter_value_types) - return self._retrieve_range(from_date, to_date, city_slug, filter_value_types) + return self._retrieve_range(from_date, to_date, city_slugs, filter_value_types) @staticmethod - def _retrieve_past_24hrs(city_slug, filter_value_types): + def _retrieve_past_24hrs(city_slugs, filter_value_types): to_date = timezone.now().replace(minute=0, second=0, microsecond=0) from_date = to_date - datetime.timedelta(hours=24) @@ -130,8 +130,8 @@ def _retrieve_past_24hrs(city_slug, filter_value_types): timestamp__lte=to_date, ) - if city_slug: - queryset = queryset.filter(city_slug=city_slug) + if city_slugs: + queryset = queryset.filter(city_slug__in=city_slugs.split(',')) return ( queryset.order_by() @@ -150,7 +150,7 @@ def _retrieve_past_24hrs(city_slug, filter_value_types): ) @staticmethod - def _retrieve_range(from_date, to_date, city_slug, filter_value_types): + def _retrieve_range(from_date, to_date, city_slugs, filter_value_types): if not to_date: from_date = beginning_of_day(from_date) to_date = end_of_today() @@ -160,7 +160,7 @@ def _retrieve_range(from_date, to_date, city_slug, filter_value_types): return ( SensorDataStat.objects.filter( - city_slug=city_slug, + city_slug__in=city_slugs.split(','), value_type__in=filter_value_types, timestamp__gte=from_date, timestamp__lt=to_date, @@ -185,3 +185,4 @@ def _retrieve_range(from_date, to_date, city_slug, filter_value_types): class CityView(mixins.ListModelMixin, viewsets.GenericViewSet): queryset = City.objects.all() serializer_class = CitySerializer + pagination_class = StandardResultsSetPagination diff --git a/sensorsafrica/tests/test_city_view.py b/sensorsafrica/tests/test_city_view.py index 1d7f131..8f15470 100644 --- a/sensorsafrica/tests/test_city_view.py +++ b/sensorsafrica/tests/test_city_view.py @@ -9,7 +9,7 @@ def test_getting_cities(self, client, sensorsdatastats): data = response.json() - assert len(data) == 3 + assert data["count"] == 3 assert { "latitude": "-6.79240000000", @@ -18,4 +18,4 @@ def test_getting_cities(self, client, sensorsdatastats): "name": "Dar es Salaam", "country": "Tanzania", "label": "Dar es Salaam, Tanzania", - } in data + } in data["results"] diff --git a/sensorsafrica/tests/test_sensordatastats_view.py b/sensorsafrica/tests/test_sensordatastats_view.py index 4ada06b..d521f14 100644 --- a/sensorsafrica/tests/test_sensordatastats_view.py +++ b/sensorsafrica/tests/test_sensordatastats_view.py @@ -14,7 +14,7 @@ def test_getting_air_data_now(self, client, sensorsdatastats): assert data["count"] == 1 - result = data["results"] + result = data["results"][0] assert "P1" in result assert result["P1"]["average"] == 0.0 @@ -48,6 +48,22 @@ def test_getting_air_data_now_all_cities(self, client, sensorsdatastats): assert "P2" in results[1] assert results[2]["city_slug"] == "nairobi" + def test_getting_air_data_now_filter_cities(self, client, sensorsdatastats): + response = client.get("/v2/data/air/?city=dar-es-salaam,bagamoyo", format="json") + assert response.status_code == 200 + + data = response.json() + + assert data["count"] == 2 + + results = data["results"] + + assert results[0]["city_slug"] == "bagamoyo" + assert results[1]["city_slug"] == "dar-es-salaam" + assert "P1" in results[1] + assert results[1]["city_slug"] == "dar-es-salaam" + assert "P2" in results[1] + def test_getting_air_data_value_type(self, client, sensorsdatastats): response = client.get( "/v2/data/air/?city=dar-es-salaam&value_type=P2", format="json" @@ -57,7 +73,10 @@ def test_getting_air_data_value_type(self, client, sensorsdatastats): data = response.json() assert data["count"] == 1 - assert "P2" in data["results"] + assert "P2" in data["results"][0] + assert "P1" not in data["results"][0] + assert "temperature" not in data["results"][0] + assert "humidity" not in data["results"][0] def test_getting_air_data_from_date(self, client, sensorsdatastats): response = client.get( @@ -69,8 +88,8 @@ def test_getting_air_data_from_date(self, client, sensorsdatastats): data = response.json() - assert type(data["results"]["P1"]) == list - assert type(data["results"]["P2"]) == list + assert type(data["results"][0]["P1"]) == list + assert type(data["results"][0]["P2"]) == list def test_getting_air_data_from_date_to_date(self, client, sensorsdatastats): now = timezone.now() @@ -83,8 +102,8 @@ def test_getting_air_data_from_date_to_date(self, client, sensorsdatastats): data = response.json() assert data["count"] == 1 - assert type(data["results"]["P1"]) == list - assert type(data["results"]["P2"]) == list + assert type(data["results"][0]["P1"]) == list + assert type(data["results"][0]["P2"]) == list def test_getting_air_data_with_invalid_request(self, client, sensorsdatastats): response = client.get( @@ -123,7 +142,7 @@ def test_getting_air_data_now_with_additional_values( assert data["count"] == 1 - result = data["results"] + result = data["results"][0] assert "P1" in result assert result["P1"]["average"] == 0.0