From c6bb9a9f3aca26bedd92b8464780490c8c2a85c8 Mon Sep 17 00:00:00 2001 From: student Date: Sat, 7 Sep 2024 18:49:32 +0300 Subject: [PATCH 01/27] added changes --- .gitignore | 30 +++++++++++ aquasens/__init__.py | 0 aquasens/asgi.py | 16 ++++++ aquasens/settings.py | 123 +++++++++++++++++++++++++++++++++++++++++++ aquasens/urls.py | 22 ++++++++ aquasens/wsgi.py | 16 ++++++ manage.py | 22 ++++++++ 7 files changed, 229 insertions(+) create mode 100644 .gitignore create mode 100644 aquasens/__init__.py create mode 100644 aquasens/asgi.py create mode 100644 aquasens/settings.py create mode 100644 aquasens/urls.py create mode 100644 aquasens/wsgi.py create mode 100755 manage.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..939fe0e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Virtual environment (if applicable) +venv/ +# Environment variables +.env +# Django +db.sqlite3 +__pycache__/ +*.pyc +# Backup files +*.bak +# PyCharm +.idea/ +# Python Packaginga +*.egg-info/ +dist/ +build/ +# VSCode +.vscode/ +# Ignore custom file containing databases +database_config.py +env/ +env + + + + + + + + diff --git a/aquasens/__init__.py b/aquasens/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/aquasens/asgi.py b/aquasens/asgi.py new file mode 100644 index 00000000..3e81e442 --- /dev/null +++ b/aquasens/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for aquasens project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'aquasens.settings') + +application = get_asgi_application() diff --git a/aquasens/settings.py b/aquasens/settings.py new file mode 100644 index 00000000..b3438174 --- /dev/null +++ b/aquasens/settings.py @@ -0,0 +1,123 @@ +""" +Django settings for aquasens project. + +Generated by 'django-admin startproject' using Django 5.0.7. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-3iv0zb#bjadq6r4!x%4c9g)3@#a3h)&v!%*g^_fxvyw59w_0d6' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'aquasens.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'aquasens.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/aquasens/urls.py b/aquasens/urls.py new file mode 100644 index 00000000..d436f9d8 --- /dev/null +++ b/aquasens/urls.py @@ -0,0 +1,22 @@ +""" +URL configuration for aquasens project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/aquasens/wsgi.py b/aquasens/wsgi.py new file mode 100644 index 00000000..8e75fb32 --- /dev/null +++ b/aquasens/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for aquasens project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'aquasens.settings') + +application = get_wsgi_application() diff --git a/manage.py b/manage.py new file mode 100755 index 00000000..6f9abd68 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'aquasens.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() From 933791ebc38b62f80887ab3ce92879bdf28edaeb Mon Sep 17 00:00:00 2001 From: Mutesi-alin Date: Fri, 13 Sep 2024 21:41:36 +0300 Subject: [PATCH 02/27] add changes --- api/__init__.py | 0 api/admin.py | 3 + api/apps.py | 6 + api/migrations/__init__.py | 0 api/models.py | 3 + api/serializers.py | 29 +++++ api/tests.py | 3 + api/urls.py | 10 ++ api/views.py | 103 ++++++++++++++++++ aquasens/settings.py | 71 +++++++++--- aquasens/urls.py | 10 +- authentication/__init__.py | 0 authentication/admin.py | 3 + authentication/apps.py | 6 + authentication/migrations/__init__.py | 0 authentication/models.py | 3 + .../templates/authentication/index.html | 16 +++ authentication/tests.py | 3 + authentication/urls.py | 10 ++ authentication/views.py | 61 +++++++++++ requirements.txt | 6 + user/__init__.py | 0 user/admin.py | 11 ++ user/apps.py | 6 + user/migrations/0001_initial.py | 30 +++++ user/migrations/__init__.py | 0 user/models.py | 31 ++++++ user/tests.py | 3 + user/views.py | 3 + 29 files changed, 414 insertions(+), 16 deletions(-) create mode 100644 api/__init__.py create mode 100644 api/admin.py create mode 100644 api/apps.py create mode 100644 api/migrations/__init__.py create mode 100644 api/models.py create mode 100644 api/serializers.py create mode 100644 api/tests.py create mode 100644 api/urls.py create mode 100644 api/views.py create mode 100644 authentication/__init__.py create mode 100644 authentication/admin.py create mode 100644 authentication/apps.py create mode 100644 authentication/migrations/__init__.py create mode 100644 authentication/models.py create mode 100644 authentication/templates/authentication/index.html create mode 100644 authentication/tests.py create mode 100644 authentication/urls.py create mode 100644 authentication/views.py create mode 100644 requirements.txt create mode 100644 user/__init__.py create mode 100644 user/admin.py create mode 100644 user/apps.py create mode 100644 user/migrations/0001_initial.py create mode 100644 user/migrations/__init__.py create mode 100644 user/models.py create mode 100644 user/tests.py create mode 100644 user/views.py diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/apps.py b/api/apps.py new file mode 100644 index 00000000..66656fd2 --- /dev/null +++ b/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api' diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/models.py b/api/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/api/serializers.py b/api/serializers.py new file mode 100644 index 00000000..fc43adc7 --- /dev/null +++ b/api/serializers.py @@ -0,0 +1,29 @@ +from rest_framework import serializers +from user .models import User , ROLE_CHOICES +from django.contrib.auth.models import User as DjangoUser +class UserSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True, required=True) + + class Meta: + model = User + fields = ['id', 'first_name', 'last_name', 'phone_number', 'email', 'role', 'password'] + read_only_fields = ['id'] + + def create(self, validated_data): + django_user = DjangoUser.objects.create_user( + username=validated_data['email'], + email=validated_data['email'], + password=validated_data['password'] + ) + user = User.objects.create( + user=django_user, + first_name=validated_data['first_name'], + last_name=validated_data['last_name'], + phone_number=validated_data['phone_number'], + email=validated_data['email'], + role=validated_data['role'] + ) + return user +class RoleSerializer(serializers.Serializer): + user_id = serializers.IntegerField() + # role = serializers.ChoiceField(choices=User.ROLE_CHOICES) \ No newline at end of file diff --git a/api/tests.py b/api/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 00000000..3b926f55 --- /dev/null +++ b/api/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from .views import UserListView, UserDetailView, RegisterView, LoginView, RoleBasedView + +urlpatterns = [ + path('users/', UserListView.as_view(), name='user-list'), + path('users//', UserDetailView.as_view(), name='user-detail'), + path('users/register/', RegisterView.as_view(), name='user-register'), + path('users/login/', LoginView.as_view(), name='user-login'), + path('users/role-based/', RoleBasedView.as_view(), name='role-based'), +] \ No newline at end of file diff --git a/api/views.py b/api/views.py new file mode 100644 index 00000000..273a15a5 --- /dev/null +++ b/api/views.py @@ -0,0 +1,103 @@ +from django.shortcuts import render + +# Create your views here. + +import logging +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated, IsAdminUser + +from django.contrib.auth.hashers import make_password +from user.models import User +from .serializers import UserSerializer,RoleSerializer +from django.contrib.auth import authenticate + + + +logger = logging.getLogger(__name__) + +class UserListView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + users = User.objects.all() + serializer = UserSerializer(users, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + +class UserDetailView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, id): + try: + user = User.objects.get(id=id) + except User.DoesNotExist: + + return Response({"detail": "User not found."}, status=status.HTTP_404_NOT_FOUND) + serializer = UserSerializer(user) + logger.info(f'User with ID {id} retrieved successfully.') + return Response(serializer.data) + + + + + +class RegisterView(APIView): + def post(self, request): + serializer = UserSerializer(data=request.data) + if serializer.is_valid(): + # hashing password + serializer.validated_data['password'] = make_password(serializer.validated_data['password']) + user = serializer.save() + logger.info(f'User registered successfully: {user.email}') + return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED) + logger.error(f'User registration failed: {serializer.errors}') + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) +class LoginView(APIView): + + def post(self, request): + email = request.data.get('email') + password = request.data.get('password') + + if not email or not password: + return Response({'error': 'Email and password are required'}, status=status.HTTP_400_BAD_REQUEST) + + try: + user = User.objects.get(email=email) + except User.DoesNotExist: + logger.info(f'Login attempt for non-existent user: {email}') + return Response({ + 'error': 'User does not exist', + 'signup_required': True + }, status=status.HTTP_404_NOT_FOUND) + + django_user = authenticate(username=email, password=password) + if django_user: + logger.info(f'User logged in successfully: {email}') + return Response({}, status=status.HTTP_200_OK) + + logger.error(f'Login failed for user: {email}') + return Response({'error': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) + + +class RoleBasedView(APIView): + permission_classes = [IsAuthenticated, IsAdminUser] + + def post(self, request): + serializer = RoleSerializer(data=request.data) + if serializer.is_valid(): + user_id = serializer.validated_data['user_id'] + new_role = serializer.validated_data['role'] + + try: + user = User.objects.get(id=user_id) + user.role = new_role + user.save() + logger.info(f'Role updated for user {user.email}: {new_role}') + return Response({"detail": f"Role updated to {new_role} for user successfully"}, status=status.HTTP_200_OK) + except User.DoesNotExist: + logger.error(f'User with ID {user_id} not found') + return Response({"detail": "User not found"}, status=status.HTTP_404_NOT_FOUND) + else: + logger.error(f'Invalid role update data: {serializer.errors}') + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/aquasens/settings.py b/aquasens/settings.py index b3438174..a88b9416 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -1,31 +1,40 @@ + + + """ -Django settings for aquasens project. +Django settings for newproject project. -Generated by 'django-admin startproject' using Django 5.0.7. +Generated by 'django-admin startproject' using Django 5.1.1. For more information on this file, see -https://docs.djangoproject.com/en/5.0/topics/settings/ +https://docs.djangoproject.com/en/5.1/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/5.0/ref/settings/ +https://docs.djangoproject.com/en/5.1/ref/settings/ """ - +from datetime import timedelta +import os +from pathlib import Path +from dotenv import load_dotenv, find_dotenv from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent +TEMPLATE_DIR = os.path.join(BASE_DIR, "drainage", "templates") # Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-3iv0zb#bjadq6r4!x%4c9g)3@#a3h)&v!%*g^_fxvyw59w_0d6' +SECRET_KEY = 'django-insecure-9bv3ge1%z6^itsr^q#$ssef0so*!ex&i#5*e*gg+-^ae9@tgn9' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] + +ALLOWED_HOSTS = [ 'localhost'] + # Application definition @@ -37,6 +46,11 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'authentication', + 'user', + 'api', + 'rest_framework', + ] MIDDLEWARE = [ @@ -51,10 +65,23 @@ ROOT_URLCONF = 'aquasens.urls' +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ], +} +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=365*10), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=365*10), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': True, +} + + TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + "DIRS": [TEMPLATE_DIR], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -62,16 +89,21 @@ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + ], }, }, ] +ENV_FILE = find_dotenv() +if ENV_FILE: + load_dotenv(ENV_FILE) + WSGI_APPLICATION = 'aquasens.wsgi.application' # Database -# https://docs.djangoproject.com/en/5.0/ref/settings/#databases +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases DATABASES = { 'default': { @@ -82,7 +114,7 @@ # Password validation -# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { @@ -101,7 +133,7 @@ # Internationalization -# https://docs.djangoproject.com/en/5.0/topics/i18n/ +# https://docs.djangoproject.com/en/5.1/topics/i18n/ LANGUAGE_CODE = 'en-us' @@ -113,11 +145,22 @@ # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/5.0/howto/static-files/ +# https://docs.djangoproject.com/en/5.1/howto/static-files/ STATIC_URL = 'static/' # Default primary key field type -# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +AUTH0_CLIENT_ID = os.getenv('AUTH0_CLIENT_ID') +AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET') +AUTH0_DOMAIN = os.getenv('AUTH0_DOMAIN') + + +REDIRECT_URI = 'http://localhost:8000/auth/callback/' + + + DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + diff --git a/aquasens/urls.py b/aquasens/urls.py index d436f9d8..f5d55007 100644 --- a/aquasens/urls.py +++ b/aquasens/urls.py @@ -14,9 +14,15 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.contrib import admin -from django.urls import path +from django.urls import path, include +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView urlpatterns = [ path('admin/', admin.site.urls), -] + path('auth/', include("authentication.urls")), + path('api/', include("api.urls")), + path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), +] \ No newline at end of file diff --git a/authentication/__init__.py b/authentication/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/authentication/admin.py b/authentication/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/authentication/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/authentication/apps.py b/authentication/apps.py new file mode 100644 index 00000000..8bab8df0 --- /dev/null +++ b/authentication/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthenticationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'authentication' diff --git a/authentication/migrations/__init__.py b/authentication/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/authentication/models.py b/authentication/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/authentication/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/authentication/templates/authentication/index.html b/authentication/templates/authentication/index.html new file mode 100644 index 00000000..f10174ba --- /dev/null +++ b/authentication/templates/authentication/index.html @@ -0,0 +1,16 @@ + + + + Auth0 + + +{% if session %} +

Welcome {{session.userinfo.name}}!

+

Logout

+
{{pretty}}
+{% else %} +

Welcome Guest

+

Login

+{% endif %} + + \ No newline at end of file diff --git a/authentication/tests.py b/authentication/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/authentication/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/authentication/urls.py b/authentication/urls.py new file mode 100644 index 00000000..7a907889 --- /dev/null +++ b/authentication/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.index, name="index"), + path("login", views.login, name="login"), + path("logout", views.logout, name="logout"), + path("callback", views.callback, name="callback"), +] \ No newline at end of file diff --git a/authentication/views.py b/authentication/views.py new file mode 100644 index 00000000..17012402 --- /dev/null +++ b/authentication/views.py @@ -0,0 +1,61 @@ +from django.shortcuts import render + + +import json +from authlib.integrations.django_client import OAuth +from django.conf import settings +from django.shortcuts import redirect, render +from django.urls import reverse +from urllib.parse import quote_plus, urlencode + + +oauth = OAuth() + +oauth.register( + "auth0", + client_id=settings.AUTH0_CLIENT_ID, + client_secret=settings.AUTH0_CLIENT_SECRET, + client_kwargs={ + "scope": "openid profile email", + }, + server_metadata_url=f"http://{settings.AUTH0_DOMAIN}/.well-known/openid-configuration", +) + + +def login(request): + return oauth.auth0.authorize_redirect( + request, request.build_absolute_uri(reverse("callback")) + ) + + +def callback(request): + token = oauth.auth0.authorize_access_token(request) + request.session["user"] = token + return redirect(request.build_absolute_uri(reverse("index"))) + + +def logout(request): + request.session.clear() + + return redirect( + f"http://{settings.AUTH0_DOMAIN}/v2/logout?" + + urlencode( + { + "returnTo": request.build_absolute_uri(reverse("index")), + "client_id": settings.AUTH0_CLIENT_ID, + }, + quote_via=quote_plus, + ), + ) + + +def index(request): + return render( + request, + "authentication/index.html", + context={ + "session": request.session.get("user"), + "pretty": json.dumps(request.session.get("user"), indent=4), + }, + ) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..a7da563d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ + + +authlib ~= 1.0 +django ~= 4.0 +python-dotenv ~= 0.19 +requests ~= 2.27 \ No newline at end of file diff --git a/user/__init__.py b/user/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/user/admin.py b/user/admin.py new file mode 100644 index 00000000..310349cf --- /dev/null +++ b/user/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +# Register your models here. +from django.contrib import admin + +# Register your models here. + + +from .models import User +admin.site.register(User) + diff --git a/user/apps.py b/user/apps.py new file mode 100644 index 00000000..36cce4c8 --- /dev/null +++ b/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user' diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py new file mode 100644 index 00000000..31684193 --- /dev/null +++ b/user/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1 on 2024-09-12 05:57 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_name', models.CharField(max_length=30)), + ('last_name', models.CharField(max_length=30)), + ('phone_number', models.CharField(max_length=20)), + ('email', models.EmailField(max_length=254, unique=True)), + ('password', models.CharField(max_length=16)), + ('role', models.CharField(choices=[('Estate_Associate', 'Estate_Associate'), ('ADMIN', 'ADMIN')], default='Estate_Associate', max_length=30)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/user/migrations/__init__.py b/user/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/user/models.py b/user/models.py new file mode 100644 index 00000000..af7fa9ea --- /dev/null +++ b/user/models.py @@ -0,0 +1,31 @@ +from django.db import models + +# Create your models here. +from django.db import models +from django.contrib.auth.models import User as DjangoUser + +Estate_Associate = 'Estate_Associate' +ADMIN = 'ADMIN' +ROLE_CHOICES = [ + (Estate_Associate, 'Estate_Associate'), + (ADMIN, 'ADMIN'), +] + +class User(models.Model): + user = models.OneToOneField(DjangoUser, on_delete=models.CASCADE) + first_name = models.CharField(max_length=30) + last_name = models.CharField(max_length=30) + phone_number = models.CharField(max_length=20) + email = models.EmailField(unique=True) + password = models.CharField(max_length=16) + role = models.CharField( + max_length=30, + choices=ROLE_CHOICES, + default=Estate_Associate, + ) + + def __str__(self): + return f"{self.first_name} {self.last_name} ({self.email})" + + + \ No newline at end of file diff --git a/user/tests.py b/user/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/user/views.py b/user/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/user/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 2ca2451444797a3b2b1ba771ccad413c3f6344d1 Mon Sep 17 00:00:00 2001 From: student Date: Sat, 14 Sep 2024 10:06:41 +0300 Subject: [PATCH 03/27] add file --- api/serializers.py | 7 +++++- api/urls.py | 9 ++++++++ api/views.py | 7 ++++++ aquasens/settings.py | 1 + aquasens/urls.py | 1 + datamonitoring/__init__.py | 0 datamonitoring/admin.py | 5 ++++ datamonitoring/apps.py | 6 +++++ datamonitoring/migrations/0001_initial.py | 28 +++++++++++++++++++++++ datamonitoring/migrations/__init__.py | 0 datamonitoring/models.py | 16 +++++++++++++ datamonitoring/tests.py | 3 +++ datamonitoring/views.py | 3 +++ 13 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 datamonitoring/__init__.py create mode 100644 datamonitoring/admin.py create mode 100644 datamonitoring/apps.py create mode 100644 datamonitoring/migrations/0001_initial.py create mode 100644 datamonitoring/migrations/__init__.py create mode 100644 datamonitoring/models.py create mode 100644 datamonitoring/tests.py create mode 100644 datamonitoring/views.py diff --git a/api/serializers.py b/api/serializers.py index fc43adc7..1c78842a 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,6 +1,7 @@ from rest_framework import serializers from user .models import User , ROLE_CHOICES from django.contrib.auth.models import User as DjangoUser +from datamonitoring.models import MonitoringData class UserSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=True) @@ -26,4 +27,8 @@ def create(self, validated_data): return user class RoleSerializer(serializers.Serializer): user_id = serializers.IntegerField() - # role = serializers.ChoiceField(choices=User.ROLE_CHOICES) \ No newline at end of file + # role = serializers.ChoiceField(choices=User.ROLE_CHOICES) +class MonitoringDataSerializer(serializers.ModelSerializer): + class Meta: + model = MonitoringData + fields = ['monitoring_id', 'user_id', 'drainage_id', 'timestamp', 'water_level', 'water_pressure'] \ No newline at end of file diff --git a/api/urls.py b/api/urls.py index 3b926f55..218be3ec 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,10 +1,19 @@ from django.urls import path from .views import UserListView, UserDetailView, RegisterView, LoginView, RoleBasedView +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import MonitoringDataViewSet + +router = DefaultRouter() +router.register(r'monitoring-data', MonitoringDataViewSet) + urlpatterns = [ path('users/', UserListView.as_view(), name='user-list'), path('users//', UserDetailView.as_view(), name='user-detail'), path('users/register/', RegisterView.as_view(), name='user-register'), path('users/login/', LoginView.as_view(), name='user-login'), path('users/role-based/', RoleBasedView.as_view(), name='role-based'), + path('', include(router.urls)), + ] \ No newline at end of file diff --git a/api/views.py b/api/views.py index 273a15a5..56948052 100644 --- a/api/views.py +++ b/api/views.py @@ -12,6 +12,9 @@ from user.models import User from .serializers import UserSerializer,RoleSerializer from django.contrib.auth import authenticate +from rest_framework import viewsets +from datamonitoring.models import MonitoringData +from .serializers import MonitoringDataSerializer @@ -101,3 +104,7 @@ def post(self, request): else: logger.error(f'Invalid role update data: {serializer.errors}') return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class MonitoringDataViewSet(viewsets.ModelViewSet): + queryset = MonitoringData.objects.all() + serializer_class = MonitoringDataSerializer diff --git a/aquasens/settings.py b/aquasens/settings.py index a88b9416..ce461360 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -50,6 +50,7 @@ 'user', 'api', 'rest_framework', + 'datamonitoring', ] diff --git a/aquasens/urls.py b/aquasens/urls.py index f5d55007..dbddbb25 100644 --- a/aquasens/urls.py +++ b/aquasens/urls.py @@ -25,4 +25,5 @@ path('api/', include("api.urls")), path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('api/', include('api.urls')), ] \ No newline at end of file diff --git a/datamonitoring/__init__.py b/datamonitoring/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/datamonitoring/admin.py b/datamonitoring/admin.py new file mode 100644 index 00000000..7c6a98ef --- /dev/null +++ b/datamonitoring/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin + +# Register your models here. +from .models import MonitoringData +admin.site.register(MonitoringData) \ No newline at end of file diff --git a/datamonitoring/apps.py b/datamonitoring/apps.py new file mode 100644 index 00000000..54afdd9d --- /dev/null +++ b/datamonitoring/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DatamonitoringConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'datamonitoring' diff --git a/datamonitoring/migrations/0001_initial.py b/datamonitoring/migrations/0001_initial.py new file mode 100644 index 00000000..aa323f3f --- /dev/null +++ b/datamonitoring/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.7 on 2024-09-14 07:00 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='MonitoringData', + fields=[ + ('monitoring_id', models.AutoField(primary_key=True, serialize=False)), + ('drainage_id', models.IntegerField()), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('water_level', models.DecimalField(decimal_places=2, max_digits=10)), + ('water_pressure', models.DecimalField(decimal_places=2, max_digits=10)), + ('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/datamonitoring/migrations/__init__.py b/datamonitoring/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/datamonitoring/models.py b/datamonitoring/models.py new file mode 100644 index 00000000..f2cc1624 --- /dev/null +++ b/datamonitoring/models.py @@ -0,0 +1,16 @@ +from django.db import models + +# Create your models here. +from django.db import models + +class MonitoringData(models.Model): + monitoring_id = models.AutoField(primary_key=True) + user_id = models.ForeignKey('auth.User', on_delete=models.CASCADE) + drainage_id = models.IntegerField() + timestamp = models.DateTimeField(auto_now_add=True) + water_level = models.DecimalField(max_digits=10, decimal_places=2) + water_pressure = models.DecimalField(max_digits=10, decimal_places=2) + + def __str__(self): + return f'Monitoring Data {self.monitoring_id}' + diff --git a/datamonitoring/tests.py b/datamonitoring/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/datamonitoring/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/datamonitoring/views.py b/datamonitoring/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/datamonitoring/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From d19e483ffdb2f9cd41914bec8cf52a7131ce54f5 Mon Sep 17 00:00:00 2001 From: nyabangWech Date: Sat, 14 Sep 2024 11:48:50 +0300 Subject: [PATCH 04/27] added drainagesystem --- api/serializers.py | 11 +++++++- api/urls.py | 4 ++- api/views.py | 34 +++++++++++++++++++++++ aquasens/settings.py | 1 + aquasens/urls.py | 2 +- drainagesystem/__init__.py | 0 drainagesystem/admin.py | 3 ++ drainagesystem/apps.py | 6 ++++ drainagesystem/migrations/0001_initial.py | 25 +++++++++++++++++ drainagesystem/migrations/__init__.py | 0 drainagesystem/models.py | 11 ++++++++ drainagesystem/tests.py | 3 ++ drainagesystem/views.py | 3 ++ 13 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 drainagesystem/__init__.py create mode 100644 drainagesystem/admin.py create mode 100644 drainagesystem/apps.py create mode 100644 drainagesystem/migrations/0001_initial.py create mode 100644 drainagesystem/migrations/__init__.py create mode 100644 drainagesystem/models.py create mode 100644 drainagesystem/tests.py create mode 100644 drainagesystem/views.py diff --git a/api/serializers.py b/api/serializers.py index 1c78842a..83eddca9 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -2,6 +2,8 @@ from user .models import User , ROLE_CHOICES from django.contrib.auth.models import User as DjangoUser from datamonitoring.models import MonitoringData +from rest_framework import serializers +from drainagesystem.models import DrainageSystem class UserSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=True) @@ -31,4 +33,11 @@ class RoleSerializer(serializers.Serializer): class MonitoringDataSerializer(serializers.ModelSerializer): class Meta: model = MonitoringData - fields = ['monitoring_id', 'user_id', 'drainage_id', 'timestamp', 'water_level', 'water_pressure'] \ No newline at end of file + fields = ['monitoring_id', 'user_id', 'drainage_id', 'timestamp', 'water_level', 'water_pressure'] + + + +class DrainageSystemSerializer(serializers.ModelSerializer): + class Meta: + model = DrainageSystem + fields = '__all__' \ No newline at end of file diff --git a/api/urls.py b/api/urls.py index 218be3ec..d2bb2391 100644 --- a/api/urls.py +++ b/api/urls.py @@ -4,6 +4,7 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import MonitoringDataViewSet +from .views import DrainageSystemList, DrainageSystemDetail router = DefaultRouter() router.register(r'monitoring-data', MonitoringDataViewSet) @@ -15,5 +16,6 @@ path('users/login/', LoginView.as_view(), name='user-login'), path('users/role-based/', RoleBasedView.as_view(), name='role-based'), path('', include(router.urls)), - + path('drainage-systems/', DrainageSystemList.as_view(), name='drainage-system-list'), + path('drainage-systems//', DrainageSystemDetail.as_view(), name='drainage-system-detail'), ] \ No newline at end of file diff --git a/api/views.py b/api/views.py index 56948052..5e968d8a 100644 --- a/api/views.py +++ b/api/views.py @@ -18,6 +18,17 @@ +from django.shortcuts import render +from django.shortcuts import render +from rest_framework import generics,status +from rest_framework.response import Response +from drainagesystem.models import DrainageSystem +from .serializers import DrainageSystemSerializer +from rest_framework.views import APIView + + + + logger = logging.getLogger(__name__) class UserListView(APIView): @@ -108,3 +119,26 @@ def post(self, request): class MonitoringDataViewSet(viewsets.ModelViewSet): queryset = MonitoringData.objects.all() serializer_class = MonitoringDataSerializer + +class DrainageSystemList(generics.ListCreateAPIView): + queryset = DrainageSystem.objects.all() + serializer_class = DrainageSystemSerializer + def get (self, request,*args,**kwargs): + serializer = DrainageSystemSerializer(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) + + +class DrainageSystemDetail(APIView): + def post(self, request, *args, **kwargs): + serializer = DrainageSystemSerializer(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) + + + + diff --git a/aquasens/settings.py b/aquasens/settings.py index ce461360..ac563042 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -51,6 +51,7 @@ 'api', 'rest_framework', 'datamonitoring', + 'drainagesystem', ] diff --git a/aquasens/urls.py b/aquasens/urls.py index dbddbb25..327d0353 100644 --- a/aquasens/urls.py +++ b/aquasens/urls.py @@ -25,5 +25,5 @@ path('api/', include("api.urls")), path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), - path('api/', include('api.urls')), + ] \ No newline at end of file diff --git a/drainagesystem/__init__.py b/drainagesystem/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/drainagesystem/admin.py b/drainagesystem/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/drainagesystem/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/drainagesystem/apps.py b/drainagesystem/apps.py new file mode 100644 index 00000000..a0fa61d2 --- /dev/null +++ b/drainagesystem/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DrainagesystemConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'drainagesystem' diff --git a/drainagesystem/migrations/0001_initial.py b/drainagesystem/migrations/0001_initial.py new file mode 100644 index 00000000..0c0a7b6c --- /dev/null +++ b/drainagesystem/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.16 on 2024-09-14 08:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='DrainageSystem', + fields=[ + ('Drainage_ID', models.AutoField(primary_key=True, serialize=False)), + ('Location', models.CharField(max_length=100)), + ('waterlevel', models.DecimalField(decimal_places=2, max_digits=10)), + ('waterpressure', models.DecimalField(decimal_places=2, max_digits=10)), + ('Status', models.CharField(max_length=100)), + ('Timestamp', models.DateTimeField(auto_now_add=True)), + ], + ), + ] diff --git a/drainagesystem/migrations/__init__.py b/drainagesystem/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/drainagesystem/models.py b/drainagesystem/models.py new file mode 100644 index 00000000..e5c60704 --- /dev/null +++ b/drainagesystem/models.py @@ -0,0 +1,11 @@ +from django.db import models +class DrainageSystem(models.Model): + Drainage_ID = models.AutoField(primary_key=True) + Location = models.CharField(max_length=100) + waterlevel=models.DecimalField(max_digits=10, decimal_places=2) + waterpressure=models.DecimalField(max_digits=10, decimal_places=2) + Status = models.CharField(max_length=100) + Timestamp=models.DateTimeField(auto_now_add=True) + + +# Create your models here. diff --git a/drainagesystem/tests.py b/drainagesystem/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/drainagesystem/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/drainagesystem/views.py b/drainagesystem/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/drainagesystem/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From d19c55d9852cb06f063641534584aa0c21609d35 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Sat, 14 Sep 2024 16:30:28 +0300 Subject: [PATCH 05/27] added files --- api/serializers.py | 27 +++++- api/urls.py | 23 ++++- api/views.py | 55 +++++++++--- aquasens/settings.py | 4 +- aquasens/urls.py | 2 + map/__init__.py | 0 map/admin.py | 7 ++ map/apps.py | 6 ++ map/migrations/0001_initial.py | 25 ++++++ map/migrations/__init__.py | 0 map/models.py | 12 +++ map/serializers.py | 7 ++ map/templates/map/map_templates.html | 106 ++++++++++++++++++++++++ map/tests.py | 3 + map/urls.py | 12 +++ map/views.py | 51 ++++++++++++ notification/__init__.py | 0 notification/admin.py | 4 + notification/apps.py | 6 ++ notification/migrations/0001_initial.py | 24 ++++++ notification/migrations/__init__.py | 0 notification/models.py | 17 ++++ notification/tests.py | 3 + notification/views.py | 3 + sensor/__init__.py | 0 sensor/admin.py | 3 + sensor/apps.py | 6 ++ sensor/migrations/0001_initial.py | 24 ++++++ sensor/migrations/__init__.py | 0 sensor/models.py | 16 ++++ sensor/tests.py | 3 + sensor/views.py | 3 + 32 files changed, 433 insertions(+), 19 deletions(-) create mode 100644 map/__init__.py create mode 100644 map/admin.py create mode 100644 map/apps.py create mode 100644 map/migrations/0001_initial.py create mode 100644 map/migrations/__init__.py create mode 100644 map/models.py create mode 100644 map/serializers.py create mode 100644 map/templates/map/map_templates.html create mode 100644 map/tests.py create mode 100644 map/urls.py create mode 100644 map/views.py create mode 100644 notification/__init__.py create mode 100644 notification/admin.py create mode 100644 notification/apps.py create mode 100644 notification/migrations/0001_initial.py create mode 100644 notification/migrations/__init__.py create mode 100644 notification/models.py create mode 100644 notification/tests.py create mode 100644 notification/views.py create mode 100644 sensor/__init__.py create mode 100644 sensor/admin.py create mode 100644 sensor/apps.py create mode 100644 sensor/migrations/0001_initial.py create mode 100644 sensor/migrations/__init__.py create mode 100644 sensor/models.py create mode 100644 sensor/tests.py create mode 100644 sensor/views.py diff --git a/api/serializers.py b/api/serializers.py index 83eddca9..73e0a291 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -2,8 +2,13 @@ from user .models import User , ROLE_CHOICES from django.contrib.auth.models import User as DjangoUser from datamonitoring.models import MonitoringData -from rest_framework import serializers from drainagesystem.models import DrainageSystem +from rest_framework import serializers +from sensor.models import Sensor +from rest_framework import serializers +from map.models import Device +from notification.models import Notification + class UserSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=True) @@ -40,4 +45,22 @@ class Meta: class DrainageSystemSerializer(serializers.ModelSerializer): class Meta: model = DrainageSystem - fields = '__all__' \ No newline at end of file + fields = '__all__' + + +class DeviceSerializer(serializers.ModelSerializer): + class Meta: + model = Device + fields = ['id', 'latitude', 'longitude', 'address', 'type'] + + +class SensorSerializer(serializers.ModelSerializer): + class Meta: + model = Sensor + fields = ['Sensor_ID', 'Type', 'Location','Status', 'Time_Date'] + +class NotificationSerializer(serializers.ModelSerializer): + class Meta: + model = Notification + fields = ['id', 'title', 'message', 'type', 'created_at'] + \ No newline at end of file diff --git a/api/urls.py b/api/urls.py index d2bb2391..cd80c2d7 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,13 +1,18 @@ from django.urls import path from .views import UserListView, UserDetailView, RegisterView, LoginView, RoleBasedView - from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import MonitoringDataViewSet from .views import DrainageSystemList, DrainageSystemDetail - +from map.views import map_view +from map.views import MapDeviceListView, DeviceSearchView, map_view +from .views import SensorListCreateView, SensorDetailView +from .views import NotificationViewSet router = DefaultRouter() router.register(r'monitoring-data', MonitoringDataViewSet) +router = DefaultRouter() +router.register(r'notifications', NotificationViewSet) +urlpatterns = router.urls # Use the router's URLs urlpatterns = [ path('users/', UserListView.as_view(), name='user-list'), @@ -18,4 +23,16 @@ path('', include(router.urls)), path('drainage-systems/', DrainageSystemList.as_view(), name='drainage-system-list'), path('drainage-systems//', DrainageSystemDetail.as_view(), name='drainage-system-detail'), -] \ No newline at end of file + path('api', map_view, name='map_view'), # This matches the `map/` URL + path('map/', map_view, name='map_view'), + path('devices/', MapDeviceListView.as_view(), name='device-list'), + path('search/', DeviceSearchView.as_view(), name='device-search'), + path('sensors/', SensorListCreateView.as_view(), name='sensor-list-create'), + path('sensors//', SensorDetailView.as_view(), name='sensor-detail'), + path('notifications/', NotificationViewSet.as_view({'get': 'list', 'post': 'create'})), + path('notifications//', NotificationViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})), + + +] + + diff --git a/api/views.py b/api/views.py index 5e968d8a..10159644 100644 --- a/api/views.py +++ b/api/views.py @@ -1,13 +1,9 @@ from django.shortcuts import render - -# Create your views here. - import logging from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated, IsAdminUser - from django.contrib.auth.hashers import make_password from user.models import User from .serializers import UserSerializer,RoleSerializer @@ -15,16 +11,18 @@ from rest_framework import viewsets from datamonitoring.models import MonitoringData from .serializers import MonitoringDataSerializer - - - -from django.shortcuts import render -from django.shortcuts import render from rest_framework import generics,status from rest_framework.response import Response from drainagesystem.models import DrainageSystem from .serializers import DrainageSystemSerializer -from rest_framework.views import APIView +from .serializers import DeviceSerializer +from sensor.models import Sensor +from .serializers import SensorSerializer +from rest_framework import viewsets +from notification.models import Notification +from .serializers import NotificationSerializer + + @@ -139,6 +137,37 @@ def post(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - - +class SensorListCreateView(generics.ListCreateAPIView): + queryset = Sensor.objects.all() + serializer_class = SensorSerializer + def post(self, request): + serializer = SensorSerializer(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): + return Response({}) + +class SensorDetailView(generics.RetrieveUpdateDestroyAPIView): + queryset = Sensor.objects.all() + serializer_class = SensorSerializer + def get(self, request, id): + sensor = self.get_object() + serializer = SensorSerializer(sensor) + return Response(serializer.data) + def put(self, request, id): + sensor = self.get_object() + serializer = SensorSerializer(sensor, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + def delete(self, request, id): + sensor = self.get_object() + sensor.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + +class NotificationViewSet(viewsets.ModelViewSet): + queryset = Notification.objects.all() + serializer_class = NotificationSerializer \ No newline at end of file diff --git a/aquasens/settings.py b/aquasens/settings.py index ac563042..0263542a 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -52,7 +52,9 @@ 'rest_framework', 'datamonitoring', 'drainagesystem', - + 'map', + 'sensor', + 'notification', ] MIDDLEWARE = [ diff --git a/aquasens/urls.py b/aquasens/urls.py index 327d0353..35254614 100644 --- a/aquasens/urls.py +++ b/aquasens/urls.py @@ -25,5 +25,7 @@ path('api/', include("api.urls")), path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('map/', include('map.urls')), + # path('api/',include('notification.urls')), ] \ No newline at end of file diff --git a/map/__init__.py b/map/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/map/admin.py b/map/admin.py new file mode 100644 index 00000000..0d89021c --- /dev/null +++ b/map/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from.models import Device +admin.site.register(Device) + +from django.contrib import admin +from map.models import Device +# Register your models here. diff --git a/map/apps.py b/map/apps.py new file mode 100644 index 00000000..604c7c7d --- /dev/null +++ b/map/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MapConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'map' diff --git a/map/migrations/0001_initial.py b/map/migrations/0001_initial.py new file mode 100644 index 00000000..d4464baa --- /dev/null +++ b/map/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.6 on 2024-09-14 13:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Device', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('address', models.CharField(max_length=255)), + ('latitude', models.FloatField()), + ('longitude', models.FloatField()), + ('status', models.CharField(max_length=255)), + ('type', models.CharField(default='sensor', max_length=50)), + ], + ), + ] diff --git a/map/migrations/__init__.py b/map/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/map/models.py b/map/models.py new file mode 100644 index 00000000..d76f8adc --- /dev/null +++ b/map/models.py @@ -0,0 +1,12 @@ +from django.db import models + +# Create your models here. +class Device(models.Model): + address = models.CharField(max_length=255) + latitude = models.FloatField() + longitude = models.FloatField() + status = models.CharField(max_length=255) + type = models.CharField(max_length=50, default ='sensor') + + def __str__(self): + return f"{self.type} - {self.address}" \ No newline at end of file diff --git a/map/serializers.py b/map/serializers.py new file mode 100644 index 00000000..3091bc0b --- /dev/null +++ b/map/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import Device + +class DeviceSerializer(serializers.ModelSerializer): + class Meta: + model = Device + fields = '__all__' \ No newline at end of file diff --git a/map/templates/map/map_templates.html b/map/templates/map/map_templates.html new file mode 100644 index 00000000..f4d984b4 --- /dev/null +++ b/map/templates/map/map_templates.html @@ -0,0 +1,106 @@ + + + + + + Device Map + + + + + +

Locations of My Devices

+ +
+ + + + + + + + \ No newline at end of file diff --git a/map/tests.py b/map/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/map/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/map/urls.py b/map/urls.py new file mode 100644 index 00000000..069653c8 --- /dev/null +++ b/map/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from .views import map_view +from .views import MapDeviceListView, DeviceSearchView, map_view + +urlpatterns = [ + path('api', map_view, name='map_view'), # This matches the `map/` URL + path('map/', map_view, name='map_view'), + path('devices/', MapDeviceListView.as_view(), name='device-list'), + path('search/', DeviceSearchView.as_view(), name='device-search'), +] + + diff --git a/map/views.py b/map/views.py new file mode 100644 index 00000000..830920c1 --- /dev/null +++ b/map/views.py @@ -0,0 +1,51 @@ +import os +from django.shortcuts import render +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import generics, status +from map.models import Device +from .serializers import DeviceSerializer +from django.shortcuts import render +from .models import Device + +# Load API key from environment variable +GOOGLE_MAPS_API_KEY = os.getenv('GOOGLE_MAPS_API_KEY') +GEOCODE_URL = 'https://maps.googleapis.com/maps/api/geocode/json' + + +def map_view(request): + devices = list(Device.objects.values('latitude', 'longitude', 'address', 'status')) + return render(request, 'map/map_template.html', {'devices': devices}) + +class MapDeviceListView(generics.ListCreateAPIView): + 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) + +class DeviceSearchView(APIView): + def get(self, request): + latitude = request.query_params.get('latitude') + longitude = request.query_params.get('longitude') + + if not latitude or not longitude: + return Response({'error': 'Latitude and Longitude are required'}, status=status.HTTP_400_BAD_REQUEST) + + try: + latitude = float(latitude) + longitude = float(longitude) + nearby_devices = Device.objects.filter( + latitude__range=(latitude - 0.01, latitude + 0.01), + longitude__range=(longitude - 0.01, longitude + 0.01) + ) + serializer = DeviceSerializer(nearby_devices, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except ValueError: + return Response({'error': 'Invalid latitude or longitude'}, status=status.HTTP_400_BAD_REQUEST) + except requests.RequestException as e: + return Response({'error': f'Network error: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) \ No newline at end of file diff --git a/notification/__init__.py b/notification/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/notification/admin.py b/notification/admin.py new file mode 100644 index 00000000..d94563a3 --- /dev/null +++ b/notification/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from .models import Notification +admin.site.register(Notification) +# Register your models here. diff --git a/notification/apps.py b/notification/apps.py new file mode 100644 index 00000000..8757bbe5 --- /dev/null +++ b/notification/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class NotificationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'notification' diff --git a/notification/migrations/0001_initial.py b/notification/migrations/0001_initial.py new file mode 100644 index 00000000..951de316 --- /dev/null +++ b/notification/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-09-14 13:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('title', models.CharField(max_length=200)), + ('message', models.TextField()), + ('type', models.CharField(choices=[('info', 'Information'), ('warn', 'Warning'), ('error', 'Error'), ('success', 'Success')], max_length=255)), + ('created_at', models.DateField(auto_now_add=True)), + ], + ), + ] diff --git a/notification/migrations/__init__.py b/notification/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/notification/models.py b/notification/models.py new file mode 100644 index 00000000..841c0c3a --- /dev/null +++ b/notification/models.py @@ -0,0 +1,17 @@ +from django.db import models + +NOTIFICATION_TYPES = [ + ('info', 'Information'), + ('warn', 'Warning'), + ('error', 'Error'), + ('success', 'Success'), +] +class Notification(models.Model): + id = models.AutoField(primary_key=True) + title = models.CharField(max_length=200) + message = models.TextField() + type = models.CharField(max_length=255, choices=NOTIFICATION_TYPES) + created_at = models.DateField(auto_now_add=True) + def __str__(self): + return self.title +# Create your models here. diff --git a/notification/tests.py b/notification/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/notification/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/notification/views.py b/notification/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/notification/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/sensor/__init__.py b/sensor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sensor/admin.py b/sensor/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/sensor/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/sensor/apps.py b/sensor/apps.py new file mode 100644 index 00000000..ec892d44 --- /dev/null +++ b/sensor/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SensorConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'sensor' diff --git a/sensor/migrations/0001_initial.py b/sensor/migrations/0001_initial.py new file mode 100644 index 00000000..f57554af --- /dev/null +++ b/sensor/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-09-14 13:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Sensor', + fields=[ + ('Sensor_ID', models.AutoField(primary_key=True, serialize=False)), + ('Type', models.CharField(max_length=255)), + ('Location', models.CharField(default='karen', max_length=255)), + ('Status', models.CharField(max_length=100)), + ('Time_Date', models.DateField(auto_now_add=True)), + ], + ), + ] diff --git a/sensor/migrations/__init__.py b/sensor/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sensor/models.py b/sensor/models.py new file mode 100644 index 00000000..69f749f5 --- /dev/null +++ b/sensor/models.py @@ -0,0 +1,16 @@ +from django.db import models +from django.db import models +# from django.utils import timezone + +class Sensor(models.Model): + Sensor_ID = models.AutoField(primary_key=True) + Type = models.CharField(max_length=255) + Location=models.CharField(max_length=255,default='karen') + Status = models.CharField(max_length=100) + Time_Date=models.DateField(auto_now_add=True) + def __str__(self): + return f"Sensor {self.Sensor_ID} - {self.Type}" + +# Create your models here. + +# Create your models here. diff --git a/sensor/tests.py b/sensor/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/sensor/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/sensor/views.py b/sensor/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/sensor/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From b8b49d7f574a7b4f2d148dc315b43e44b3b9714c Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Mon, 16 Sep 2024 11:30:46 +0300 Subject: [PATCH 06/27] add test files --- api/serializers.py | 4 +- api/tests.py | 3 - api/views.py | 10 --- aquasens/settings.py | 4 - authentication/models.py | 2 + authentication/tests.py | 3 - authentication/views.py | 2 - datamonitoring/models.py | 4 - datamonitoring/tests.py | 94 +++++++++++++++++++- drainagesystem/models.py | 11 ++- drainagesystem/tests.py | 93 +++++++++++++++++++- map/models.py | 2 +- map/templates/map/map_templates.html | 106 ---------------------- map/tests.py | 72 ++++++++++++++- notification/tests.py | 127 ++++++++++++++++++++++++++- sensor/models.py | 3 - sensor/tests.py | 73 ++++++++++++++- user/models.py | 3 - user/tests.py | 127 ++++++++++++++++++++++++++- 19 files changed, 592 insertions(+), 151 deletions(-) delete mode 100644 map/templates/map/map_templates.html diff --git a/api/serializers.py b/api/serializers.py index 73e0a291..a4e79ac9 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -3,15 +3,13 @@ from django.contrib.auth.models import User as DjangoUser from datamonitoring.models import MonitoringData from drainagesystem.models import DrainageSystem -from rest_framework import serializers from sensor.models import Sensor -from rest_framework import serializers from map.models import Device from notification.models import Notification class UserSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=True) - + class Meta: model = User fields = ['id', 'first_name', 'last_name', 'phone_number', 'email', 'role', 'password'] diff --git a/api/tests.py b/api/tests.py index 7ce503c2..e69de29b 100644 --- a/api/tests.py +++ b/api/tests.py @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/api/views.py b/api/views.py index 10159644..d26b42b2 100644 --- a/api/views.py +++ b/api/views.py @@ -22,11 +22,6 @@ from notification.models import Notification from .serializers import NotificationSerializer - - - - - logger = logging.getLogger(__name__) class UserListView(APIView): @@ -49,11 +44,6 @@ def get(self, request, id): serializer = UserSerializer(user) logger.info(f'User with ID {id} retrieved successfully.') return Response(serializer.data) - - - - - class RegisterView(APIView): def post(self, request): serializer = UserSerializer(data=request.data) diff --git a/aquasens/settings.py b/aquasens/settings.py index 0263542a..981c5d71 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -162,9 +162,5 @@ REDIRECT_URI = 'http://localhost:8000/auth/callback/' - - - - DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/authentication/models.py b/authentication/models.py index 71a83623..a4a82b85 100644 --- a/authentication/models.py +++ b/authentication/models.py @@ -1,3 +1,5 @@ from django.db import models + + # Create your models here. diff --git a/authentication/tests.py b/authentication/tests.py index 7ce503c2..e69de29b 100644 --- a/authentication/tests.py +++ b/authentication/tests.py @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/authentication/views.py b/authentication/views.py index 17012402..14d5ae75 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -1,6 +1,4 @@ from django.shortcuts import render - - import json from authlib.integrations.django_client import OAuth from django.conf import settings diff --git a/datamonitoring/models.py b/datamonitoring/models.py index f2cc1624..02248eda 100644 --- a/datamonitoring/models.py +++ b/datamonitoring/models.py @@ -1,8 +1,4 @@ from django.db import models - -# Create your models here. -from django.db import models - class MonitoringData(models.Model): monitoring_id = models.AutoField(primary_key=True) user_id = models.ForeignKey('auth.User', on_delete=models.CASCADE) diff --git a/datamonitoring/tests.py b/datamonitoring/tests.py index 7ce503c2..b490a067 100644 --- a/datamonitoring/tests.py +++ b/datamonitoring/tests.py @@ -1,3 +1,95 @@ +from decimal import Decimal from django.test import TestCase +from django.utils import timezone +from .models import MonitoringData +from django.contrib.auth.models import User +from django.core.exceptions import ValidationError +from django.db.utils import IntegrityError -# Create your tests here. +class MonitoringDataModelTest(TestCase): + + def setUp(self): + self.user = User.objects.create_user( + username='user', + email='daisychekuiri@gmail.com', + password='2003rt', + ) + + # This involves Creating monitoring data with valid values + self.monitoring_data = MonitoringData.objects.create( + user_id=self.user, + drainage_id=1, + water_level=Decimal('10.5'), + water_pressure=Decimal('2.3'), + timestamp=timezone.now() + ) + + def test_monitoring_data_creation(self): + # Happy path : which Ensures that monitoring data is created correctly + monitoring_data = MonitoringData.objects.get(monitoring_id=self.monitoring_data.monitoring_id) + self.assertEqual(monitoring_data.user_id, self.user) + self.assertEqual(monitoring_data.drainage_id, 1) + self.assertEqual(monitoring_data.water_level, Decimal('10.5')) + self.assertEqual(monitoring_data.water_pressure, Decimal('2.3')) + self.assertIsInstance(monitoring_data.timestamp, timezone.datetime) + + def test_string_representation(self): + # Happy path : Which Ensures that string representation is correct + self.assertEqual(str(self.monitoring_data), f'Monitoring Data {self.monitoring_data.monitoring_id}') + + def test_monitoring_data_creation_missing_user(self): + # Unhappy path test: which Verify's that IntegrityError is raised if user_id is missing + with self.assertRaises(IntegrityError): + MonitoringData.objects.create( + user_id=None, + drainage_id=1, + water_level=Decimal('10.5'), + water_pressure=Decimal('2.3'), + timestamp=timezone.now() + ) + + def test_monitoring_data_creation_missing_drainage_id(self): + # Unhappy path test: which Verify IntegrityError is raised if drainage_id is missing + with self.assertRaises(IntegrityError): + MonitoringData.objects.create( + user_id=self.user, + drainage_id=None, + water_level=Decimal('10.5'), + water_pressure=Decimal('2.3'), + timestamp=timezone.now() + ) + + def test_monitoring_data_creation_invalid_water_level(self): + # Unhappy path test: Verify ValidationError is raised for negative water_level + + monitoring_data = MonitoringData( + user_id=self.user, + drainage_id=1, + water_level=Decimal('-10.5'), + water_pressure=Decimal('2.3'), + timestamp=timezone.now() + ) + monitoring_data.full_clean() + + def test_monitoring_data_creation_invalid_water_pressure(self): + # Unhappy path test: which Verify ValidationError is raised for negative water_pressure + + monitoring_data = MonitoringData( + user_id=self.user, + drainage_id=1, + water_level=Decimal('10.5'), + water_pressure=Decimal('-2.3'), + timestamp=timezone.now() + ) + monitoring_data.full_clean() + + def test_monitoring_data_creation_missing_timestamp(self): + # Unhappy path test:which Verify IntegrityError is raised if timestamp is missing + + MonitoringData.objects.create( + user_id=self.user, + drainage_id=1, + water_level=Decimal('10.5'), + water_pressure=Decimal('2.3'), + timestamp=None + ) diff --git a/drainagesystem/models.py b/drainagesystem/models.py index e5c60704..916bad14 100644 --- a/drainagesystem/models.py +++ b/drainagesystem/models.py @@ -1,11 +1,16 @@ from django.db import models + class DrainageSystem(models.Model): Drainage_ID = models.AutoField(primary_key=True) Location = models.CharField(max_length=100) - waterlevel=models.DecimalField(max_digits=10, decimal_places=2) - waterpressure=models.DecimalField(max_digits=10, decimal_places=2) + waterlevel = models.DecimalField(max_digits=10, decimal_places=2) + waterpressure = models.DecimalField(max_digits=10, decimal_places=2) Status = models.CharField(max_length=100) - Timestamp=models.DateTimeField(auto_now_add=True) + Timestamp = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f'Drainagesystem {self.Drainage_ID}' + # Create your models here. diff --git a/drainagesystem/tests.py b/drainagesystem/tests.py index 7ce503c2..5838f15e 100644 --- a/drainagesystem/tests.py +++ b/drainagesystem/tests.py @@ -1,3 +1,94 @@ from django.test import TestCase +from .models import DrainageSystem +from django.core.exceptions import ValidationError +from django.db.utils import IntegrityError +from django.utils import timezone -# Create your tests here. +class DrainageSystemModelTest(TestCase): + + def setUp(self): + self.drainage_system = DrainageSystem.objects.create( + Location="Karen", + waterlevel=5.75, + waterpressure= 4.45, + Status="Active" + ) + + def test_drainage_system_creation(self): + drainage_system = DrainageSystem.objects.get(Drainage_ID=self.drainage_system.Drainage_ID) + self.assertEqual(drainage_system.Location, "Karen") + self.assertEqual(drainage_system.waterlevel, 5.75) + self.assertEqual(drainage_system.Status, "Active") + self.assertIsNotNone(drainage_system.Timestamp) + + def test_drainage_system_string_representation(self): + self.assertEqual(str(self.drainage_system), f'Drainagesystem {self.drainage_system.Drainage_ID}') + + def test_drainage_system_creation_missing_location(self): + with self.assertRaises(IntegrityError): + DrainageSystem.objects.create( + Location=None, + waterlevel=5.75, + waterpressure=3.45, + Status="Active" + ) + + def test_drainage_system_creation_missing_waterlevel(self): + with self.assertRaises(IntegrityError): + DrainageSystem.objects.create( + Location="Karen", + waterlevel=None, + waterpressure=3.45, + Status="Active" + ) + + def test_drainage_system_creation_missing_waterpressure(self): + with self.assertRaises(IntegrityError): + DrainageSystem.objects.create( + Location="Karen", + waterlevel=5.75, + waterpressure=None, + Status="Active" + ) + + def test_drainage_system_creation_missing_status(self): + with self.assertRaises(IntegrityError): + DrainageSystem.objects.create( + Location="Karen", + waterlevel=5.75, + waterpressure=3.45, + Status=None + ) + + def test_invalid_waterlevel(self): + drainage_system = DrainageSystem( + Location="Karen", + waterlevel=-5.75, + waterpressure=3.45, + Status="Active" + ) + with self.assertRaises(ValidationError): + drainage_system.full_clean() + + def test_invalid_waterpressure(self): + drainage_system = DrainageSystem( + Location="Karen", + waterlevel=5.75, + waterpressure=-3.45, + Status="Active" + ) + with self.assertRaises(ValidationError): + drainage_system.full_clean() + + def test_long_location(self): + long_location = "x" * 200 + drainage_system = DrainageSystem( + Location=long_location, + waterlevel=5.75, + waterpressure=3.45, + Status="Active" + ) + with self.assertRaises(ValidationError): + drainage_system.full_clean() + + \ No newline at end of file diff --git a/map/models.py b/map/models.py index d76f8adc..32147e3f 100644 --- a/map/models.py +++ b/map/models.py @@ -5,7 +5,7 @@ class Device(models.Model): address = models.CharField(max_length=255) latitude = models.FloatField() longitude = models.FloatField() - status = models.CharField(max_length=255) + status = models.CharField(max_length=255, null=False, blank=False) type = models.CharField(max_length=50, default ='sensor') def __str__(self): diff --git a/map/templates/map/map_templates.html b/map/templates/map/map_templates.html deleted file mode 100644 index f4d984b4..00000000 --- a/map/templates/map/map_templates.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - Device Map - - - - - -

Locations of My Devices

- -
- - - - - - - - \ No newline at end of file diff --git a/map/tests.py b/map/tests.py index 7ce503c2..b399123c 100644 --- a/map/tests.py +++ b/map/tests.py @@ -1,3 +1,73 @@ from django.test import TestCase +from django.db import IntegrityError +from map.models import Device -# Create your tests here. +class DeviceModelTest(TestCase): + def setUp(self): + # This involves creating of normal device with a valid case + self.device = Device.objects.create( + address='123 Nairobi Street', + latitude=-1.286389, + longitude=36.817223, + status='active', + type='sensor' + ) + + def test_device_creation(self): + # This here invovles testing that the device is created successfully with valid data + device = self.device + self.assertIsInstance(device, Device) + self.assertEqual(device.address, '123 Nairobi Street') + self.assertEqual(device.latitude, -1.286389) + self.assertEqual(device.longitude, 36.817223) + self.assertEqual(device.status, 'active') + self.assertEqual(device.type, 'sensor') + + def test_device_str_method(self): + + device = self.device + expected_str = f"{device.type} - {device.address}" + self.assertEqual(str(device), expected_str) + + def test_empty_address_field(self): + # This here involves testing device when the address field is empty + Device.objects.create( + address='', + latitude=-1.286389, + longitude=36.817223, + status='active', + type='sensor' + ) + + def test_missing_latitude_field(self): + # This test device creation when the latitude field is Null or missing. + with self.assertRaises(IntegrityError): + Device.objects.create( + address='456 Another Street', + latitude=None, + longitude=36.817223, + status='active', + type='sensor' + ) + + def test_invalid_latitude_value(self): + # This Tests that a ValueError is raised when attempting to create a device with an invalid latitude value whereby a latitude should be a float instead a string is provided in this case + with self.assertRaises(ValueError): + Device.objects.create( + address='789 Invalid Lat Street', + latitude='invalid_latitude', + longitude=36.817223, + status='active', + type='sensor' + ) + + def test_empty_status_field(self): + # This here Tests that when creating a device with an empty status field , it raises a ValidationError + # which whereby the 'status' field is set to an empty string, which should be invalid + Device.objects.create( + address='999 Status Street', + latitude=-1.286389, + longitude=36.817223, + status='', + type='sensor' + ) diff --git a/notification/tests.py b/notification/tests.py index 7ce503c2..98beb640 100644 --- a/notification/tests.py +++ b/notification/tests.py @@ -1,3 +1,128 @@ from django.test import TestCase +from .models import Notification +from django.core.exceptions import ValidationError +from django.db.utils import IntegrityError + +class NotificationModelTest(TestCase): + + def setUp(self): + self.notification = Notification.objects.create( + title="Notification", + message="we are testing our Notification.", + type="info" + ) + + def test_notification_creation(self): + notification = Notification.objects.get(id=self.notification.id) + self.assertEqual(notification.title, "Notification") + self.assertEqual(notification.message, "we are testing our Notification.") + self.assertEqual(notification.type, "info") + self.assertIsNotNone(notification.created_at) + + def test_notification_string_representation(self): + self.assertEqual(str(self.notification), "Notification") + + def test_notification_type_choices(self): + valid_types = [choice[0] for choice in Notification._meta.get_field('type').choices] + self.assertIn('info', valid_types) + self.assertIn('warn', valid_types) + self.assertIn('error', valid_types) + self.assertIn('success', valid_types) + + def test_notification_creation_missing_title(self): + with self.assertRaises(IntegrityError): + Notification.objects.create( + title=None, + message="This notification has no title.", + type="error" + ) + + def test_invalid_type_choice(self): + notification = Notification( + title="Invalid Type Notification", + message="This has an invalid type.", + type="invalid" + ) + with self.assertRaises(ValidationError): + notification.full_clean() + + def test_long_title(self): + long_title = "x" * 300 + notification = Notification( + title=long_title, + message="Welcome back to our home.", + type="info" + ) + with self.assertRaises(ValidationError): + notification.full_clean() + + def test_notification_creation_missing_message(self): + with self.assertRaises(IntegrityError): + Notification.objects.create( + title="No Message Notification", + message=None, + type="warn" + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# from django.test import TestCase +# from .models import Notification +# from django.utils import timezone + +# class NotificationModelTest(TestCase): + +# def setUp(self): + +# self.notification = Notification.objects.create( +# title="Test Notification", +# message="This is a message for testing.", +# type="info", +# created_at=timezone.now().date() +# ) + +# def test_notification_creation(self): + +# notification = Notification.objects.get(id=self.notification.id) +# self.assertEqual(notification.title, "Test Notification") +# self.assertEqual(notification.message, "This is a message for testing.") +# self.assertEqual(notification.type, "info") +# self.assertEqual(notification.created_at, timezone.now().date()) + +# def test_notification_string_representation(self): + +# self.assertEqual(str(self.notification), "Test Notification") + +# def test_notification_type_choices(self): + +# valid_types = [choice[0] for choice in Notification._meta.get_field('type').choices] +# self.assertIn('info', valid_types) +# self.assertIn('warn', valid_types) +# self.assertIn('error', valid_types) +# self.assertIn('success', valid_types) -# Create your tests here. diff --git a/sensor/models.py b/sensor/models.py index 69f749f5..891d728b 100644 --- a/sensor/models.py +++ b/sensor/models.py @@ -1,6 +1,4 @@ from django.db import models -from django.db import models -# from django.utils import timezone class Sensor(models.Model): Sensor_ID = models.AutoField(primary_key=True) @@ -13,4 +11,3 @@ def __str__(self): # Create your models here. -# Create your models here. diff --git a/sensor/tests.py b/sensor/tests.py index 7ce503c2..c4cbd2ae 100644 --- a/sensor/tests.py +++ b/sensor/tests.py @@ -1,3 +1,74 @@ from django.test import TestCase +from .models import Sensor +from django.utils import timezone +from django.core.exceptions import ValidationError +from django.db.utils import IntegrityError -# Create your tests here. +class SensorModelTest(TestCase): + + def setUp(self): + # Happy Path:this involves Setting up a valid Sensor instance for testing + self.sensor = Sensor.objects.create( + Type="Waterpressure", + Location="Nairobi", + Status="Active", + Time_Date=timezone.now().date() + ) + + def test_sensor_creation(self): + # Happy Path: this Ensures that Sensor is created correctly with all valid fields + sensor = Sensor.objects.get(Sensor_ID=self.sensor.Sensor_ID) + self.assertEqual(sensor.Type, "Waterpressure") + self.assertEqual(sensor.Location, "Nairobi") + self.assertEqual(sensor.Status, "Active") + self.assertEqual(sensor.Time_Date, self.sensor.Time_Date) + + def test_sensor_string_representation(self): + # Happy Path: this Ensures that the string representation of the Sensor is correct + self.assertEqual(str(self.sensor), f"Sensor {self.sensor.Sensor_ID} - Waterpressure") + + def test_default_location(self): + # Happy Path: It involves Creating a Sensor without specifying Location, it verify default value + default_sensor = Sensor.objects.create( + Type="Water Pressure", + Status="Inactive" + ) + self.assertEqual(default_sensor.Location, "karen") + + def test_sensor_creation_missing_type(self): + # Unhappy Path: this Verify IntegrityError is raised if Type is missing + with self.assertRaises(IntegrityError): + Sensor.objects.create( + Type=None, # Missing Type + Location="Karen", + Status="Active" + ) + + def test_sensor_creation_missing_status(self): + # Unhappy Path: this here Verify IntegrityError is raised if Status is missing + with self.assertRaises(IntegrityError): + Sensor.objects.create( + Type="Waterpressure", + Location="Nasra", + Status=None + ) + + def test_sensor_creation_invalid_location(self): + # Unhappy Path: this Verify ValidationError is raised for invalid Location + with self.assertRaises(ValidationError): + sensor = Sensor( + Type="Waterpressure", + Location='Karen' * 300, + Status="Active" + ) + sensor.full_clean() + + def test_sensor_creation_invalid_type(self): + # Unhappy Path: this here Verify ValidationError is raised for invalid Type + with self.assertRaises(ValidationError): + sensor = Sensor( + Type='waterlevel' * 300, + Location="Nasra", + Status="Active" + ) + sensor.full_clean() diff --git a/user/models.py b/user/models.py index af7fa9ea..6d171b42 100644 --- a/user/models.py +++ b/user/models.py @@ -1,7 +1,4 @@ from django.db import models - -# Create your models here. -from django.db import models from django.contrib.auth.models import User as DjangoUser Estate_Associate = 'Estate_Associate' diff --git a/user/tests.py b/user/tests.py index 7ce503c2..eb5e121e 100644 --- a/user/tests.py +++ b/user/tests.py @@ -1,3 +1,128 @@ from django.test import TestCase +from django.contrib.auth.models import User as DjangoUser +from .models import User, Estate_Associate, ADMIN +from django.db.utils import IntegrityError +from django.core.exceptions import ValidationError -# Create your tests here. +class UserModelTest(TestCase): + + def setUp(self): + + self.django_user = DjangoUser.objects.create_user( + username="Alinemutesi", + password="testpassword1563" + ) + + + self.user = User.objects.create( + user=self.django_user, + first_name="Mutesi", + last_name="Aline", + phone_number="1234567890", + email="alinemutesi@gmail.com", + password="testpassword", + role=Estate_Associate, + ) + + def test_user_creation(self): + + self.assertEqual(self.user.first_name, "Mutesi") + self.assertEqual(self.user.last_name, "Aline") + self.assertEqual(self.user.phone_number, "1234567890") + self.assertEqual(self.user.email, "alinemutesi@gmail.com") + self.assertEqual(self.user.role, Estate_Associate) + + def test_default_role(self): + + self.assertEqual(self.user.role, Estate_Associate) + + def test_role_choices(self): + + self.user.role = ADMIN + self.user.save() + self.assertEqual(self.user.role, ADMIN) + + def test_str_representation(self): + + self.assertEqual(str(self.user), "Mutesi Aline (alinemutesi@gmail.com)") + + def test_unique_email(self): + # Unhappy Path: Check if creating a User with a duplicate email raises an IntegrityError + with self.assertRaises(IntegrityError): + User.objects.create( + user=self.django_user, + first_name="linet", + last_name="mkandoe", + phone_number="0987654321", + email="alinemutesi@gmail.com", + password="linet56678", + role=Estate_Associate, + ) + + def test_missing_email(self): + # Unhappy Path: Verify if missing email raises ValidationError + with self.assertRaises(ValidationError): + user = User( + user=self.django_user, + first_name="Nyabang", + last_name="Wech", + phone_number="1234567890", + password="testpassword200366", + role=Estate_Associate, + ) + user.full_clean() + + def test_missing_role(self): + # Unhappy Path: Check if missing role raises ValidationError + with self.assertRaises(ValidationError): + user = User( + user=self.django_user, + first_name="Nyabang", + last_name="wech", + phone_number="1234567890", + email="nyabangwech@gmail.com", + password="testpassword200366", + ) + user.full_clean() + + def test_invalid_role(self): + # Unhappy Path: Verify if setting an invalid role raises ValidationError + with self.assertRaises(ValidationError): + user = User( + user=self.django_user, + first_name="Nyabang", + last_name="wech", + phone_number="1234567890", + email="nyabangwech@gmail.com", + password="testpassword200366", + role="invalid_role" + ) + user.full_clean() + + def test_long_phone_number(self): + # Unhappy Path: Test if a phone number exceeding max_length raises ValidationError + with self.assertRaises(ValidationError): + user = User( + user=self.django_user, + first_name="Nyabang", + last_name="wech", + phone_number="1" * 21, + email="nyabangwech@gmail.com", + password="testpassword200366", + role=Estate_Associate, + ) + user.full_clean() + + def test_long_password(self): + # Unhappy Path: Test if a password exceeding max_length raises ValidationError + with self.assertRaises(ValidationError): + user = User( + user=self.django_user, + first_name="Nyabang", + last_name="Wech", + phone_number="1234567890", + email="nyabangwech@gmail.com", + password="p" * 201, + role=Estate_Associate, + ) + user.full_clean() From 61a9dd62a30facaf45fd17cc1bc1d79d4cc1a456 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Mon, 16 Sep 2024 12:04:06 +0300 Subject: [PATCH 07/27] made changes --- api/urls.py | 4 ++-- aquasens/settings.py | 7 ------- aquasens/urls.py | 3 +-- datamonitoring/tests.py | 13 ++++++------- map/tests.py | 10 +++------- map/urls.py | 2 +- sensor/tests.py | 13 +++++-------- user/tests.py | 6 ------ 8 files changed, 18 insertions(+), 40 deletions(-) diff --git a/api/urls.py b/api/urls.py index cd80c2d7..9191ad65 100644 --- a/api/urls.py +++ b/api/urls.py @@ -12,7 +12,7 @@ router.register(r'monitoring-data', MonitoringDataViewSet) router = DefaultRouter() router.register(r'notifications', NotificationViewSet) -urlpatterns = router.urls # Use the router's URLs +urlpatterns = router.urls urlpatterns = [ path('users/', UserListView.as_view(), name='user-list'), @@ -23,7 +23,7 @@ path('', include(router.urls)), path('drainage-systems/', DrainageSystemList.as_view(), name='drainage-system-list'), path('drainage-systems//', DrainageSystemDetail.as_view(), name='drainage-system-detail'), - path('api', map_view, name='map_view'), # This matches the `map/` URL + path('api', map_view, name='map_view'), path('map/', map_view, name='map_view'), path('devices/', MapDeviceListView.as_view(), name='device-list'), path('search/', DeviceSearchView.as_view(), name='device-search'), diff --git a/aquasens/settings.py b/aquasens/settings.py index 981c5d71..fa9b97d2 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -18,15 +18,8 @@ from dotenv import load_dotenv, find_dotenv from pathlib import Path -# Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent TEMPLATE_DIR = os.path.join(BASE_DIR, "drainage", "templates") - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'django-insecure-9bv3ge1%z6^itsr^q#$ssef0so*!ex&i#5*e*gg+-^ae9@tgn9' # SECURITY WARNING: don't run with debug turned on in production! diff --git a/aquasens/urls.py b/aquasens/urls.py index 35254614..cee7a63e 100644 --- a/aquasens/urls.py +++ b/aquasens/urls.py @@ -26,6 +26,5 @@ path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('map/', include('map.urls')), - # path('api/',include('notification.urls')), - + ] \ No newline at end of file diff --git a/datamonitoring/tests.py b/datamonitoring/tests.py index b490a067..02d8ac98 100644 --- a/datamonitoring/tests.py +++ b/datamonitoring/tests.py @@ -14,8 +14,7 @@ def setUp(self): email='daisychekuiri@gmail.com', password='2003rt', ) - - # This involves Creating monitoring data with valid values + self.monitoring_data = MonitoringData.objects.create( user_id=self.user, drainage_id=1, @@ -25,7 +24,7 @@ def setUp(self): ) def test_monitoring_data_creation(self): - # Happy path : which Ensures that monitoring data is created correctly + monitoring_data = MonitoringData.objects.get(monitoring_id=self.monitoring_data.monitoring_id) self.assertEqual(monitoring_data.user_id, self.user) self.assertEqual(monitoring_data.drainage_id, 1) @@ -34,7 +33,7 @@ def test_monitoring_data_creation(self): self.assertIsInstance(monitoring_data.timestamp, timezone.datetime) def test_string_representation(self): - # Happy path : Which Ensures that string representation is correct + self.assertEqual(str(self.monitoring_data), f'Monitoring Data {self.monitoring_data.monitoring_id}') def test_monitoring_data_creation_missing_user(self): @@ -49,7 +48,7 @@ def test_monitoring_data_creation_missing_user(self): ) def test_monitoring_data_creation_missing_drainage_id(self): - # Unhappy path test: which Verify IntegrityError is raised if drainage_id is missing + with self.assertRaises(IntegrityError): MonitoringData.objects.create( user_id=self.user, @@ -60,7 +59,7 @@ def test_monitoring_data_creation_missing_drainage_id(self): ) def test_monitoring_data_creation_invalid_water_level(self): - # Unhappy path test: Verify ValidationError is raised for negative water_level + monitoring_data = MonitoringData( user_id=self.user, @@ -84,7 +83,7 @@ def test_monitoring_data_creation_invalid_water_pressure(self): monitoring_data.full_clean() def test_monitoring_data_creation_missing_timestamp(self): - # Unhappy path test:which Verify IntegrityError is raised if timestamp is missing + MonitoringData.objects.create( user_id=self.user, diff --git a/map/tests.py b/map/tests.py index b399123c..c6d3eb9a 100644 --- a/map/tests.py +++ b/map/tests.py @@ -4,7 +4,7 @@ class DeviceModelTest(TestCase): def setUp(self): - # This involves creating of normal device with a valid case + self.device = Device.objects.create( address='123 Nairobi Street', latitude=-1.286389, @@ -14,7 +14,7 @@ def setUp(self): ) def test_device_creation(self): - # This here invovles testing that the device is created successfully with valid data + device = self.device self.assertIsInstance(device, Device) self.assertEqual(device.address, '123 Nairobi Street') @@ -30,7 +30,7 @@ def test_device_str_method(self): self.assertEqual(str(device), expected_str) def test_empty_address_field(self): - # This here involves testing device when the address field is empty + Device.objects.create( address='', latitude=-1.286389, @@ -40,7 +40,6 @@ def test_empty_address_field(self): ) def test_missing_latitude_field(self): - # This test device creation when the latitude field is Null or missing. with self.assertRaises(IntegrityError): Device.objects.create( address='456 Another Street', @@ -51,7 +50,6 @@ def test_missing_latitude_field(self): ) def test_invalid_latitude_value(self): - # This Tests that a ValueError is raised when attempting to create a device with an invalid latitude value whereby a latitude should be a float instead a string is provided in this case with self.assertRaises(ValueError): Device.objects.create( address='789 Invalid Lat Street', @@ -62,8 +60,6 @@ def test_invalid_latitude_value(self): ) def test_empty_status_field(self): - # This here Tests that when creating a device with an empty status field , it raises a ValidationError - # which whereby the 'status' field is set to an empty string, which should be invalid Device.objects.create( address='999 Status Street', latitude=-1.286389, diff --git a/map/urls.py b/map/urls.py index 069653c8..377f93a7 100644 --- a/map/urls.py +++ b/map/urls.py @@ -3,7 +3,7 @@ from .views import MapDeviceListView, DeviceSearchView, map_view urlpatterns = [ - path('api', map_view, name='map_view'), # This matches the `map/` URL + path('api', map_view, name='map_view'), path('map/', map_view, name='map_view'), path('devices/', MapDeviceListView.as_view(), name='device-list'), path('search/', DeviceSearchView.as_view(), name='device-search'), diff --git a/sensor/tests.py b/sensor/tests.py index c4cbd2ae..f53e8ada 100644 --- a/sensor/tests.py +++ b/sensor/tests.py @@ -7,7 +7,7 @@ class SensorModelTest(TestCase): def setUp(self): - # Happy Path:this involves Setting up a valid Sensor instance for testing + self.sensor = Sensor.objects.create( Type="Waterpressure", Location="Nairobi", @@ -16,7 +16,7 @@ def setUp(self): ) def test_sensor_creation(self): - # Happy Path: this Ensures that Sensor is created correctly with all valid fields + sensor = Sensor.objects.get(Sensor_ID=self.sensor.Sensor_ID) self.assertEqual(sensor.Type, "Waterpressure") self.assertEqual(sensor.Location, "Nairobi") @@ -24,7 +24,7 @@ def test_sensor_creation(self): self.assertEqual(sensor.Time_Date, self.sensor.Time_Date) def test_sensor_string_representation(self): - # Happy Path: this Ensures that the string representation of the Sensor is correct + self.assertEqual(str(self.sensor), f"Sensor {self.sensor.Sensor_ID} - Waterpressure") def test_default_location(self): @@ -36,16 +36,15 @@ def test_default_location(self): self.assertEqual(default_sensor.Location, "karen") def test_sensor_creation_missing_type(self): - # Unhappy Path: this Verify IntegrityError is raised if Type is missing + with self.assertRaises(IntegrityError): Sensor.objects.create( - Type=None, # Missing Type + Type=None, Location="Karen", Status="Active" ) def test_sensor_creation_missing_status(self): - # Unhappy Path: this here Verify IntegrityError is raised if Status is missing with self.assertRaises(IntegrityError): Sensor.objects.create( Type="Waterpressure", @@ -54,7 +53,6 @@ def test_sensor_creation_missing_status(self): ) def test_sensor_creation_invalid_location(self): - # Unhappy Path: this Verify ValidationError is raised for invalid Location with self.assertRaises(ValidationError): sensor = Sensor( Type="Waterpressure", @@ -64,7 +62,6 @@ def test_sensor_creation_invalid_location(self): sensor.full_clean() def test_sensor_creation_invalid_type(self): - # Unhappy Path: this here Verify ValidationError is raised for invalid Type with self.assertRaises(ValidationError): sensor = Sensor( Type='waterlevel' * 300, diff --git a/user/tests.py b/user/tests.py index eb5e121e..f10795ad 100644 --- a/user/tests.py +++ b/user/tests.py @@ -47,7 +47,6 @@ def test_str_representation(self): self.assertEqual(str(self.user), "Mutesi Aline (alinemutesi@gmail.com)") def test_unique_email(self): - # Unhappy Path: Check if creating a User with a duplicate email raises an IntegrityError with self.assertRaises(IntegrityError): User.objects.create( user=self.django_user, @@ -60,7 +59,6 @@ def test_unique_email(self): ) def test_missing_email(self): - # Unhappy Path: Verify if missing email raises ValidationError with self.assertRaises(ValidationError): user = User( user=self.django_user, @@ -73,7 +71,6 @@ def test_missing_email(self): user.full_clean() def test_missing_role(self): - # Unhappy Path: Check if missing role raises ValidationError with self.assertRaises(ValidationError): user = User( user=self.django_user, @@ -86,7 +83,6 @@ def test_missing_role(self): user.full_clean() def test_invalid_role(self): - # Unhappy Path: Verify if setting an invalid role raises ValidationError with self.assertRaises(ValidationError): user = User( user=self.django_user, @@ -100,7 +96,6 @@ def test_invalid_role(self): user.full_clean() def test_long_phone_number(self): - # Unhappy Path: Test if a phone number exceeding max_length raises ValidationError with self.assertRaises(ValidationError): user = User( user=self.django_user, @@ -114,7 +109,6 @@ def test_long_phone_number(self): user.full_clean() def test_long_password(self): - # Unhappy Path: Test if a password exceeding max_length raises ValidationError with self.assertRaises(ValidationError): user = User( user=self.django_user, From 15e2c3dbf19d315068754fe5e70bea923ef3a158 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Mon, 16 Sep 2024 13:56:03 +0300 Subject: [PATCH 08/27] made changes --- api/urls.py | 4 ++- aquasens/settings.py | 1 + aquasens/urls.py | 2 +- notification/tests.py | 64 +------------------------------------------ 4 files changed, 6 insertions(+), 65 deletions(-) diff --git a/api/urls.py b/api/urls.py index 9191ad65..04d28c5f 100644 --- a/api/urls.py +++ b/api/urls.py @@ -15,6 +15,7 @@ urlpatterns = router.urls urlpatterns = [ + path('',include(router.urls)), path('users/', UserListView.as_view(), name='user-list'), path('users//', UserDetailView.as_view(), name='user-detail'), path('users/register/', RegisterView.as_view(), name='user-register'), @@ -26,10 +27,11 @@ path('api', map_view, name='map_view'), path('map/', map_view, name='map_view'), path('devices/', MapDeviceListView.as_view(), name='device-list'), - path('search/', DeviceSearchView.as_view(), name='device-search'), + path('search/', DeviceSearchView.as_view(), name='device-search'), path('sensors/', SensorListCreateView.as_view(), name='sensor-list-create'), path('sensors//', SensorDetailView.as_view(), name='sensor-detail'), path('notifications/', NotificationViewSet.as_view({'get': 'list', 'post': 'create'})), + path('datamonitoring/', MonitoringDataViewSet.as_view({'get': 'list', 'post': 'create'})), path('notifications//', NotificationViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})), diff --git a/aquasens/settings.py b/aquasens/settings.py index fa9b97d2..500a1934 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -157,3 +157,4 @@ REDIRECT_URI = 'http://localhost:8000/auth/callback/' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] diff --git a/aquasens/urls.py b/aquasens/urls.py index cee7a63e..d0646b2b 100644 --- a/aquasens/urls.py +++ b/aquasens/urls.py @@ -6,7 +6,7 @@ Examples: Function views 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') + 2. Add a URL to urlpatterns: p # Unhappy Path: Test if a password exceeding max_length raises ValidationErrorath('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') diff --git a/notification/tests.py b/notification/tests.py index 98beb640..05ffcb6b 100644 --- a/notification/tests.py +++ b/notification/tests.py @@ -12,7 +12,7 @@ def setUp(self): type="info" ) - def test_notification_creation(self): + def test_notificatn_creation(self): notification = Notification.objects.get(id=self.notification.id) self.assertEqual(notification.title, "Notification") self.assertEqual(notification.message, "we are testing our Notification.") @@ -64,65 +64,3 @@ def test_notification_creation_missing_message(self): type="warn" ) - - - - - - - - - - - - - - - - - - - - - - - - - - - -# from django.test import TestCase -# from .models import Notification -# from django.utils import timezone - -# class NotificationModelTest(TestCase): - -# def setUp(self): - -# self.notification = Notification.objects.create( -# title="Test Notification", -# message="This is a message for testing.", -# type="info", -# created_at=timezone.now().date() -# ) - -# def test_notification_creation(self): - -# notification = Notification.objects.get(id=self.notification.id) -# self.assertEqual(notification.title, "Test Notification") -# self.assertEqual(notification.message, "This is a message for testing.") -# self.assertEqual(notification.type, "info") -# self.assertEqual(notification.created_at, timezone.now().date()) - -# def test_notification_string_representation(self): - -# self.assertEqual(str(self.notification), "Test Notification") - -# def test_notification_type_choices(self): - -# valid_types = [choice[0] for choice in Notification._meta.get_field('type').choices] -# self.assertIn('info', valid_types) -# self.assertIn('warn', valid_types) -# self.assertIn('error', valid_types) -# self.assertIn('success', valid_types) - From 7e1305845c72a586d41bc843b869e668e3eee82b Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Mon, 16 Sep 2024 16:48:05 +0300 Subject: [PATCH 09/27] added formatting --- .pre-commit-config.yaml | 6 ++ api/apps.py | 4 +- api/serializers.py | 57 +++++++---- api/urls.py | 63 +++++++----- api/views.py | 111 ++++++++++++-------- aquasens/asgi.py | 2 +- aquasens/settings.py | 118 +++++++++++----------- aquasens/urls.py | 15 ++- aquasens/wsgi.py | 2 +- authentication/apps.py | 4 +- authentication/models.py | 1 - authentication/urls.py | 2 +- authentication/views.py | 1 - datamonitoring/admin.py | 3 +- datamonitoring/apps.py | 4 +- datamonitoring/migrations/0001_initial.py | 24 +++-- datamonitoring/models.py | 15 +-- datamonitoring/tests.py | 104 ++++++++++--------- drainagesystem/apps.py | 4 +- drainagesystem/migrations/0001_initial.py | 18 ++-- drainagesystem/models.py | 4 +- drainagesystem/tests.py | 71 +++++-------- manage.py | 4 +- map/admin.py | 4 +- map/apps.py | 4 +- map/migrations/0001_initial.py | 26 +++-- map/models.py | 7 +- map/serializers.py | 3 +- map/tests.py | 61 ++++++----- map/urls.py | 10 +- map/views.py | 35 ++++--- notification/admin.py | 1 + notification/apps.py | 4 +- notification/migrations/0001_initial.py | 27 +++-- notification/models.py | 13 ++- notification/tests.py | 51 +++++----- requirements.txt | 9 +- sensor/apps.py | 4 +- sensor/migrations/0001_initial.py | 16 ++- sensor/models.py | 14 +-- sensor/tests.py | 43 +++----- user/admin.py | 2 +- user/apps.py | 4 +- user/migrations/0001_initial.py | 43 ++++++-- user/models.py | 12 +-- user/tests.py | 31 +++--- 46 files changed, 564 insertions(+), 497 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..4e3c4537 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + language_version: python3 diff --git a/api/apps.py b/api/apps.py index 66656fd2..878e7d54 100644 --- a/api/apps.py +++ b/api/apps.py @@ -2,5 +2,5 @@ class ApiConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'api' + default_auto_field = "django.db.models.BigAutoField" + name = "api" diff --git a/api/serializers.py b/api/serializers.py index a4e79ac9..2493b89d 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from user .models import User , ROLE_CHOICES +from user.models import User, ROLE_CHOICES from django.contrib.auth.models import User as DjangoUser from datamonitoring.models import MonitoringData from drainagesystem.models import DrainageSystem @@ -7,58 +7,77 @@ from map.models import Device from notification.models import Notification + class UserSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=True) class Meta: model = User - fields = ['id', 'first_name', 'last_name', 'phone_number', 'email', 'role', 'password'] - read_only_fields = ['id'] + fields = [ + "id", + "first_name", + "last_name", + "phone_number", + "email", + "role", + "password", + ] + read_only_fields = ["id"] def create(self, validated_data): django_user = DjangoUser.objects.create_user( - username=validated_data['email'], - email=validated_data['email'], - password=validated_data['password'] + username=validated_data["email"], + email=validated_data["email"], + password=validated_data["password"], ) user = User.objects.create( user=django_user, - first_name=validated_data['first_name'], - last_name=validated_data['last_name'], - phone_number=validated_data['phone_number'], - email=validated_data['email'], - role=validated_data['role'] + first_name=validated_data["first_name"], + last_name=validated_data["last_name"], + phone_number=validated_data["phone_number"], + email=validated_data["email"], + role=validated_data["role"], ) return user + + class RoleSerializer(serializers.Serializer): user_id = serializers.IntegerField() # role = serializers.ChoiceField(choices=User.ROLE_CHOICES) + + class MonitoringDataSerializer(serializers.ModelSerializer): class Meta: model = MonitoringData - fields = ['monitoring_id', 'user_id', 'drainage_id', 'timestamp', 'water_level', 'water_pressure'] - - + fields = [ + "monitoring_id", + "user_id", + "drainage_id", + "timestamp", + "water_level", + "water_pressure", + ] + class DrainageSystemSerializer(serializers.ModelSerializer): class Meta: model = DrainageSystem - fields = '__all__' + fields = "__all__" class DeviceSerializer(serializers.ModelSerializer): class Meta: model = Device - fields = ['id', 'latitude', 'longitude', 'address', 'type'] + fields = ["id", "latitude", "longitude", "address", "type"] class SensorSerializer(serializers.ModelSerializer): class Meta: model = Sensor - fields = ['Sensor_ID', 'Type', 'Location','Status', 'Time_Date'] + fields = ["Sensor_ID", "Type", "Location", "Status", "Time_Date"] + class NotificationSerializer(serializers.ModelSerializer): class Meta: model = Notification - fields = ['id', 'title', 'message', 'type', 'created_at'] - \ No newline at end of file + fields = ["id", "title", "message", "type", "created_at"] diff --git a/api/urls.py b/api/urls.py index 04d28c5f..7875df69 100644 --- a/api/urls.py +++ b/api/urls.py @@ -8,33 +8,46 @@ from map.views import MapDeviceListView, DeviceSearchView, map_view from .views import SensorListCreateView, SensorDetailView from .views import NotificationViewSet + router = DefaultRouter() -router.register(r'monitoring-data', MonitoringDataViewSet) +router.register(r"monitoring-data", MonitoringDataViewSet) router = DefaultRouter() -router.register(r'notifications', NotificationViewSet) -urlpatterns = router.urls +router.register(r"notifications", NotificationViewSet) +urlpatterns = router.urls urlpatterns = [ - path('',include(router.urls)), - path('users/', UserListView.as_view(), name='user-list'), - path('users//', UserDetailView.as_view(), name='user-detail'), - path('users/register/', RegisterView.as_view(), name='user-register'), - path('users/login/', LoginView.as_view(), name='user-login'), - path('users/role-based/', RoleBasedView.as_view(), name='role-based'), - path('', include(router.urls)), - path('drainage-systems/', DrainageSystemList.as_view(), name='drainage-system-list'), - path('drainage-systems//', DrainageSystemDetail.as_view(), name='drainage-system-detail'), - path('api', map_view, name='map_view'), - path('map/', map_view, name='map_view'), - path('devices/', MapDeviceListView.as_view(), name='device-list'), - path('search/', DeviceSearchView.as_view(), name='device-search'), - path('sensors/', SensorListCreateView.as_view(), name='sensor-list-create'), - path('sensors//', SensorDetailView.as_view(), name='sensor-detail'), - path('notifications/', NotificationViewSet.as_view({'get': 'list', 'post': 'create'})), - path('datamonitoring/', MonitoringDataViewSet.as_view({'get': 'list', 'post': 'create'})), - path('notifications//', NotificationViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})), - - + path("", include(router.urls)), + path("users/", UserListView.as_view(), name="user-list"), + path("users//", UserDetailView.as_view(), name="user-detail"), + path("users/register/", RegisterView.as_view(), name="user-register"), + path("users/login/", LoginView.as_view(), name="user-login"), + path("users/role-based/", RoleBasedView.as_view(), name="role-based"), + path("", include(router.urls)), + path( + "drainage-systems/", DrainageSystemList.as_view(), name="drainage-system-list" + ), + path( + "drainage-systems//", + DrainageSystemDetail.as_view(), + name="drainage-system-detail", + ), + path("api", map_view, name="map_view"), + path("map/", map_view, name="map_view"), + path("devices/", MapDeviceListView.as_view(), name="device-list"), + path("search/", DeviceSearchView.as_view(), name="device-search"), + path("sensors/", SensorListCreateView.as_view(), name="sensor-list-create"), + path("sensors//", SensorDetailView.as_view(), name="sensor-detail"), + path( + "notifications/", NotificationViewSet.as_view({"get": "list", "post": "create"}) + ), + path( + "datamonitoring/", + MonitoringDataViewSet.as_view({"get": "list", "post": "create"}), + ), + path( + "notifications//", + NotificationViewSet.as_view( + {"get": "retrieve", "put": "update", "delete": "destroy"} + ), + ), ] - - diff --git a/api/views.py b/api/views.py index d26b42b2..267e5360 100644 --- a/api/views.py +++ b/api/views.py @@ -6,17 +6,17 @@ from rest_framework.permissions import IsAuthenticated, IsAdminUser from django.contrib.auth.hashers import make_password from user.models import User -from .serializers import UserSerializer,RoleSerializer +from .serializers import UserSerializer, RoleSerializer from django.contrib.auth import authenticate from rest_framework import viewsets from datamonitoring.models import MonitoringData from .serializers import MonitoringDataSerializer -from rest_framework import generics,status +from rest_framework import generics, status from rest_framework.response import Response from drainagesystem.models import DrainageSystem from .serializers import DrainageSystemSerializer from .serializers import DeviceSerializer -from sensor.models import Sensor +from sensor.models import Sensor from .serializers import SensorSerializer from rest_framework import viewsets from notification.models import Notification @@ -24,6 +24,7 @@ logger = logging.getLogger(__name__) + class UserListView(APIView): permission_classes = [IsAuthenticated] @@ -32,6 +33,7 @@ def get(self, request): serializer = UserSerializer(users, many=True) return Response(serializer.data, status=status.HTTP_200_OK) + class UserDetailView(APIView): permission_classes = [IsAuthenticated] @@ -39,48 +41,59 @@ def get(self, request, id): try: user = User.objects.get(id=id) except User.DoesNotExist: - - return Response({"detail": "User not found."}, status=status.HTTP_404_NOT_FOUND) + return Response( + {"detail": "User not found."}, status=status.HTTP_404_NOT_FOUND + ) serializer = UserSerializer(user) - logger.info(f'User with ID {id} retrieved successfully.') + logger.info(f"User with ID {id} retrieved successfully.") return Response(serializer.data) + + class RegisterView(APIView): def post(self, request): serializer = UserSerializer(data=request.data) if serializer.is_valid(): # hashing password - serializer.validated_data['password'] = make_password(serializer.validated_data['password']) + serializer.validated_data["password"] = make_password( + serializer.validated_data["password"] + ) user = serializer.save() - logger.info(f'User registered successfully: {user.email}') + logger.info(f"User registered successfully: {user.email}") return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED) - logger.error(f'User registration failed: {serializer.errors}') + logger.error(f"User registration failed: {serializer.errors}") return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + class LoginView(APIView): + def post(self, request): + email = request.data.get("email") + password = request.data.get("password") - def post(self, request): - email = request.data.get('email') - password = request.data.get('password') - if not email or not password: - return Response({'error': 'Email and password are required'}, status=status.HTTP_400_BAD_REQUEST) - + return Response( + {"error": "Email and password are required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + try: user = User.objects.get(email=email) except User.DoesNotExist: - logger.info(f'Login attempt for non-existent user: {email}') - return Response({ - 'error': 'User does not exist', - 'signup_required': True - }, status=status.HTTP_404_NOT_FOUND) - + logger.info(f"Login attempt for non-existent user: {email}") + return Response( + {"error": "User does not exist", "signup_required": True}, + status=status.HTTP_404_NOT_FOUND, + ) + django_user = authenticate(username=email, password=password) if django_user: - logger.info(f'User logged in successfully: {email}') + logger.info(f"User logged in successfully: {email}") return Response({}, status=status.HTTP_200_OK) - - logger.error(f'Login failed for user: {email}') - return Response({'error': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) - + + logger.error(f"Login failed for user: {email}") + return Response( + {"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED + ) + class RoleBasedView(APIView): permission_classes = [IsAuthenticated, IsAdminUser] @@ -88,64 +101,78 @@ class RoleBasedView(APIView): def post(self, request): serializer = RoleSerializer(data=request.data) if serializer.is_valid(): - user_id = serializer.validated_data['user_id'] - new_role = serializer.validated_data['role'] + user_id = serializer.validated_data["user_id"] + new_role = serializer.validated_data["role"] try: user = User.objects.get(id=user_id) user.role = new_role user.save() - logger.info(f'Role updated for user {user.email}: {new_role}') - return Response({"detail": f"Role updated to {new_role} for user successfully"}, status=status.HTTP_200_OK) + logger.info(f"Role updated for user {user.email}: {new_role}") + return Response( + {"detail": f"Role updated to {new_role} for user successfully"}, + status=status.HTTP_200_OK, + ) except User.DoesNotExist: - logger.error(f'User with ID {user_id} not found') - return Response({"detail": "User not found"}, status=status.HTTP_404_NOT_FOUND) + logger.error(f"User with ID {user_id} not found") + return Response( + {"detail": "User not found"}, status=status.HTTP_404_NOT_FOUND + ) else: - logger.error(f'Invalid role update data: {serializer.errors}') + logger.error(f"Invalid role update data: {serializer.errors}") return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + class MonitoringDataViewSet(viewsets.ModelViewSet): queryset = MonitoringData.objects.all() serializer_class = MonitoringDataSerializer - + + class DrainageSystemList(generics.ListCreateAPIView): queryset = DrainageSystem.objects.all() serializer_class = DrainageSystemSerializer - def get (self, request,*args,**kwargs): + + def get(self, request, *args, **kwargs): serializer = DrainageSystemSerializer(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) - - + + class DrainageSystemDetail(APIView): - def post(self, request, *args, **kwargs): + def post(self, request, *args, **kwargs): serializer = DrainageSystemSerializer(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) - + + class SensorListCreateView(generics.ListCreateAPIView): queryset = Sensor.objects.all() serializer_class = SensorSerializer + def post(self, request): serializer = SensorSerializer(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): return Response({}) + class SensorDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = Sensor.objects.all() serializer_class = SensorSerializer + def get(self, request, id): sensor = self.get_object() serializer = SensorSerializer(sensor) return Response(serializer.data) + def put(self, request, id): sensor = self.get_object() serializer = SensorSerializer(sensor, data=request.data) @@ -153,11 +180,13 @@ def put(self, request, id): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + def delete(self, request, id): sensor = self.get_object() sensor.delete() return Response(status=status.HTTP_204_NO_CONTENT) - + + class NotificationViewSet(viewsets.ModelViewSet): queryset = Notification.objects.all() - serializer_class = NotificationSerializer \ No newline at end of file + serializer_class = NotificationSerializer diff --git a/aquasens/asgi.py b/aquasens/asgi.py index 3e81e442..5bd10d82 100644 --- a/aquasens/asgi.py +++ b/aquasens/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'aquasens.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aquasens.settings") application = get_asgi_application() diff --git a/aquasens/settings.py b/aquasens/settings.py index 500a1934..ed4dc0f1 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -1,6 +1,3 @@ - - - """ Django settings for newproject project. @@ -12,6 +9,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.1/ref/settings/ """ + from datetime import timedelta import os from pathlib import Path @@ -20,73 +18,71 @@ BASE_DIR = Path(__file__).resolve().parent.parent TEMPLATE_DIR = os.path.join(BASE_DIR, "drainage", "templates") -SECRET_KEY = 'django-insecure-9bv3ge1%z6^itsr^q#$ssef0so*!ex&i#5*e*gg+-^ae9@tgn9' +SECRET_KEY = "django-insecure-9bv3ge1%z6^itsr^q#$ssef0so*!ex&i#5*e*gg+-^ae9@tgn9" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [ 'localhost'] - +ALLOWED_HOSTS = ["localhost"] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'authentication', - 'user', - 'api', - 'rest_framework', - 'datamonitoring', - 'drainagesystem', - 'map', - 'sensor', - 'notification', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "authentication", + "user", + "api", + "rest_framework", + "datamonitoring", + "drainagesystem", + "map", + "sensor", + "notification", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'aquasens.urls' +ROOT_URLCONF = "aquasens.urls" REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework_simplejwt.authentication.JWTAuthentication', + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework_simplejwt.authentication.JWTAuthentication", ], } SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(days=365*10), - 'REFRESH_TOKEN_LIFETIME': timedelta(days=365*10), - 'ROTATE_REFRESH_TOKENS': False, - 'BLACKLIST_AFTER_ROTATION': True, + "ACCESS_TOKEN_LIFETIME": timedelta(days=365 * 10), + "REFRESH_TOKEN_LIFETIME": timedelta(days=365 * 10), + "ROTATE_REFRESH_TOKENS": False, + "BLACKLIST_AFTER_ROTATION": True, } TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', + "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [TEMPLATE_DIR], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, @@ -96,16 +92,16 @@ if ENV_FILE: load_dotenv(ENV_FILE) -WSGI_APPLICATION = 'aquasens.wsgi.application' +WSGI_APPLICATION = "aquasens.wsgi.application" # Database # https://docs.djangoproject.com/en/5.1/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } @@ -115,16 +111,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -132,9 +128,9 @@ # Internationalization # https://docs.djangoproject.com/en/5.1/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -144,17 +140,17 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.1/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = "static/" # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field -AUTH0_CLIENT_ID = os.getenv('AUTH0_CLIENT_ID') -AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET') -AUTH0_DOMAIN = os.getenv('AUTH0_DOMAIN') +AUTH0_CLIENT_ID = os.getenv("AUTH0_CLIENT_ID") +AUTH0_CLIENT_SECRET = os.getenv("AUTH0_CLIENT_SECRET") +AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN") -REDIRECT_URI = 'http://localhost:8000/auth/callback/' -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +REDIRECT_URI = "http://localhost:8000/auth/callback/" +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" -ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] +ALLOWED_HOSTS = ["127.0.0.1", "localhost"] diff --git a/aquasens/urls.py b/aquasens/urls.py index d0646b2b..60870275 100644 --- a/aquasens/urls.py +++ b/aquasens/urls.py @@ -20,11 +20,10 @@ from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView urlpatterns = [ - path('admin/', admin.site.urls), - path('auth/', include("authentication.urls")), - path('api/', include("api.urls")), - path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), - path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), - path('map/', include('map.urls')), - -] \ No newline at end of file + path("admin/", admin.site.urls), + path("auth/", include("authentication.urls")), + path("api/", include("api.urls")), + path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), + path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), + path("map/", include("map.urls")), +] diff --git a/aquasens/wsgi.py b/aquasens/wsgi.py index 8e75fb32..c8eab803 100644 --- a/aquasens/wsgi.py +++ b/aquasens/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'aquasens.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aquasens.settings") application = get_wsgi_application() diff --git a/authentication/apps.py b/authentication/apps.py index 8bab8df0..c65f1d28 100644 --- a/authentication/apps.py +++ b/authentication/apps.py @@ -2,5 +2,5 @@ class AuthenticationConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'authentication' + default_auto_field = "django.db.models.BigAutoField" + name = "authentication" diff --git a/authentication/models.py b/authentication/models.py index a4a82b85..c389f5d7 100644 --- a/authentication/models.py +++ b/authentication/models.py @@ -1,5 +1,4 @@ from django.db import models - # Create your models here. diff --git a/authentication/urls.py b/authentication/urls.py index 7a907889..39c3c42c 100644 --- a/authentication/urls.py +++ b/authentication/urls.py @@ -7,4 +7,4 @@ path("login", views.login, name="login"), path("logout", views.logout, name="logout"), path("callback", views.callback, name="callback"), -] \ No newline at end of file +] diff --git a/authentication/views.py b/authentication/views.py index 14d5ae75..69a1ac9e 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -56,4 +56,3 @@ def index(request): "pretty": json.dumps(request.session.get("user"), indent=4), }, ) - diff --git a/datamonitoring/admin.py b/datamonitoring/admin.py index 7c6a98ef..24fc7984 100644 --- a/datamonitoring/admin.py +++ b/datamonitoring/admin.py @@ -2,4 +2,5 @@ # Register your models here. from .models import MonitoringData -admin.site.register(MonitoringData) \ No newline at end of file + +admin.site.register(MonitoringData) diff --git a/datamonitoring/apps.py b/datamonitoring/apps.py index 54afdd9d..ce97c963 100644 --- a/datamonitoring/apps.py +++ b/datamonitoring/apps.py @@ -2,5 +2,5 @@ class DatamonitoringConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'datamonitoring' + default_auto_field = "django.db.models.BigAutoField" + name = "datamonitoring" diff --git a/datamonitoring/migrations/0001_initial.py b/datamonitoring/migrations/0001_initial.py index aa323f3f..389631db 100644 --- a/datamonitoring/migrations/0001_initial.py +++ b/datamonitoring/migrations/0001_initial.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ @@ -15,14 +14,23 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='MonitoringData', + name="MonitoringData", fields=[ - ('monitoring_id', models.AutoField(primary_key=True, serialize=False)), - ('drainage_id', models.IntegerField()), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('water_level', models.DecimalField(decimal_places=2, max_digits=10)), - ('water_pressure', models.DecimalField(decimal_places=2, max_digits=10)), - ('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ("monitoring_id", models.AutoField(primary_key=True, serialize=False)), + ("drainage_id", models.IntegerField()), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ("water_level", models.DecimalField(decimal_places=2, max_digits=10)), + ( + "water_pressure", + models.DecimalField(decimal_places=2, max_digits=10), + ), + ( + "user_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/datamonitoring/models.py b/datamonitoring/models.py index 02248eda..831bdfb4 100644 --- a/datamonitoring/models.py +++ b/datamonitoring/models.py @@ -1,12 +1,13 @@ from django.db import models + + class MonitoringData(models.Model): monitoring_id = models.AutoField(primary_key=True) - user_id = models.ForeignKey('auth.User', on_delete=models.CASCADE) - drainage_id = models.IntegerField() - timestamp = models.DateTimeField(auto_now_add=True) - water_level = models.DecimalField(max_digits=10, decimal_places=2) - water_pressure = models.DecimalField(max_digits=10, decimal_places=2) + user_id = models.ForeignKey("auth.User", on_delete=models.CASCADE) + drainage_id = models.IntegerField() + timestamp = models.DateTimeField(auto_now_add=True) + water_level = models.DecimalField(max_digits=10, decimal_places=2) + water_pressure = models.DecimalField(max_digits=10, decimal_places=2) def __str__(self): - return f'Monitoring Data {self.monitoring_id}' - + return f"Monitoring Data {self.monitoring_id}" diff --git a/datamonitoring/tests.py b/datamonitoring/tests.py index 02d8ac98..73504277 100644 --- a/datamonitoring/tests.py +++ b/datamonitoring/tests.py @@ -6,89 +6,87 @@ from django.core.exceptions import ValidationError from django.db.utils import IntegrityError -class MonitoringDataModelTest(TestCase): +class MonitoringDataModelTest(TestCase): def setUp(self): self.user = User.objects.create_user( - username='user', - email='daisychekuiri@gmail.com', - password='2003rt', + username="user", + email="daisychekuiri@gmail.com", + password="2003rt", ) - + self.monitoring_data = MonitoringData.objects.create( - user_id=self.user, + user_id=self.user, drainage_id=1, - water_level=Decimal('10.5'), - water_pressure=Decimal('2.3'), - timestamp=timezone.now() + water_level=Decimal("10.5"), + water_pressure=Decimal("2.3"), + timestamp=timezone.now(), ) def test_monitoring_data_creation(self): - - monitoring_data = MonitoringData.objects.get(monitoring_id=self.monitoring_data.monitoring_id) + monitoring_data = MonitoringData.objects.get( + monitoring_id=self.monitoring_data.monitoring_id + ) self.assertEqual(monitoring_data.user_id, self.user) self.assertEqual(monitoring_data.drainage_id, 1) - self.assertEqual(monitoring_data.water_level, Decimal('10.5')) - self.assertEqual(monitoring_data.water_pressure, Decimal('2.3')) + self.assertEqual(monitoring_data.water_level, Decimal("10.5")) + self.assertEqual(monitoring_data.water_pressure, Decimal("2.3")) self.assertIsInstance(monitoring_data.timestamp, timezone.datetime) - + def test_string_representation(self): - - self.assertEqual(str(self.monitoring_data), f'Monitoring Data {self.monitoring_data.monitoring_id}') + self.assertEqual( + str(self.monitoring_data), + f"Monitoring Data {self.monitoring_data.monitoring_id}", + ) def test_monitoring_data_creation_missing_user(self): # Unhappy path test: which Verify's that IntegrityError is raised if user_id is missing with self.assertRaises(IntegrityError): MonitoringData.objects.create( - user_id=None, + user_id=None, drainage_id=1, - water_level=Decimal('10.5'), - water_pressure=Decimal('2.3'), - timestamp=timezone.now() + water_level=Decimal("10.5"), + water_pressure=Decimal("2.3"), + timestamp=timezone.now(), ) def test_monitoring_data_creation_missing_drainage_id(self): - with self.assertRaises(IntegrityError): MonitoringData.objects.create( user_id=self.user, - drainage_id=None, - water_level=Decimal('10.5'), - water_pressure=Decimal('2.3'), - timestamp=timezone.now() + drainage_id=None, + water_level=Decimal("10.5"), + water_pressure=Decimal("2.3"), + timestamp=timezone.now(), ) def test_monitoring_data_creation_invalid_water_level(self): - - - monitoring_data = MonitoringData( - user_id=self.user, - drainage_id=1, - water_level=Decimal('-10.5'), - water_pressure=Decimal('2.3'), - timestamp=timezone.now() - ) - monitoring_data.full_clean() + monitoring_data = MonitoringData( + user_id=self.user, + drainage_id=1, + water_level=Decimal("-10.5"), + water_pressure=Decimal("2.3"), + timestamp=timezone.now(), + ) + monitoring_data.full_clean() def test_monitoring_data_creation_invalid_water_pressure(self): # Unhappy path test: which Verify ValidationError is raised for negative water_pressure - - monitoring_data = MonitoringData( - user_id=self.user, - drainage_id=1, - water_level=Decimal('10.5'), - water_pressure=Decimal('-2.3'), - timestamp=timezone.now() - ) - monitoring_data.full_clean() + + monitoring_data = MonitoringData( + user_id=self.user, + drainage_id=1, + water_level=Decimal("10.5"), + water_pressure=Decimal("-2.3"), + timestamp=timezone.now(), + ) + monitoring_data.full_clean() def test_monitoring_data_creation_missing_timestamp(self): - - - MonitoringData.objects.create( - user_id=self.user, - drainage_id=1, - water_level=Decimal('10.5'), - water_pressure=Decimal('2.3'), - timestamp=None - ) + MonitoringData.objects.create( + user_id=self.user, + drainage_id=1, + water_level=Decimal("10.5"), + water_pressure=Decimal("2.3"), + timestamp=None, + ) diff --git a/drainagesystem/apps.py b/drainagesystem/apps.py index a0fa61d2..f1b463f6 100644 --- a/drainagesystem/apps.py +++ b/drainagesystem/apps.py @@ -2,5 +2,5 @@ class DrainagesystemConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'drainagesystem' + default_auto_field = "django.db.models.BigAutoField" + name = "drainagesystem" diff --git a/drainagesystem/migrations/0001_initial.py b/drainagesystem/migrations/0001_initial.py index 0c0a7b6c..ec8dfcea 100644 --- a/drainagesystem/migrations/0001_initial.py +++ b/drainagesystem/migrations/0001_initial.py @@ -4,22 +4,20 @@ class Migration(migrations.Migration): - initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='DrainageSystem', + name="DrainageSystem", fields=[ - ('Drainage_ID', models.AutoField(primary_key=True, serialize=False)), - ('Location', models.CharField(max_length=100)), - ('waterlevel', models.DecimalField(decimal_places=2, max_digits=10)), - ('waterpressure', models.DecimalField(decimal_places=2, max_digits=10)), - ('Status', models.CharField(max_length=100)), - ('Timestamp', models.DateTimeField(auto_now_add=True)), + ("Drainage_ID", models.AutoField(primary_key=True, serialize=False)), + ("Location", models.CharField(max_length=100)), + ("waterlevel", models.DecimalField(decimal_places=2, max_digits=10)), + ("waterpressure", models.DecimalField(decimal_places=2, max_digits=10)), + ("Status", models.CharField(max_length=100)), + ("Timestamp", models.DateTimeField(auto_now_add=True)), ], ), ] diff --git a/drainagesystem/models.py b/drainagesystem/models.py index 916bad14..48da60c0 100644 --- a/drainagesystem/models.py +++ b/drainagesystem/models.py @@ -1,5 +1,6 @@ from django.db import models + class DrainageSystem(models.Model): Drainage_ID = models.AutoField(primary_key=True) Location = models.CharField(max_length=100) @@ -9,8 +10,7 @@ class DrainageSystem(models.Model): Timestamp = models.DateTimeField(auto_now_add=True) def __str__(self): - return f'Drainagesystem {self.Drainage_ID}' - + return f"Drainagesystem {self.Drainage_ID}" # Create your models here. diff --git a/drainagesystem/tests.py b/drainagesystem/tests.py index 5838f15e..3436706c 100644 --- a/drainagesystem/tests.py +++ b/drainagesystem/tests.py @@ -4,91 +4,70 @@ from django.db.utils import IntegrityError from django.utils import timezone + class DrainageSystemModelTest(TestCase): - def setUp(self): self.drainage_system = DrainageSystem.objects.create( - Location="Karen", - waterlevel=5.75, - waterpressure= 4.45, - Status="Active" + Location="Karen", waterlevel=5.75, waterpressure=4.45, Status="Active" ) - + def test_drainage_system_creation(self): - drainage_system = DrainageSystem.objects.get(Drainage_ID=self.drainage_system.Drainage_ID) + drainage_system = DrainageSystem.objects.get( + Drainage_ID=self.drainage_system.Drainage_ID + ) self.assertEqual(drainage_system.Location, "Karen") self.assertEqual(drainage_system.waterlevel, 5.75) self.assertEqual(drainage_system.Status, "Active") - self.assertIsNotNone(drainage_system.Timestamp) - + self.assertIsNotNone(drainage_system.Timestamp) + def test_drainage_system_string_representation(self): - self.assertEqual(str(self.drainage_system), f'Drainagesystem {self.drainage_system.Drainage_ID}') - + self.assertEqual( + str(self.drainage_system), + f"Drainagesystem {self.drainage_system.Drainage_ID}", + ) + def test_drainage_system_creation_missing_location(self): with self.assertRaises(IntegrityError): DrainageSystem.objects.create( - Location=None, - waterlevel=5.75, - waterpressure=3.45, - Status="Active" + Location=None, waterlevel=5.75, waterpressure=3.45, Status="Active" ) def test_drainage_system_creation_missing_waterlevel(self): with self.assertRaises(IntegrityError): DrainageSystem.objects.create( - Location="Karen", - waterlevel=None, - waterpressure=3.45, - Status="Active" + Location="Karen", waterlevel=None, waterpressure=3.45, Status="Active" ) - + def test_drainage_system_creation_missing_waterpressure(self): with self.assertRaises(IntegrityError): DrainageSystem.objects.create( - Location="Karen", - waterlevel=5.75, - waterpressure=None, - Status="Active" + Location="Karen", waterlevel=5.75, waterpressure=None, Status="Active" ) def test_drainage_system_creation_missing_status(self): with self.assertRaises(IntegrityError): DrainageSystem.objects.create( - Location="Karen", - waterlevel=5.75, - waterpressure=3.45, - Status=None + Location="Karen", waterlevel=5.75, waterpressure=3.45, Status=None ) def test_invalid_waterlevel(self): drainage_system = DrainageSystem( - Location="Karen", - waterlevel=-5.75, - waterpressure=3.45, - Status="Active" + Location="Karen", waterlevel=-5.75, waterpressure=3.45, Status="Active" ) with self.assertRaises(ValidationError): - drainage_system.full_clean() + drainage_system.full_clean() def test_invalid_waterpressure(self): drainage_system = DrainageSystem( - Location="Karen", - waterlevel=5.75, - waterpressure=-3.45, - Status="Active" + Location="Karen", waterlevel=5.75, waterpressure=-3.45, Status="Active" ) with self.assertRaises(ValidationError): - drainage_system.full_clean() + drainage_system.full_clean() def test_long_location(self): - long_location = "x" * 200 + long_location = "x" * 200 drainage_system = DrainageSystem( - Location=long_location, - waterlevel=5.75, - waterpressure=3.45, - Status="Active" + Location=long_location, waterlevel=5.75, waterpressure=3.45, Status="Active" ) with self.assertRaises(ValidationError): - drainage_system.full_clean() - - \ No newline at end of file + drainage_system.full_clean() diff --git a/manage.py b/manage.py index 6f9abd68..e5d2018c 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'aquasens.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aquasens.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/map/admin.py b/map/admin.py index 0d89021c..442656dc 100644 --- a/map/admin.py +++ b/map/admin.py @@ -1,7 +1,9 @@ from django.contrib import admin -from.models import Device +from .models import Device + admin.site.register(Device) from django.contrib import admin from map.models import Device + # Register your models here. diff --git a/map/apps.py b/map/apps.py index 604c7c7d..dc04de09 100644 --- a/map/apps.py +++ b/map/apps.py @@ -2,5 +2,5 @@ class MapConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'map' + default_auto_field = "django.db.models.BigAutoField" + name = "map" diff --git a/map/migrations/0001_initial.py b/map/migrations/0001_initial.py index d4464baa..22a14482 100644 --- a/map/migrations/0001_initial.py +++ b/map/migrations/0001_initial.py @@ -4,22 +4,28 @@ class Migration(migrations.Migration): - initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Device', + name="Device", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('address', models.CharField(max_length=255)), - ('latitude', models.FloatField()), - ('longitude', models.FloatField()), - ('status', models.CharField(max_length=255)), - ('type', models.CharField(default='sensor', max_length=50)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("address", models.CharField(max_length=255)), + ("latitude", models.FloatField()), + ("longitude", models.FloatField()), + ("status", models.CharField(max_length=255)), + ("type", models.CharField(default="sensor", max_length=50)), ], ), ] diff --git a/map/models.py b/map/models.py index 32147e3f..14006746 100644 --- a/map/models.py +++ b/map/models.py @@ -1,12 +1,13 @@ from django.db import models + # Create your models here. class Device(models.Model): address = models.CharField(max_length=255) latitude = models.FloatField() longitude = models.FloatField() - status = models.CharField(max_length=255, null=False, blank=False) - type = models.CharField(max_length=50, default ='sensor') + status = models.CharField(max_length=255, null=False, blank=False) + type = models.CharField(max_length=50, default="sensor") def __str__(self): - return f"{self.type} - {self.address}" \ No newline at end of file + return f"{self.type} - {self.address}" diff --git a/map/serializers.py b/map/serializers.py index 3091bc0b..d97d9bf2 100644 --- a/map/serializers.py +++ b/map/serializers.py @@ -1,7 +1,8 @@ from rest_framework import serializers from .models import Device + class DeviceSerializer(serializers.ModelSerializer): class Meta: model = Device - fields = '__all__' \ No newline at end of file + fields = "__all__" diff --git a/map/tests.py b/map/tests.py index c6d3eb9a..6d7e885b 100644 --- a/map/tests.py +++ b/map/tests.py @@ -2,68 +2,65 @@ from django.db import IntegrityError from map.models import Device + class DeviceModelTest(TestCase): def setUp(self): - self.device = Device.objects.create( - address='123 Nairobi Street', + address="123 Nairobi Street", latitude=-1.286389, longitude=36.817223, - status='active', - type='sensor' + status="active", + type="sensor", ) def test_device_creation(self): - device = self.device self.assertIsInstance(device, Device) - self.assertEqual(device.address, '123 Nairobi Street') + self.assertEqual(device.address, "123 Nairobi Street") self.assertEqual(device.latitude, -1.286389) self.assertEqual(device.longitude, 36.817223) - self.assertEqual(device.status, 'active') - self.assertEqual(device.type, 'sensor') + self.assertEqual(device.status, "active") + self.assertEqual(device.type, "sensor") def test_device_str_method(self): - device = self.device expected_str = f"{device.type} - {device.address}" self.assertEqual(str(device), expected_str) def test_empty_address_field(self): - - Device.objects.create( - address='', - latitude=-1.286389, - longitude=36.817223, - status='active', - type='sensor' - ) + Device.objects.create( + address="", + latitude=-1.286389, + longitude=36.817223, + status="active", + type="sensor", + ) def test_missing_latitude_field(self): with self.assertRaises(IntegrityError): Device.objects.create( - address='456 Another Street', - latitude=None, + address="456 Another Street", + latitude=None, longitude=36.817223, - status='active', - type='sensor' + status="active", + type="sensor", ) def test_invalid_latitude_value(self): with self.assertRaises(ValueError): Device.objects.create( - address='789 Invalid Lat Street', - latitude='invalid_latitude', + address="789 Invalid Lat Street", + latitude="invalid_latitude", longitude=36.817223, - status='active', - type='sensor' + status="active", + type="sensor", ) def test_empty_status_field(self): - Device.objects.create( - address='999 Status Street', - latitude=-1.286389, - longitude=36.817223, - status='', - type='sensor' - ) + Device.objects.create( + address="999 Status Street", + latitude=-1.286389, + longitude=36.817223, + status="", + type="sensor", + ) diff --git a/map/urls.py b/map/urls.py index 377f93a7..9a191057 100644 --- a/map/urls.py +++ b/map/urls.py @@ -3,10 +3,8 @@ from .views import MapDeviceListView, DeviceSearchView, map_view urlpatterns = [ - path('api', map_view, name='map_view'), - path('map/', map_view, name='map_view'), - path('devices/', MapDeviceListView.as_view(), name='device-list'), - path('search/', DeviceSearchView.as_view(), name='device-search'), + path("api", map_view, name="map_view"), + path("map/", map_view, name="map_view"), + path("devices/", MapDeviceListView.as_view(), name="device-list"), + path("search/", DeviceSearchView.as_view(), name="device-search"), ] - - diff --git a/map/views.py b/map/views.py index 830920c1..0005cf2c 100644 --- a/map/views.py +++ b/map/views.py @@ -9,18 +9,19 @@ from .models import Device # Load API key from environment variable -GOOGLE_MAPS_API_KEY = os.getenv('GOOGLE_MAPS_API_KEY') -GEOCODE_URL = 'https://maps.googleapis.com/maps/api/geocode/json' +GOOGLE_MAPS_API_KEY = os.getenv("GOOGLE_MAPS_API_KEY") +GEOCODE_URL = "https://maps.googleapis.com/maps/api/geocode/json" def map_view(request): - devices = list(Device.objects.values('latitude', 'longitude', 'address', 'status')) - return render(request, 'map/map_template.html', {'devices': devices}) + devices = list(Device.objects.values("latitude", "longitude", "address", "status")) + return render(request, "map/map_template.html", {"devices": devices}) + class MapDeviceListView(generics.ListCreateAPIView): queryset = Device.objects.all() serializer_class = DeviceSerializer - + def post(self, request): serializer = DeviceSerializer(data=request.data) if serializer.is_valid(): @@ -28,24 +29,34 @@ def post(self, request): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + class DeviceSearchView(APIView): def get(self, request): - latitude = request.query_params.get('latitude') - longitude = request.query_params.get('longitude') + latitude = request.query_params.get("latitude") + longitude = request.query_params.get("longitude") if not latitude or not longitude: - return Response({'error': 'Latitude and Longitude are required'}, status=status.HTTP_400_BAD_REQUEST) - + return Response( + {"error": "Latitude and Longitude are required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + try: latitude = float(latitude) longitude = float(longitude) nearby_devices = Device.objects.filter( latitude__range=(latitude - 0.01, latitude + 0.01), - longitude__range=(longitude - 0.01, longitude + 0.01) + longitude__range=(longitude - 0.01, longitude + 0.01), ) serializer = DeviceSerializer(nearby_devices, many=True) return Response(serializer.data, status=status.HTTP_200_OK) except ValueError: - return Response({'error': 'Invalid latitude or longitude'}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Invalid latitude or longitude"}, + status=status.HTTP_400_BAD_REQUEST, + ) except requests.RequestException as e: - return Response({'error': f'Network error: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) \ No newline at end of file + return Response( + {"error": f"Network error: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) diff --git a/notification/admin.py b/notification/admin.py index d94563a3..a9bb0f8d 100644 --- a/notification/admin.py +++ b/notification/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin from .models import Notification + admin.site.register(Notification) # Register your models here. diff --git a/notification/apps.py b/notification/apps.py index 8757bbe5..24a58ddc 100644 --- a/notification/apps.py +++ b/notification/apps.py @@ -2,5 +2,5 @@ class NotificationConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'notification' + default_auto_field = "django.db.models.BigAutoField" + name = "notification" diff --git a/notification/migrations/0001_initial.py b/notification/migrations/0001_initial.py index 951de316..e54abf6a 100644 --- a/notification/migrations/0001_initial.py +++ b/notification/migrations/0001_initial.py @@ -4,21 +4,30 @@ class Migration(migrations.Migration): - initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Notification', + name="Notification", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('title', models.CharField(max_length=200)), - ('message', models.TextField()), - ('type', models.CharField(choices=[('info', 'Information'), ('warn', 'Warning'), ('error', 'Error'), ('success', 'Success')], max_length=255)), - ('created_at', models.DateField(auto_now_add=True)), + ("id", models.AutoField(primary_key=True, serialize=False)), + ("title", models.CharField(max_length=200)), + ("message", models.TextField()), + ( + "type", + models.CharField( + choices=[ + ("info", "Information"), + ("warn", "Warning"), + ("error", "Error"), + ("success", "Success"), + ], + max_length=255, + ), + ), + ("created_at", models.DateField(auto_now_add=True)), ], ), ] diff --git a/notification/models.py b/notification/models.py index 841c0c3a..b949b565 100644 --- a/notification/models.py +++ b/notification/models.py @@ -1,17 +1,22 @@ from django.db import models NOTIFICATION_TYPES = [ - ('info', 'Information'), - ('warn', 'Warning'), - ('error', 'Error'), - ('success', 'Success'), + ("info", "Information"), + ("warn", "Warning"), + ("error", "Error"), + ("success", "Success"), ] + + class Notification(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=200) message = models.TextField() type = models.CharField(max_length=255, choices=NOTIFICATION_TYPES) created_at = models.DateField(auto_now_add=True) + def __str__(self): return self.title + + # Create your models here. diff --git a/notification/tests.py b/notification/tests.py index 05ffcb6b..696be4d4 100644 --- a/notification/tests.py +++ b/notification/tests.py @@ -3,64 +3,59 @@ from django.core.exceptions import ValidationError from django.db.utils import IntegrityError + class NotificationModelTest(TestCase): - def setUp(self): self.notification = Notification.objects.create( - title="Notification", + title="Notification", message="we are testing our Notification.", - type="info" + type="info", ) - + def test_notificatn_creation(self): notification = Notification.objects.get(id=self.notification.id) - self.assertEqual(notification.title, "Notification") - self.assertEqual(notification.message, "we are testing our Notification.") + self.assertEqual(notification.title, "Notification") + self.assertEqual(notification.message, "we are testing our Notification.") self.assertEqual(notification.type, "info") self.assertIsNotNone(notification.created_at) - + def test_notification_string_representation(self): - self.assertEqual(str(self.notification), "Notification") - + self.assertEqual(str(self.notification), "Notification") + def test_notification_type_choices(self): - valid_types = [choice[0] for choice in Notification._meta.get_field('type').choices] - self.assertIn('info', valid_types) - self.assertIn('warn', valid_types) - self.assertIn('error', valid_types) - self.assertIn('success', valid_types) + valid_types = [ + choice[0] for choice in Notification._meta.get_field("type").choices + ] + self.assertIn("info", valid_types) + self.assertIn("warn", valid_types) + self.assertIn("error", valid_types) + self.assertIn("success", valid_types) def test_notification_creation_missing_title(self): with self.assertRaises(IntegrityError): Notification.objects.create( - title=None, - message="This notification has no title.", - type="error" + title=None, message="This notification has no title.", type="error" ) def test_invalid_type_choice(self): notification = Notification( title="Invalid Type Notification", message="This has an invalid type.", - type="invalid" + type="invalid", ) with self.assertRaises(ValidationError): - notification.full_clean() + notification.full_clean() def test_long_title(self): - long_title = "x" * 300 + long_title = "x" * 300 notification = Notification( - title=long_title, - message="Welcome back to our home.", - type="info" + title=long_title, message="Welcome back to our home.", type="info" ) with self.assertRaises(ValidationError): - notification.full_clean() + notification.full_clean() def test_notification_creation_missing_message(self): with self.assertRaises(IntegrityError): Notification.objects.create( - title="No Message Notification", - message=None, - type="warn" + title="No Message Notification", message=None, type="warn" ) - diff --git a/requirements.txt b/requirements.txt index a7da563d..f6cc9d91 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ +authlib~=1.0 +django~=4.0 +python-dotenv~=0.19 +requests~=2.27 +black~=23.1.0 -authlib ~= 1.0 -django ~= 4.0 -python-dotenv ~= 0.19 -requests ~= 2.27 \ No newline at end of file diff --git a/sensor/apps.py b/sensor/apps.py index ec892d44..d12e9a61 100644 --- a/sensor/apps.py +++ b/sensor/apps.py @@ -2,5 +2,5 @@ class SensorConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'sensor' + default_auto_field = "django.db.models.BigAutoField" + name = "sensor" diff --git a/sensor/migrations/0001_initial.py b/sensor/migrations/0001_initial.py index f57554af..1e2f7ee9 100644 --- a/sensor/migrations/0001_initial.py +++ b/sensor/migrations/0001_initial.py @@ -4,21 +4,19 @@ class Migration(migrations.Migration): - initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Sensor', + name="Sensor", fields=[ - ('Sensor_ID', models.AutoField(primary_key=True, serialize=False)), - ('Type', models.CharField(max_length=255)), - ('Location', models.CharField(default='karen', max_length=255)), - ('Status', models.CharField(max_length=100)), - ('Time_Date', models.DateField(auto_now_add=True)), + ("Sensor_ID", models.AutoField(primary_key=True, serialize=False)), + ("Type", models.CharField(max_length=255)), + ("Location", models.CharField(default="karen", max_length=255)), + ("Status", models.CharField(max_length=100)), + ("Time_Date", models.DateField(auto_now_add=True)), ], ), ] diff --git a/sensor/models.py b/sensor/models.py index 891d728b..910659ea 100644 --- a/sensor/models.py +++ b/sensor/models.py @@ -1,13 +1,15 @@ from django.db import models + class Sensor(models.Model): - Sensor_ID = models.AutoField(primary_key=True) - Type = models.CharField(max_length=255) - Location=models.CharField(max_length=255,default='karen') - Status = models.CharField(max_length=100) - Time_Date=models.DateField(auto_now_add=True) + Sensor_ID = models.AutoField(primary_key=True) + Type = models.CharField(max_length=255) + Location = models.CharField(max_length=255, default="karen") + Status = models.CharField(max_length=100) + Time_Date = models.DateField(auto_now_add=True) + def __str__(self): return f"Sensor {self.Sensor_ID} - {self.Type}" -# Create your models here. +# Create your models here. diff --git a/sensor/tests.py b/sensor/tests.py index f53e8ada..9e789832 100644 --- a/sensor/tests.py +++ b/sensor/tests.py @@ -4,19 +4,17 @@ from django.core.exceptions import ValidationError from django.db.utils import IntegrityError -class SensorModelTest(TestCase): +class SensorModelTest(TestCase): def setUp(self): - self.sensor = Sensor.objects.create( Type="Waterpressure", Location="Nairobi", Status="Active", - Time_Date=timezone.now().date() + Time_Date=timezone.now().date(), ) def test_sensor_creation(self): - sensor = Sensor.objects.get(Sensor_ID=self.sensor.Sensor_ID) self.assertEqual(sensor.Type, "Waterpressure") self.assertEqual(sensor.Location, "Nairobi") @@ -24,48 +22,31 @@ def test_sensor_creation(self): self.assertEqual(sensor.Time_Date, self.sensor.Time_Date) def test_sensor_string_representation(self): - - self.assertEqual(str(self.sensor), f"Sensor {self.sensor.Sensor_ID} - Waterpressure") + self.assertEqual( + str(self.sensor), f"Sensor {self.sensor.Sensor_ID} - Waterpressure" + ) def test_default_location(self): # Happy Path: It involves Creating a Sensor without specifying Location, it verify default value - default_sensor = Sensor.objects.create( - Type="Water Pressure", - Status="Inactive" - ) + default_sensor = Sensor.objects.create(Type="Water Pressure", Status="Inactive") self.assertEqual(default_sensor.Location, "karen") def test_sensor_creation_missing_type(self): - with self.assertRaises(IntegrityError): - Sensor.objects.create( - Type=None, - Location="Karen", - Status="Active" - ) + Sensor.objects.create(Type=None, Location="Karen", Status="Active") def test_sensor_creation_missing_status(self): with self.assertRaises(IntegrityError): - Sensor.objects.create( - Type="Waterpressure", - Location="Nasra", - Status=None - ) + Sensor.objects.create(Type="Waterpressure", Location="Nasra", Status=None) def test_sensor_creation_invalid_location(self): with self.assertRaises(ValidationError): sensor = Sensor( - Type="Waterpressure", - Location='Karen' * 300, - Status="Active" + Type="Waterpressure", Location="Karen" * 300, Status="Active" ) - sensor.full_clean() + sensor.full_clean() def test_sensor_creation_invalid_type(self): with self.assertRaises(ValidationError): - sensor = Sensor( - Type='waterlevel' * 300, - Location="Nasra", - Status="Active" - ) - sensor.full_clean() + sensor = Sensor(Type="waterlevel" * 300, Location="Nasra", Status="Active") + sensor.full_clean() diff --git a/user/admin.py b/user/admin.py index 310349cf..3519a81e 100644 --- a/user/admin.py +++ b/user/admin.py @@ -7,5 +7,5 @@ from .models import User -admin.site.register(User) +admin.site.register(User) diff --git a/user/apps.py b/user/apps.py index 36cce4c8..578292c2 100644 --- a/user/apps.py +++ b/user/apps.py @@ -2,5 +2,5 @@ class UserConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'user' + default_auto_field = "django.db.models.BigAutoField" + name = "user" diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py index 31684193..5b70bcb9 100644 --- a/user/migrations/0001_initial.py +++ b/user/migrations/0001_initial.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ @@ -15,16 +14,40 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_name', models.CharField(max_length=30)), - ('last_name', models.CharField(max_length=30)), - ('phone_number', models.CharField(max_length=20)), - ('email', models.EmailField(max_length=254, unique=True)), - ('password', models.CharField(max_length=16)), - ('role', models.CharField(choices=[('Estate_Associate', 'Estate_Associate'), ('ADMIN', 'ADMIN')], default='Estate_Associate', max_length=30)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("first_name", models.CharField(max_length=30)), + ("last_name", models.CharField(max_length=30)), + ("phone_number", models.CharField(max_length=20)), + ("email", models.EmailField(max_length=254, unique=True)), + ("password", models.CharField(max_length=16)), + ( + "role", + models.CharField( + choices=[ + ("Estate_Associate", "Estate_Associate"), + ("ADMIN", "ADMIN"), + ], + default="Estate_Associate", + max_length=30, + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/user/models.py b/user/models.py index 6d171b42..ed645530 100644 --- a/user/models.py +++ b/user/models.py @@ -1,13 +1,14 @@ from django.db import models from django.contrib.auth.models import User as DjangoUser -Estate_Associate = 'Estate_Associate' -ADMIN = 'ADMIN' +Estate_Associate = "Estate_Associate" +ADMIN = "ADMIN" ROLE_CHOICES = [ - (Estate_Associate, 'Estate_Associate'), - (ADMIN, 'ADMIN'), + (Estate_Associate, "Estate_Associate"), + (ADMIN, "ADMIN"), ] + class User(models.Model): user = models.OneToOneField(DjangoUser, on_delete=models.CASCADE) first_name = models.CharField(max_length=30) @@ -23,6 +24,3 @@ class User(models.Model): def __str__(self): return f"{self.first_name} {self.last_name} ({self.email})" - - - \ No newline at end of file diff --git a/user/tests.py b/user/tests.py index f10795ad..aa2587b5 100644 --- a/user/tests.py +++ b/user/tests.py @@ -4,16 +4,13 @@ from django.db.utils import IntegrityError from django.core.exceptions import ValidationError -class UserModelTest(TestCase): +class UserModelTest(TestCase): def setUp(self): - self.django_user = DjangoUser.objects.create_user( - username="Alinemutesi", - password="testpassword1563" + username="Alinemutesi", password="testpassword1563" ) - - + self.user = User.objects.create( user=self.django_user, first_name="Mutesi", @@ -25,7 +22,6 @@ def setUp(self): ) def test_user_creation(self): - self.assertEqual(self.user.first_name, "Mutesi") self.assertEqual(self.user.last_name, "Aline") self.assertEqual(self.user.phone_number, "1234567890") @@ -33,17 +29,14 @@ def test_user_creation(self): self.assertEqual(self.user.role, Estate_Associate) def test_default_role(self): - self.assertEqual(self.user.role, Estate_Associate) def test_role_choices(self): - self.user.role = ADMIN self.user.save() self.assertEqual(self.user.role, ADMIN) def test_str_representation(self): - self.assertEqual(str(self.user), "Mutesi Aline (alinemutesi@gmail.com)") def test_unique_email(self): @@ -53,7 +46,7 @@ def test_unique_email(self): first_name="linet", last_name="mkandoe", phone_number="0987654321", - email="alinemutesi@gmail.com", + email="alinemutesi@gmail.com", password="linet56678", role=Estate_Associate, ) @@ -68,7 +61,7 @@ def test_missing_email(self): password="testpassword200366", role=Estate_Associate, ) - user.full_clean() + user.full_clean() def test_missing_role(self): with self.assertRaises(ValidationError): @@ -80,7 +73,7 @@ def test_missing_role(self): email="nyabangwech@gmail.com", password="testpassword200366", ) - user.full_clean() + user.full_clean() def test_invalid_role(self): with self.assertRaises(ValidationError): @@ -91,9 +84,9 @@ def test_invalid_role(self): phone_number="1234567890", email="nyabangwech@gmail.com", password="testpassword200366", - role="invalid_role" + role="invalid_role", ) - user.full_clean() + user.full_clean() def test_long_phone_number(self): with self.assertRaises(ValidationError): @@ -101,12 +94,12 @@ def test_long_phone_number(self): user=self.django_user, first_name="Nyabang", last_name="wech", - phone_number="1" * 21, + phone_number="1" * 21, email="nyabangwech@gmail.com", password="testpassword200366", role=Estate_Associate, ) - user.full_clean() + user.full_clean() def test_long_password(self): with self.assertRaises(ValidationError): @@ -116,7 +109,7 @@ def test_long_password(self): last_name="Wech", phone_number="1234567890", email="nyabangwech@gmail.com", - password="p" * 201, + password="p" * 201, role=Estate_Associate, ) - user.full_clean() + user.full_clean() From 911fb28b9065071d0c5de62daed8c7f1113a12ce Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Tue, 17 Sep 2024 11:26:09 +0300 Subject: [PATCH 10/27] added requirement.txt --- requirements.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f6cc9d91..c8b80b73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,3 @@ django~=4.0 python-dotenv~=0.19 requests~=2.27 black~=23.1.0 - - From ea09730f3ba5a86e4d656643cf768c7ad4f8c673 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Tue, 17 Sep 2024 13:46:04 +0300 Subject: [PATCH 11/27] add requirement.txt --- datamonitoring/admin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/datamonitoring/admin.py b/datamonitoring/admin.py index 24fc7984..466bc8b7 100644 --- a/datamonitoring/admin.py +++ b/datamonitoring/admin.py @@ -1,6 +1,4 @@ from django.contrib import admin - -# Register your models here. from .models import MonitoringData admin.site.register(MonitoringData) From 467441b3ce899a68541b37126e30f43b4de620c7 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Tue, 17 Sep 2024 14:39:29 +0300 Subject: [PATCH 12/27] added requirements --- requirements.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index c8b80b73..da11aad1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -authlib~=1.0 -django~=4.0 -python-dotenv~=0.19 -requests~=2.27 -black~=23.1.0 +authlib~=1.0 +django~=4.0 +python-dotenv~=0.19 +requests~=2.27 +black==24.1.0 + From 9c5ed67a4de246080068fbf539e45690c5dc5c82 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Tue, 17 Sep 2024 14:40:16 +0300 Subject: [PATCH 13/27] added requirements --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index da11aad1..cd020820 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,4 @@ authlib~=1.0 django~=4.0 python-dotenv~=0.19 requests~=2.27 -black==24.1.0 - +black==24.1.0 \ No newline at end of file From 2807ae84eef64890b7b31719fdfe3dc83b0424db Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Tue, 17 Sep 2024 15:04:04 +0300 Subject: [PATCH 14/27] added changes --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cd020820..fd182448 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ authlib~=1.0 django~=4.0 python-dotenv~=0.19 requests~=2.27 -black==24.1.0 \ No newline at end of file +black==24.8.0 \ No newline at end of file From 973a1f69fef9d6e8ebb88c92ba54c3fc0a572bc7 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Tue, 17 Sep 2024 17:11:23 +0300 Subject: [PATCH 15/27] added requirements --- sensor/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sensor/tests.py b/sensor/tests.py index 9e789832..fc9b5a66 100644 --- a/sensor/tests.py +++ b/sensor/tests.py @@ -27,7 +27,6 @@ def test_sensor_string_representation(self): ) def test_default_location(self): - # Happy Path: It involves Creating a Sensor without specifying Location, it verify default value default_sensor = Sensor.objects.create(Type="Water Pressure", Status="Inactive") self.assertEqual(default_sensor.Location, "karen") From e921c49905f1b0e6f02a9bf43094af7ecd5a9b88 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Tue, 17 Sep 2024 18:39:49 +0300 Subject: [PATCH 16/27] add change --- drainagesystem/models.py | 3 --- sensor/models.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/drainagesystem/models.py b/drainagesystem/models.py index 48da60c0..9da3ba4e 100644 --- a/drainagesystem/models.py +++ b/drainagesystem/models.py @@ -11,6 +11,3 @@ class DrainageSystem(models.Model): def __str__(self): return f"Drainagesystem {self.Drainage_ID}" - - -# Create your models here. diff --git a/sensor/models.py b/sensor/models.py index 910659ea..b5b41f93 100644 --- a/sensor/models.py +++ b/sensor/models.py @@ -10,6 +10,3 @@ class Sensor(models.Model): def __str__(self): return f"Sensor {self.Sensor_ID} - {self.Type}" - - -# Create your models here. From 10e5f3f68e81817a6fa70ad7703679a5fae4a269 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Tue, 17 Sep 2024 18:57:38 +0300 Subject: [PATCH 17/27] added changes --- api/models.py | 2 -- sensor/views.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/api/models.py b/api/models.py index 71a83623..137941ff 100644 --- a/api/models.py +++ b/api/models.py @@ -1,3 +1 @@ from django.db import models - -# Create your models here. diff --git a/sensor/views.py b/sensor/views.py index 91ea44a2..2536b376 100644 --- a/sensor/views.py +++ b/sensor/views.py @@ -1,3 +1 @@ from django.shortcuts import render - -# Create your views here. From 264aa574d5189d7f2364fcb82e04ce147dd89ff5 Mon Sep 17 00:00:00 2001 From: Linet Mkandoe Ndau <158444032+Linetmkandoe@users.noreply.github.com> Date: Tue, 17 Sep 2024 19:37:41 +0300 Subject: [PATCH 18/27] Update aquasense.yml --- .github/workflows/aquasense.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/aquasense.yml b/.github/workflows/aquasense.yml index 64ea9847..11dac168 100644 --- a/.github/workflows/aquasense.yml +++ b/.github/workflows/aquasense.yml @@ -27,6 +27,9 @@ jobs: - name: Run tests run: | python manage.py test + - name: Format code with black + run: | + black . From 26db1195a85b3255ff325a0715884bf6bbe0a4ec Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Wed, 18 Sep 2024 10:51:26 +0300 Subject: [PATCH 19/27] added yml file --- .github/workflows/django.yml | 17 +++++++++++++++++ aquasens/settings.py | 21 ++++++++++++++------- notification/views.py | 2 -- user/views.py | 2 -- 4 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/django.yml diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml new file mode 100644 index 00000000..7ef8911b --- /dev/null +++ b/.github/workflows/django.yml @@ -0,0 +1,17 @@ +name: Django CI +on: + push: + branches: [ "main" ] +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Set up Python environment + uses: actions/setup-python@v2 + with: + python-version: 3.11 + - name: Install dependencies + run: | + pip install -r requirements.txt \ No newline at end of file diff --git a/aquasens/settings.py b/aquasens/settings.py index ed4dc0f1..087244f9 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -24,7 +24,7 @@ DEBUG = True -ALLOWED_HOSTS = ["localhost"] +ALLOWED_HOSTS = ["*"] # Application definition @@ -55,6 +55,7 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", ] ROOT_URLCONF = "aquasens.urls" @@ -98,12 +99,18 @@ # Database # https://docs.djangoproject.com/en/5.1/ref/settings/#databases -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", +import os +import dj_database_url + +DATABASES = {"default": dj_database_url.config(default=os.getenv("DATABASE_URL"))} +# Fallback for local development and test environments +if not os.getenv("DATABASE_URL"): + DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } } -} # Password validation @@ -141,6 +148,7 @@ # https://docs.djangoproject.com/en/5.1/howto/static-files/ STATIC_URL = "static/" +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles/") # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field @@ -152,5 +160,4 @@ REDIRECT_URI = "http://localhost:8000/auth/callback/" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - ALLOWED_HOSTS = ["127.0.0.1", "localhost"] diff --git a/notification/views.py b/notification/views.py index 91ea44a2..2536b376 100644 --- a/notification/views.py +++ b/notification/views.py @@ -1,3 +1 @@ from django.shortcuts import render - -# Create your views here. diff --git a/user/views.py b/user/views.py index 91ea44a2..2536b376 100644 --- a/user/views.py +++ b/user/views.py @@ -1,3 +1 @@ from django.shortcuts import render - -# Create your views here. From c4793693a391f6867facdd8f4f283256b113640c Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Wed, 18 Sep 2024 12:08:35 +0300 Subject: [PATCH 20/27] added changes --- .github/workflows/django.yml | 17 ++++++++++++++++- aquasens/wsgi.py | 2 -- authentication/models.py | 3 --- notification/models.py | 3 --- requirements.txt | 9 ++++++++- sensor/admin.py | 2 -- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 7ef8911b..a0c7be35 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -14,4 +14,19 @@ jobs: python-version: 3.11 - name: Install dependencies run: | - pip install -r requirements.txt \ No newline at end of file + pip install -r requirements.txt + - name: Set up Git remote for Heroku + run: | + git remote -v + git remote add heroku https://git.heroku.com/aquasense.git || echo "Heroku remote already exists" + - name: Set Heroku buildpack + run: | + heroku buildpacks:set heroku/python -a aquasense + env: + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + - name: Deploy to Heroku + uses: akhileshns/heroku-deploy@v3.13.15 + with: + heroku_api_key: ${{ secrets.HEROKU_API_KEY }} + heroku_app_name: "aquasense" + heroku_email: ${{ secrets.HEROKU_EMAIL }} diff --git a/aquasens/wsgi.py b/aquasens/wsgi.py index c8eab803..e30c0f54 100644 --- a/aquasens/wsgi.py +++ b/aquasens/wsgi.py @@ -8,9 +8,7 @@ """ import os - from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aquasens.settings") - application = get_wsgi_application() diff --git a/authentication/models.py b/authentication/models.py index c389f5d7..137941ff 100644 --- a/authentication/models.py +++ b/authentication/models.py @@ -1,4 +1 @@ from django.db import models - - -# Create your models here. diff --git a/notification/models.py b/notification/models.py index b949b565..45ffe696 100644 --- a/notification/models.py +++ b/notification/models.py @@ -17,6 +17,3 @@ class Notification(models.Model): def __str__(self): return self.title - - -# Create your models here. diff --git a/requirements.txt b/requirements.txt index fd182448..92a037c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,11 @@ authlib~=1.0 django~=4.0 python-dotenv~=0.19 requests~=2.27 -black==24.8.0 \ No newline at end of file +black==24.8.0 +whitenoise==6.7.0 +gunicorn==23.0.0 +psycopg2-binary==2.9.9 +dj-database-url==2.2.0 +django-cors-headers==4.4.0 +djangorestframework==3.15.2 +protobuf==3.12.4 \ No newline at end of file diff --git a/sensor/admin.py b/sensor/admin.py index 8c38f3f3..694323fa 100644 --- a/sensor/admin.py +++ b/sensor/admin.py @@ -1,3 +1 @@ from django.contrib import admin - -# Register your models here. From 16f90ecfeed23b142be11a9de0ff1501721dd5c6 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Wed, 18 Sep 2024 14:47:59 +0300 Subject: [PATCH 21/27] changed to all --- .github/workflows/aquasense.yml | 9 +-------- .github/workflows/django.yml | 5 ++++- aquasens/settings.py | 18 ++++++++---------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.github/workflows/aquasense.yml b/.github/workflows/aquasense.yml index 64ea9847..03d7e74d 100644 --- a/.github/workflows/aquasense.yml +++ b/.github/workflows/aquasense.yml @@ -26,11 +26,4 @@ jobs: python manage.py migrate - name: Run tests run: | - python manage.py test - - - - - - - + python manage.py test \ No newline at end of file diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index a0c7be35..24572852 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -23,10 +23,13 @@ jobs: run: | heroku buildpacks:set heroku/python -a aquasense env: - HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}aquasense-project - name: Deploy to Heroku uses: akhileshns/heroku-deploy@v3.13.15 with: heroku_api_key: ${{ secrets.HEROKU_API_KEY }} heroku_app_name: "aquasense" heroku_email: ${{ secrets.HEROKU_EMAIL }} + + + diff --git a/aquasens/settings.py b/aquasens/settings.py index 087244f9..31d1fcf8 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -22,11 +22,6 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True - - -ALLOWED_HOSTS = ["*"] - - # Application definition INSTALLED_APPS = [ @@ -45,6 +40,7 @@ "map", "sensor", "notification", + "corsheaders", ] MIDDLEWARE = [ @@ -52,6 +48,7 @@ "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", + "corsheaders.middleware.CorsMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", @@ -149,15 +146,16 @@ STATIC_URL = "static/" STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles/") +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field -AUTH0_CLIENT_ID = os.getenv("AUTH0_CLIENT_ID") -AUTH0_CLIENT_SECRET = os.getenv("AUTH0_CLIENT_SECRET") -AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN") +AUTH0_CLIENT_ID = os.getenv("AUTH0_CLIENT_ID", "") +AUTH0_CLIENT_SECRET = os.getenv("AUTH0_CLIENT_SECRET", "") +AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN", "") -REDIRECT_URI = "http://localhost:8000/auth/callback/" +REDIRECT_URI = os.environ.get("REDIRECT_URI", "") DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" -ALLOWED_HOSTS = ["127.0.0.1", "localhost"] +ALLOWED_HOSTS = ["*"] From 6ef15f4c28043038b2ef17920959f455c98935dc Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Wed, 18 Sep 2024 16:35:00 +0300 Subject: [PATCH 22/27] added changes --- aquasens/settings.py | 1 - drainagesystem/views.py | 2 -- requirements.txt | 31 ++++++++++++++++++++++--------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/aquasens/settings.py b/aquasens/settings.py index 31d1fcf8..70e0a37a 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -100,7 +100,6 @@ import dj_database_url DATABASES = {"default": dj_database_url.config(default=os.getenv("DATABASE_URL"))} -# Fallback for local development and test environments if not os.getenv("DATABASE_URL"): DATABASES = { "default": { diff --git a/drainagesystem/views.py b/drainagesystem/views.py index 91ea44a2..2536b376 100644 --- a/drainagesystem/views.py +++ b/drainagesystem/views.py @@ -1,3 +1 @@ from django.shortcuts import render - -# Create your views here. diff --git a/requirements.txt b/requirements.txt index 92a037c4..d5576544 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,25 @@ -authlib~=1.0 -django~=4.0 -python-dotenv~=0.19 -requests~=2.27 -black==24.8.0 -whitenoise==6.7.0 -gunicorn==23.0.0 -psycopg2-binary==2.9.9 +asgiref==3.8.1 +Authlib==1.3.2 +certifi==2024.8.30 +cffi==1.17.1 +charset-normalizer==3.3.2 +cryptography==43.0.1 dj-database-url==2.2.0 +Django==5.1.1 django-cors-headers==4.4.0 djangorestframework==3.15.2 -protobuf==3.12.4 \ No newline at end of file +djangorestframework-simplejwt==5.3.1 +gunicorn==23.0.0 +idna==3.10 +packaging==24.1 +protobuf==3.12.4 +psycopg2-binary==2.9.9 +pycparser==2.22 +PyJWT==2.9.0 +python-dotenv==1.0.1 +requests==2.32.3 +six==1.16.0 +sqlparse==0.5.1 +typing_extensions==4.12.2 +urllib3==2.2.3 +whitenoise==6.7.0 From 213729a02bfe1525b62ff7f8bcd74e1d7b69c2bb Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Wed, 18 Sep 2024 18:09:40 +0300 Subject: [PATCH 23/27] made changes --- aquasens/settings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/aquasens/settings.py b/aquasens/settings.py index 70e0a37a..2a4bcc3f 100644 --- a/aquasens/settings.py +++ b/aquasens/settings.py @@ -153,8 +153,6 @@ AUTH0_CLIENT_ID = os.getenv("AUTH0_CLIENT_ID", "") AUTH0_CLIENT_SECRET = os.getenv("AUTH0_CLIENT_SECRET", "") AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN", "") - - REDIRECT_URI = os.environ.get("REDIRECT_URI", "") DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" ALLOWED_HOSTS = ["*"] From f0e1bc53a51b62566e44745b30638f38ac991ee5 Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Wed, 18 Sep 2024 18:31:01 +0300 Subject: [PATCH 24/27] maded changes --- requirements.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/requirements.txt b/requirements.txt index d5576544..3cefc8e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,15 @@ python-dotenv==1.0.1 requests==2.32.3 six==1.16.0 sqlparse==0.5.1 +authlib~=1.0 +django~=4.0 +python-dotenv~=0.19 +requests~=2.27 +black==24.8.0 +whitenoise==6.7.0 +gunicorn==23.0.0 +psycopg2-binary==2.9.9 typing_extensions==4.12.2 urllib3==2.2.3 whitenoise==6.7.0 +protobuf==3.12.4 From d55a9af42af4c853b4caec10614fb69a292fdd2f Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Wed, 18 Sep 2024 19:43:51 +0300 Subject: [PATCH 25/27] downgraded Django --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3cefc8e5..e5ccc812 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ cffi==1.17.1 charset-normalizer==3.3.2 cryptography==43.0.1 dj-database-url==2.2.0 -Django==5.1.1 +django==4.2.1 django-cors-headers==4.4.0 djangorestframework==3.15.2 djangorestframework-simplejwt==5.3.1 From 18cf17b400cfcdc8c536b6e21bd1ae21cfe23d7e Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Wed, 18 Sep 2024 20:05:39 +0300 Subject: [PATCH 26/27] made changes --- requirements.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index e5ccc812..638287fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,6 @@ django==4.2.1 django-cors-headers==4.4.0 djangorestframework==3.15.2 djangorestframework-simplejwt==5.3.1 -gunicorn==23.0.0 idna==3.10 packaging==24.1 protobuf==3.12.4 @@ -20,15 +19,10 @@ python-dotenv==1.0.1 requests==2.32.3 six==1.16.0 sqlparse==0.5.1 -authlib~=1.0 -django~=4.0 -python-dotenv~=0.19 requests~=2.27 black==24.8.0 whitenoise==6.7.0 gunicorn==23.0.0 -psycopg2-binary==2.9.9 typing_extensions==4.12.2 urllib3==2.2.3 whitenoise==6.7.0 -protobuf==3.12.4 From 61f46c258ce99c312169e66dc88387dd7eee186e Mon Sep 17 00:00:00 2001 From: Linet Ndau Date: Wed, 18 Sep 2024 20:39:59 +0300 Subject: [PATCH 27/27] made changes --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 638287fb..cad5c9ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ cffi==1.17.1 charset-normalizer==3.3.2 cryptography==43.0.1 dj-database-url==2.2.0 -django==4.2.1 +Django==4.2.1 django-cors-headers==4.4.0 djangorestframework==3.15.2 djangorestframework-simplejwt==5.3.1