From ebfbf4e339dad6aed195b5e5903f26319bc07826 Mon Sep 17 00:00:00 2001 From: Alagappan Ramanathan <96157054+AlagappanRa@users.noreply.github.com> Date: Tue, 27 Feb 2024 20:47:36 +0800 Subject: [PATCH 1/4] Switch to os.environ.get - Env vars were not being loaded properly into file context with environ.Env.read_env(). - Suspect its a bug with the package itself - Python's default read_dotenv is more reliable. --- api/migrations/__init__.py | 0 finsec_website_backend/sample.env | 12 --------- finsec_website_backend/settings.py | 39 ++++++++++++++++++++++-------- 3 files changed, 29 insertions(+), 22 deletions(-) delete mode 100644 api/migrations/__init__.py delete mode 100644 finsec_website_backend/sample.env diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/finsec_website_backend/sample.env b/finsec_website_backend/sample.env deleted file mode 100644 index a908249..0000000 --- a/finsec_website_backend/sample.env +++ /dev/null @@ -1,12 +0,0 @@ -################################################### -# THIS IS A SAMPLE FILE. DO NOT USE IN PRODUCTION # -################################################### -SECRET_KEY= - -DATABASE_URL= - -DB_NAME= -DB_USER= -DB_PASSWORD= -DB_HOST= -DB_PORT= \ No newline at end of file diff --git a/finsec_website_backend/settings.py b/finsec_website_backend/settings.py index f094362..93ab4d8 100644 --- a/finsec_website_backend/settings.py +++ b/finsec_website_backend/settings.py @@ -17,13 +17,30 @@ import environ import os -env = environ.Env() -environ.Env.read_env() +# Define a function to manually parse the .env file +def read_dotenv(env_file_path): + with open(env_file_path) as f: + for line in f: + if line.startswith('#') or not line.strip(): + continue # Skip comments and empty lines + # Split the line into key/value pair + key, value = line.strip().split('=', 1) + # Set the value in environment variables + os.environ[key] = value + +# Path to your .env file +env_file = os.path.join(os.path.dirname(__file__), '.env') + +# Call the function if the .env file exists +if os.path.isfile(env_file): + read_dotenv(env_file) + +# Test if the environment variables are set +print('DB_USER IS', os.environ.get('DB_USER')) # 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/4.2/howto/deployment/checklist/ @@ -87,16 +104,18 @@ # https://docs.djangoproject.com/en/4.2/ref/settings/#databases if not DEBUG: - DATABASES = {'default': dj_database_url.parse(env('DATABASE_URL'))} + DATABASES = { + 'default': dj_database_url.parse(os.environ.get('DATABASE_URL')) + } else: DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': env("DB_NAME"), - 'USER': env("DB_USER"), - 'PASSWORD': env("DB_PASSWORD"), - 'HOST': env("DB_HOST"), - 'PORT': env("DB_PORT"), + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ.get('DB_NAME'), + 'USER': os.environ.get('DB_USER'), + 'PASSWORD': os.environ.get('DB_PASSWORD'), + 'HOST': os.environ.get('DB_HOST'), + 'PORT': os.environ.get('DB_PORT'), } } From 66f275e6d3623a67c82d000512af9cc33db513ce Mon Sep 17 00:00:00 2001 From: Alagappan Ramanathan <96157054+AlagappanRa@users.noreply.github.com> Date: Tue, 27 Feb 2024 21:07:39 +0800 Subject: [PATCH 2/4] Update settings.py Update settings.py --- finsec_website_backend/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/finsec_website_backend/settings.py b/finsec_website_backend/settings.py index 93ab4d8..9f38cd9 100644 --- a/finsec_website_backend/settings.py +++ b/finsec_website_backend/settings.py @@ -14,7 +14,6 @@ from datetime import timedelta import dj_database_url -import environ import os # Define a function to manually parse the .env file From fbdca297735ab0fbaf89c7db2a2b2acf1d0a731b Mon Sep 17 00:00:00 2001 From: Alagappan Ramanathan <96157054+AlagappanRa@users.noreply.github.com> Date: Tue, 27 Feb 2024 21:18:56 +0800 Subject: [PATCH 3/4] Update settings.py Update settings.py --- finsec_website_backend/settings.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/finsec_website_backend/settings.py b/finsec_website_backend/settings.py index 9f38cd9..d5dc6a0 100644 --- a/finsec_website_backend/settings.py +++ b/finsec_website_backend/settings.py @@ -16,21 +16,16 @@ import dj_database_url import os -# Define a function to manually parse the .env file def read_dotenv(env_file_path): with open(env_file_path) as f: for line in f: if line.startswith('#') or not line.strip(): - continue # Skip comments and empty lines - # Split the line into key/value pair + continue key, value = line.strip().split('=', 1) - # Set the value in environment variables os.environ[key] = value -# Path to your .env file env_file = os.path.join(os.path.dirname(__file__), '.env') -# Call the function if the .env file exists if os.path.isfile(env_file): read_dotenv(env_file) From 4245427446756820d4d14d7ffc8bb620957aedff Mon Sep 17 00:00:00 2001 From: Alagappan Ramanathan <96157054+AlagappanRa@users.noreply.github.com> Date: Wed, 28 Feb 2024 02:48:46 +0800 Subject: [PATCH 4/4] Create and Read for Users - Default implementation for create and read users in models API - views methods UserListCreate and UserDetail completed - 3 tests created and passed locally --- api/migrations/0001_initial.py | 33 +++++++++++++++++++++ api/migrations/__init__.py | 0 api/models.py | 18 +++++++++++- api/serializers.py | 7 +++++ api/tests.py | 53 ++++++++++++++++++++++++++++++++-- api/views.py | 13 +++++++-- finsec_website_backend/urls.py | 5 +++- 7 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 api/migrations/0001_initial.py create mode 100644 api/migrations/__init__.py create mode 100644 api/serializers.py diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py new file mode 100644 index 0000000..a04ae75 --- /dev/null +++ b/api/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 4.1.8 on 2024-02-27 17:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Role', + fields=[ + ('role_id', models.AutoField(primary_key=True, serialize=False)), + ('role_name', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='User', + fields=[ + ('user_id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('password', models.CharField(max_length=100)), + ('email', models.EmailField(max_length=254, unique=True)), + ('subcomm', models.CharField(max_length=100)), + ('role_id', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='api.role')), + ], + ), + ] diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models.py b/api/models.py index 71a8362..ceb8b0d 100644 --- a/api/models.py +++ b/api/models.py @@ -1,3 +1,19 @@ from django.db import models -# Create your models here. +class User(models.Model): + user_id = models.AutoField(primary_key=True) + name = models.CharField(max_length=100) #default length of 100 for name fields + password = models.CharField(max_length=100) + email = models.EmailField(unique=True) + role_id = models.ForeignKey('Role', on_delete=models.SET_NULL, null=True) #assume we do not want to cascade delete + subcomm = models.CharField(max_length=100) #subcomms assumed to be a name field + + def __str__(self): + return str({"user_id": self.user_id, "name": self.name, "email": self.email, "role_id": self.role_id, "subcomm": self.subcomm}) + +class Role(models.Model): + role_id = models.AutoField(primary_key=True) + role_name = models.CharField(max_length=100) + + def __str__(self): + return str({"role_id": self.role_id, "role_name": self.role_name}) diff --git a/api/serializers.py b/api/serializers.py new file mode 100644 index 0000000..4697fce --- /dev/null +++ b/api/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import User + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = '__all__' # You may specify fields you want to include diff --git a/api/tests.py b/api/tests.py index 7ce503c..2f8f846 100644 --- a/api/tests.py +++ b/api/tests.py @@ -1,3 +1,52 @@ -from django.test import TestCase +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase +from .models import User, Role -# Create your tests here. +# May need to run ALTER USER CREATEDB; in psql to allow the user to create databases +class UserTests(APITestCase): + def setUp(self): + Role.objects.create(role_name="Test Role") + roleC = Role.objects.create(role_name="Test Role 3") + User.objects.create(name="Test User 3", email="test3@example.com", password="test12345", role_id=roleC, subcomm="Subcomm3") + + def test_create_user(self): + """ + Ensure we can create a new user object. + """ + data = { + "name": "Test User", + "password": "verysecurepassword", + "email": "test@example.com", + "subcomm": "Test Subcomm", + "role_id": 1 + } + + url = reverse('user-list-create') + response = self.client.post(url, data, format='json') + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(User.objects.count(), 2) # 1 user created in this test case + 1 from setUp + self.assertIn('Test User', [user.name for user in User.objects.all()]) + + def test_view_all_users(self): + """ + Ensure we can view all users. + """ + roleB = Role.objects.create(role_name="Test Role 2") + User.objects.create(name="Test User 2", email="test2@example.com", password="test12345", role_id=roleB, subcomm="Subcomm2") + + url = reverse('user-list-create') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 2) # 1 user created in this test case + 1 from setUp + + def test_view_user_by_id(self): + """ + Ensure we can view a single user by ID. + """ + user = User.objects.get(email="test3@example.com") # test3user created in setUp + url = reverse('user-detail', args=[user.user_id]) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['email'], user.email) diff --git a/api/views.py b/api/views.py index 91ea44a..3d289ab 100644 --- a/api/views.py +++ b/api/views.py @@ -1,3 +1,12 @@ -from django.shortcuts import render +from rest_framework import generics +from .models import User +from .serializers import UserSerializer + +class UserListCreate(generics.ListCreateAPIView): + queryset = User.objects.all() + serializer_class = UserSerializer + +class UserDetail(generics.RetrieveAPIView): + queryset = User.objects.all() + serializer_class = UserSerializer -# Create your views here. diff --git a/finsec_website_backend/urls.py b/finsec_website_backend/urls.py index e47e1f7..720aade 100644 --- a/finsec_website_backend/urls.py +++ b/finsec_website_backend/urls.py @@ -16,7 +16,10 @@ """ from django.contrib import admin from django.urls import path +from api.views import UserListCreate, UserDetail urlpatterns = [ - path('admin/', admin.site.urls), + path('admin/', admin.site.urls), #do not delete + path('users/', UserListCreate.as_view(), name='user-list-create'), + path('users//', UserDetail.as_view(), name='user-detail'), ]