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

Bug fix/modules dependencies #132

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,7 @@ celerybeat.pid
logs/

/static


#folder in project directory to store temporary files or backup files locally
temp/
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.6.3
FROM python:3.9.7

ENV PYTHONUNBUFFERED 1

Expand All @@ -17,7 +17,7 @@ ADD . /src/
RUN pip install -q -U pip setuptools

# Install feinstaub from opendata-stuttgart
RUN pip install -q git+https://github.com/opendata-stuttgart/feinstaub-api
# RUN pip install -q git+https://github.com/opendata-stuttgart/feinstaub-api

# Install sensors.AFRICA-api and its dependencies
RUN pip install -q -U .
Expand Down
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,46 @@ Gitignore is standardized for this project using [gitignore.io](https://www.giti
To get the project up and running:

- Clone this repo

-
### Prerequisites

- Have [PostgreSQL](https://www.postgresql.org/download/) installed
- Have **psql** installed - a command line tool to interact with PostgreSQL. To install, follow instructions as shown [here](https://www.timescale.com/blog/how-to-install-psql-on-mac-ubuntu-debian-windows/).
- Have a Python version 3.9.7 installed in your system. If you are using a different Python version you could use a tool like [Pyenv](https://github.com/pyenv/pyenv) to manage different versions
### Virtual environment

- Use virtualenv to create your virtual environment; `virtualenv venv`
- Use virtualenv to create your virtual environment; `python -m venv venv` or `virtualenv venv`
- Activate the virtual environment; `source venv/bin/activate`
- Install feinstaub; `pip install git+https://github.com/opendata-stuttgart/feinstaub-api`
- Install the requirements; `pip install .`
- Create a sensorsafrica database with the following sql script:
- ~~Install feinstaub; `pip install git+https://github.com/opendata-stuttgart/feinstaub-api`~~ issues detected. Feinstaub sensors app manually include in project directory
- ~~Install the requirements; `pip install .`~~ Dependency hell with older conflicting module versions.
- Run the below commands in the virtual environment to install depencencies. Note : latest versions will be installed
```bash

pip install —upgrade pip
pip install —upgrade setuptools

pip install django django-cors-headers django-filter djangorestframework coreapi celery celery_slack python-dateutil timeago psycopg2-binary dj_database_url sentry_sdk django_extensions whitenoise

```

### Database setup
- Create a sensorsafrica database open your terminal and hit `psql postgres`, then run following sql script:

```sql
CREATE DATABASE sensorsafrica;
CREATE USER sensorsafrica WITH ENCRYPTED PASSWORD 'sensorsafrica';
GRANT ALL PRIVILEGES ON DATABASE sensorsafrica TO sensorsafrica;
```
### Running the app

Still in your virtual enviroment, run the following:

- Migrate the database; `python manage.py migrate`
- Run the server; `python manage.py runserver`


---

### Docker

Using docker compose:
Expand Down
File renamed without changes.
18 changes: 18 additions & 0 deletions feinstaub/main/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.contrib import admin

from .models import UserProfile


class UserProfileInline(admin.StackedInline):
model = UserProfile
can_delete = False


class UserAdmin(UserAdmin):
inlines = (UserProfileInline, )


admin.site.unregister(User)
admin.site.register(User, UserAdmin)
33 changes: 33 additions & 0 deletions feinstaub/main/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models
import django_extensions.db.fields
from django.conf import settings


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)),
('created', django_extensions.db.fields.CreationDateTimeField(verbose_name='created', auto_now_add=True)),
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
('notification_type', models.CharField(max_length=100, choices=[('none', 'no notification'), ('email', 'email'), ('pushover', 'pushover'), ('notifymyandroid', 'notifymyandroid')])),
('pushover_clientkey', models.CharField(max_length=100, blank=True, default='')),
('notifymyandroid_apikey', models.CharField(max_length=100, blank=True, default='')),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='profile',on_delete=models.CASCADE)),
],
options={
'ordering': ('-modified', '-created'),
'get_latest_by': 'modified',
'abstract': False,
},
),
]
16 changes: 16 additions & 0 deletions feinstaub/main/migrations/0002_alter_userprofile_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 4.2.1 on 2023-05-28 11:00

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("main", "0001_initial"),
]

operations = [
migrations.AlterModelOptions(
name="userprofile", options={"get_latest_by": "modified"},
),
]
18 changes: 18 additions & 0 deletions feinstaub/main/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib.auth.models import User
from django.db import models
from django_extensions.db.models import TimeStampedModel


class UserProfile(TimeStampedModel):
NOTIFICATION_TYPE_CHOICES = (('none', 'no notification'),
('email', 'email'),
('pushover', 'pushover'),
('notifymyandroid', 'notifymyandroid'),)

user = models.OneToOneField(User, on_delete=models.CASCADE,related_name='profile')
notification_type = models.CharField(max_length=100, choices=NOTIFICATION_TYPE_CHOICES)
pushover_clientkey = models.CharField(max_length=100, default='', blank=True)
notifymyandroid_apikey = models.CharField(max_length=100, default='', blank=True)

def __str__(self):
return str(self.user)
19 changes: 19 additions & 0 deletions feinstaub/main/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.contrib.auth.models import User
from rest_framework import serializers

from .models import UserProfile


class ProfileSerializer(serializers.ModelSerializer):

class Meta:
model = UserProfile
fields = ("notification_type", "pushover_clientkey", "notifymyandroid_apikey")


class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()

class Meta:
model = User
fields = ('id', 'username', 'email', 'profile')
24 changes: 24 additions & 0 deletions feinstaub/main/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.contrib.auth.models import User
from rest_framework import mixins, viewsets, filters

from .serializers import UserSerializer


class UsersView(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
""" Get more information about users
"""

serializer_class = UserSerializer
filter_backends = (filters.OrderingFilter, )
ordering = ('id', )
queryset = User.objects.all()

def get_queryset(self):
if not self.request.user.is_authenticated():
return User.objects.none()

if self.request.user.groups.filter(name="show_me_everything").exists():
return User.objects.all()
return User.objects.filter(pk=self.request.user.pk)
61 changes: 61 additions & 0 deletions feinstaub/sensors/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# coding=utf-8
from django.contrib import admin

from .models import (
Node,
Sensor,
SensorData,
SensorDataValue,
SensorLocation,
SensorType,
)


@admin.register(Node)
class NodeAdmin(admin.ModelAdmin):
search_fields = ['uid', 'description']
list_display = ['uid', 'owner', 'location',
'description', 'created', 'modified']
list_filter = ['owner', 'location']


@admin.register(Sensor)
class SensorAdmin(admin.ModelAdmin):
search_fields = ['node__uid', 'description']
list_display = ['node', 'pin', 'sensor_type',
'description', 'created', 'modified']
list_filter = ['node__owner', 'sensor_type']


@admin.register(SensorData)
class SensorDataAdmin(admin.ModelAdmin):
search_fields = ['sensor__uid', ]
list_display = ['sensor', 'sampling_rate', 'timestamp',
'location', 'created', 'modified']
list_filter = ['sensor', 'location', 'sensor__sensor_type']
show_full_result_count = False


@admin.register(SensorDataValue)
class SensorDataValueAdmin(admin.ModelAdmin):
list_display = ['sensordata', 'value_type', 'value',
'created', 'modified']
list_filter = ['value_type', 'sensordata__sensor',
'sensordata__sensor__sensor_type']
readonly_fields = ['sensordata']
show_full_result_count = False


@admin.register(SensorLocation)
class SensorLocationAdmin(admin.ModelAdmin):
search_fields = ['location', ]
list_display = ['location', 'latitude', 'longitude', 'indoor', 'owner', 'description',
'timestamp', 'created', 'modified']
list_filter = ['indoor', 'owner']


@admin.register(SensorType)
class SensorTypeAdmin(admin.ModelAdmin):
search_fields = ['uid', 'name', 'manufacturer', 'description']
list_display = ['uid', 'name', 'manufacturer',
'description', 'created', 'modified']
48 changes: 48 additions & 0 deletions feinstaub/sensors/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from rest_framework import authentication
from rest_framework import permissions
from rest_framework import exceptions

from .models import Node, Sensor, SensorData, SensorDataValue


class NodeUidAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
node_uid = request.META.get('HTTP_X_SENSOR') or request.META.get('HTTP_SENSOR') or request.META.get('HTTP_NODE')
if not node_uid:
return None

node_pin = request.META.get('HTTP_X_PIN') or request.META.get('HTTP_PIN', '-')

try:
node = Node.objects.get(uid=node_uid)
except Node.DoesNotExist:
raise exceptions.AuthenticationFailed('Node not found in database.')

return (node, node_pin)


class OwnerPermission(permissions.BasePermission):
"""Checks if authenticated user is owner of the node"""

def has_object_permission(self, request, view, obj):
if isinstance(obj, SensorDataValue):
owner_pk = SensorDataValue.objects \
.filter(pk=obj.pk) \
.values_list('sensordata__sensor__node__owner_id', flat=True) \
.first()
elif isinstance(obj, SensorData):
owner_pk = SensorData.objects \
.filter(pk=obj.pk) \
.values_list('sensor__node__owner_id', flat=True) \
.first()
elif isinstance(obj, Sensor):
owner_pk = Sensor.objects \
.filter(pk=obj.pk) \
.values_list('node__owner_id', flat=True) \
.first()
elif isinstance(obj, Node):
owner_pk = obj.owner_id
else:
return False

return request.user.pk == owner_pk
46 changes: 46 additions & 0 deletions feinstaub/sensors/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from django import forms

from .models import SensorType


class AddSensordeviceForm(forms.Form):
# more fields, see https://docs.djangoproject.com/en/1.10/ref/forms/fields/

# get user information
name_pate = forms.CharField(label='Your name', initial="")
email_pate = forms.EmailField(label='Your email', required=True)
# dbuser = forms.CharField(label='DB user', required=True) # get from login data

# Location information
# use exiting location possible?
# maybe with selection from class SensorLocation(TimeStampedModel): location
# and
# use_location_fk = forms.BooleanField(label='use existing location', required=True, initial=False)
# location: manual input
location_location = forms.CharField(label='Location name (Address)', initial="")
location_description = forms.CharField(label='Location description', initial="", widget=forms.Textarea)
location_latitude = forms.DecimalField(label='Latitude (Breite, ~48)', min_value=-90, max_value=90, decimal_places=10)
location_longitude = forms.DecimalField(label='Longitude (Länge, 9)', min_value=-180, max_value=180, decimal_places=10)

# device info
device_initials = forms.CharField(label='Device initials (label to write on device)')
device_uid = forms.CharField(label='Device UID (esp8266-<chipid>)', initial="esp8266-")

# Sensor info
# multiple devices possible, have 2 as default
# insert into model class Sensor(TimeStampedModel):

# TODO: queryset
# class SensorType(TimeStampedModel): default: SDS011 14
sensor1_type = forms.ModelChoiceField(queryset=SensorType.objects.all(), to_field_name="name", initial="SDS011")
sensor1_pin = forms.DecimalField(label='PIN', min_value=0, max_value=8, decimal_places=0, initial=1)
sensor1_description = forms.CharField(label='description for sensor 1', widget=forms.Textarea)
sensor1_public = forms.BooleanField(label='public', required=True, initial=True)

# TODO: queryset
# class SensorType(TimeStampedModel): default: DHT22 9
sensor2_type = forms.ModelChoiceField(queryset=SensorType.objects.all(), to_field_name="name", initial="DHT22", empty_label='--- No sensor ---', required=False)
sensor2_pin = forms.DecimalField(label='PIN', min_value=0, max_value=8, decimal_places=0, initial=7)
# sensor description should contain deviceinitials+"_"+sensor1_type
sensor2_description = forms.CharField(label='description for sensor 2')
sensor2_public = forms.BooleanField(label='public', required=True, initial=True)
Empty file.
Empty file.
19 changes: 19 additions & 0 deletions feinstaub/sensors/management/commands/cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# coding=utf-8
from django.core.management import BaseCommand
import datetime


class Command(BaseCommand):

help = "Cleanup sensordata. This management command may change over time."

def handle(self, *args, **options):
from sensor.models import SensorData
from django.db.models import Count

# delete all SensorData without any SensorDataValues, older than today (maybe some are written just now).
SensorData.objects.annotate(Count('sensordatavalues')).filter(sensordatavalues__count=0).filter(created__lt=datetime.date.today()).delete()

# find all ppd42ns with wrong values and delete them. fixing is way to complicated
SensorData.objects.filter(sensor_id__in=[34, 39, 60]).filter(sensordatavalues__value_type="temperature").delete()
SensorData.objects.filter(sensor_id__in=[34, 39, 60]).filter(sensordatavalues__value_type="humidity").delete()
Loading