Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/setup update api v2 #138

Merged
merged 14 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SENSORSAFRICA_DATABASE_URL=""
SENSORSAFRICA_DEBUG=""
SENSORSAFRICA_FLOWER_ADMIN_PASSWORD=""
SENSORSAFRICA_FLOWER_ADMIN_USERNAME=""
SENSORSAFRICA_RABBITMQ_URL=""
SENSORSAFRICA_SENTRY_DSN=""
23 changes: 10 additions & 13 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,21 @@ FROM python:3.6.3

ENV PYTHONUNBUFFERED 1

# Create root directory for our project in the container
RUN mkdir /src

# Create application subdirectories
# Create application root directory
WORKDIR /src

RUN mkdir media static logs
VOLUME [ "/src/logs" ]

# Copy the current directory contents into the container at sensorsafrica
ADD . /src/

# Upgrade pip and setuptools
RUN pip install -q -U pip setuptools
# Upgrade pip and setuptools with trusted hosts
RUN python -m pip install --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade pip setuptools

# Install feinstaub from opendata-stuttgart
RUN pip install -q git+https://github.com/opendata-stuttgart/feinstaub-api
# Copy the current directory contents into the container at sensorsafrica
COPY . /src/
thepsalmist marked this conversation as resolved.
Show resolved Hide resolved

# Install sensors.AFRICA-api and its dependencies
RUN pip install -q -U .
# Upgrade pip and setuptools, install dependencies
RUN pip install -q git+https://github.com/opendata-stuttgart/feinstaub-api && \
pip install -q -U .
thepsalmist marked this conversation as resolved.
Show resolved Hide resolved

# Expose port server
EXPOSE 8000
Expand All @@ -31,3 +27,4 @@ COPY ./contrib/entrypoint.sh /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
CMD [ "/start.sh" ]

7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ API to save and access data from deployed sensors in cities all around Africa.

## Documentation

The API is documented [here.](https://github.com/CodeForAfricaLabs/sensors.AFRICA-api/wiki/API-Documentation)
The API is documented [here.](https://github.com/CodeForAfricaLabs/sensors.AFRICA-api/wiki/API-Documentation)

## Development

Expand All @@ -29,6 +29,11 @@ GRANT ALL PRIVILEGES ON DATABASE sensorsafrica TO sensorsafrica;

- Migrate the database; `python manage.py migrate`
- Run the server; `python manage.py runserver`
- Create super user for admin login; `python manage.py createsuperuser`

thepsalmist marked this conversation as resolved.
Show resolved Hide resolved
username: `<username>`
email: blank
password: `<password>`

### Docker

Expand Down
30 changes: 17 additions & 13 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ version: '3.3'

services:
rabbitmq:
image: rabbitmq:3.5.1
image: rabbitmq:3.12.7-management
ports:
- 4369:4369
- 5672:5672
- 25672:25672
- 15672:15672
- "5672:5672"
# GUI port
- "15672:15672"
environment:
- RABBITMQ_USERNAME=sensorsafrica
- RABBITMQ_PASSWORD=sensorsafrica
- RABBITMQ_DEFAULT_USER=sensorsafrica
- RABBITMQ_DEFAULT_PASS=sensorsafrica
healthcheck:
test: [ "CMD-SHELL", "rabbitmq-diagnostics -q ping" ]
interval: 10s
timeout: 5s
retries: 2

postgres:
image: postgres:13.7
ports:
Expand All @@ -25,12 +30,11 @@ services:
build:
context: .
environment:
SENSORSAFRICA_DATABASE_URL: postgres://sensorsafrica:sensorsafrica@postgres:5432/sensorsafrica
SENSORSAFRICA_READ_DATABASE_URLS: postgres://sensorsafrica:sensorsafrica@postgres:5432/sensorsafrica
SENSORSAFRICA_RABBITMQ_URL: amqp://sensorsafrica:sensorsafrica@rabbitmq//
SENSORSAFRICA_FLOWER_ADMIN_USERNAME: admin
SENSORSAFRICA_FLOWER_ADMIN_PASSWORD: password
DOKKU_APP_NAME: sensorsafrica
SENSORSAFRICA_DATABASE_URL: ${SENSORSAFRICA_DATABASE_URL:-postgres://sensorsafrica:sensorsafrica@postgres:5432/sensorsafrica}
SENSORSAFRICA_RABBITMQ_URL: ${SENSORSAFRICA_RABBITMQ_URL:-amqp://sensorsafrica:sensorsafrica@rabbitmq/}
SENSORSAFRICA_FLOWER_ADMIN_USERNAME: ${SENSORSAFRICA_FLOWER_ADMIN_USERNAME:-admin}
SENSORSAFRICA_FLOWER_ADMIN_PASSWORD: ${SENSORSAFRICA_FLOWER_ADMIN_PASSWORD:-password}
DOKKU_APP_NAME: ${DOKKU_APP_NAME:-sensorsafrica}
depends_on:
- postgres
- rabbitmq
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ckanapi==4.1

celery-slack==0.3.0

urllib3<1.25,>=1.21.1 #requests 2.21.0
urllib3<2

django-cors-headers==3.0.2

Expand Down
6 changes: 5 additions & 1 deletion sensorsafrica/api/v1/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from rest_framework import routers

router = routers.DefaultRouter()
thepsalmist marked this conversation as resolved.
Show resolved Hide resolved
router.register(r"push-sensor-data", PostSensorDataView)
router.register(r"node", NodeView)
router.register(r"sensor", SensorView)
router.register(r"data", VerboseSensorDataView)
Expand All @@ -31,3 +30,8 @@
router.register(r"filter", FilterView, basename="filter")

api_urls = router.urls

push_sensor_data_router = routers.DefaultRouter()
push_sensor_data_router.register(r"push-sensor-data", PostSensorDataView)

push_sensor_data_urls = push_sensor_data_router.urls
4 changes: 2 additions & 2 deletions sensorsafrica/api/v1/views.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is Django v >=1.10

Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class NodeView(
filter_class = NodeFilter

def get_queryset(self):
if self.request.user.is_authenticated():
if self.request.user.is_authenticated:
if self.request.user.groups.filter(name="show_me_everything").exists():
return Node.objects.all()

Expand Down Expand Up @@ -92,7 +92,7 @@ class PostSensorDataView(mixins.CreateModelMixin,
permission_classes = tuple()
serializer_class = LastNotifySensorDataSerializer
queryset = SensorData.objects.all()


class VerboseSensorDataView(SensorDataView):
filter_class = SensorFilter
Expand Down
42 changes: 14 additions & 28 deletions sensorsafrica/api/v2/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,28 @@
from .views import (
CitiesView,
NodesView,
NowView,
SensorDataStatsView,
SensorDataView,
SensorLocationsView,
SensorTypesView,
SensorsView,
StatisticsView,
meta_data,
)

stat_data_router = routers.DefaultRouter()
stat_data_router.register(r"", SensorDataStatsView)

data_router = routers.DefaultRouter()
data_router.register(r"", SensorDataView)

cities_router = routers.DefaultRouter()
cities_router.register(r"", CitiesView, basename="cities")

nodes_router = routers.DefaultRouter()
nodes_router.register(r"", NodesView, basename="map")

sensors_router = routers.DefaultRouter()
sensors_router.register(r"", SensorsView, basename="sensors")

sensor_locations_router = routers.DefaultRouter()
sensor_locations_router.register(r"", SensorLocationsView, basename="locations")

sensor_types_router = routers.DefaultRouter()
sensor_types_router.register(r"", SensorTypesView, basename="sensor_types")
router = routers.DefaultRouter()
router.register(r"data", SensorDataView, basename="sensor-data")
router.register(r"data/(?P<sensor_type>[air]+)", SensorDataStatsView, basename="sensor-data-stats")
router.register(r"cities", CitiesView, basename="cities")
router.register(r"nodes", NodesView, basename="nodes")
router.register(r"now", NowView, basename="now")
router.register(r"locations", SensorLocationsView, basename="sensor-locations")
router.register(r"sensors", SensorsView, basename="sensors")
router.register(r"sensor-types", SensorTypesView, basename="sensor-types")
router.register(r"statistics", StatisticsView, basename="statistics")

api_urls = [
url(r"data/(?P<sensor_type>[air]+)/", include(stat_data_router.urls)),
url(r"data/", include(data_router.urls)),
url(r"cities/", include(cities_router.urls)),
url(r"nodes/", include(nodes_router.urls)),
url(r"locations/", include(sensor_locations_router.urls)),
url(r"sensors/", include(sensors_router.urls)),
url(r"sensor-types/", include(sensor_types_router.urls)),
url(r"meta/", meta_data),
url(r"^", include(router.urls)),
url(r"^meta/", meta_data),
]
58 changes: 55 additions & 3 deletions sensorsafrica/api/v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.conf import settings
from django.utils import timezone
from django.db import connection
from django.db.models import ExpressionWrapper, F, FloatField, Max, Min, Sum, Avg, Q
from django.db.models import ExpressionWrapper, F, FloatField, Max, Min, Sum, Avg, Q, Count
from django.db.models.functions import Cast, TruncHour, TruncDay, TruncMonth
from django.utils.decorators import method_decorator
from django.utils.text import slugify
Expand All @@ -23,7 +23,7 @@
from rest_framework.decorators import api_view, authentication_classes

from feinstaub.sensors.views import SensorFilter, StandardResultsSetPagination

from feinstaub.sensors.serializers import NowSerializer
from feinstaub.sensors.models import (
Node,
Sensor,
Expand Down Expand Up @@ -248,7 +248,7 @@ class SensorDataView(
serializer_class = SensorDataSerializer

def get_queryset(self):
if self.request.user.is_authenticated():
if self.request.user.is_authenticated:
if self.request.user.groups.filter(name="show_me_everything").exists():
return SensorData.objects.all()

Expand Down Expand Up @@ -471,3 +471,55 @@ def get_database_last_updated():
sensor_data_value = SensorDataValue.objects.latest('created')
if sensor_data_value:
return sensor_data_value.modified


class NowView(mixins.ListModelMixin, viewsets.GenericViewSet):
"""Show all public sensors active in the last 5 minutes with newest value"""

authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [IsAuthenticated]
serializer_class = NowSerializer

def get_queryset(self):
now = timezone.now()
startdate = now - datetime.timedelta(minutes=5)
return SensorData.objects.filter(
sensor__public=True, modified__range=[startdate, now]
)


class StatisticsView(viewsets.ViewSet):
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [IsAuthenticated]

def list(self, request):
user_count = User.objects.aggregate(count=Count('id'))['count']
sensor_count = Sensor.objects.aggregate(count=Count('id'))['count']
sensor_data_count = SensorData.objects.aggregate(count=Count('id'))['count']
sensor_data_value_count = SensorDataValue.objects.aggregate(count=Count('id'))['count']
sensor_type_count = SensorType.objects.aggregate(count=Count('id'))['count']
sensor_type_list = list(SensorType.objects.order_by('uid').values_list('name', flat=True))
location_count = SensorLocation.objects.aggregate(count=Count('id'))['count']

stats = {
'user': {
'count': user_count,
},
'sensor': {
'count': sensor_count,
},
'sensor_data': {
'count': sensor_data_count,
},
'sensor_data_value': {
'count': sensor_data_value_count,
},
'sensor_type': {
'count': sensor_type_count,
'list': sensor_type_list,
},
'location': {
'count': location_count,
}
}
return Response(stats)
7 changes: 0 additions & 7 deletions sensorsafrica/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,6 @@

DATABASES = {"default": dj_database_url.parse(DATABASE_URL), }

READ_DATABASE_URLS = os.getenv("SENSORSAFRICA_READ_DATABASE_URLS", DATABASE_URL).split(",")

for index, read_database_url in enumerate(READ_DATABASE_URLS,start=1):
DATABASES[f"read_replica_{index}"] = dj_database_url.parse(read_database_url)

DATABASE_ROUTERS = ["sensorsafrica.router.ReplicaRouter", ]

# Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators

Expand Down
4 changes: 2 additions & 2 deletions sensorsafrica/urls.py
thepsalmist marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.documentation import include_docs_urls

from .api.v1.router import api_urls as sensors_api_v1
from .api.v1.router import push_sensor_data_urls
from .api.v2.router import api_urls as sensors_api_v2

urlpatterns = [
url(r"^$", RedirectView.as_view(url="/docs/", permanent=False)),
url(r"^admin/", admin.site.urls),
url(r"^v1/", include(sensors_api_v1)),
url(r"^v1/push-sensor-data/", include(push_sensor_data_urls)),
url(r"^v2/", include(sensors_api_v2)),
url(r"^get-auth-token/", obtain_auth_token),
url(r"^auth/", include("rest_framework.urls", namespace="rest_framework")),
Expand Down