From 96905d9f78a1b862b389d80f66bf5fcfe5deb569 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Tue, 16 Jan 2024 21:32:07 +0100 Subject: [PATCH] finalising the project configuration --- Dockerfile | 11 +- Palto/Palto/migrations/0001_initial.py | 167 +++++++++++++++++++++++++ Palto/Palto/views.py | 2 +- Palto/settings.py | 47 ++++--- Palto/wsgi.py | 4 - README.md | 27 ++++ manage.py | 6 - requirements.txt | 1 - utils/__init__.py | 0 utils/env.py | 23 ---- 10 files changed, 235 insertions(+), 53 deletions(-) create mode 100644 Palto/Palto/migrations/0001_initial.py delete mode 100644 utils/__init__.py delete mode 100644 utils/env.py diff --git a/Dockerfile b/Dockerfile index dd06acd..fc7d078 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,9 @@ FROM python:3.12 # Set environment variables for Django ENV DJANGO_SETTINGS_MODULE=Palto.settings +ENV DJANGO_SECRET_KEY="" +ENV DATABASE_ENGINE="sqlite" +ENV DEBUG=false # Set the working directory in the container WORKDIR /App @@ -10,8 +13,12 @@ WORKDIR /App # Copy the current directory contents into the container COPY . /App -# Install any needed packages specified in requirements.txt -RUN pip install -r requirements.txt +# Install requirements +RUN python -m pip install -r requirements.txt + +# Prepare the server +RUN python manage.py collectstatic --no-input +RUN python manage.py migrate # Expose the port on which your Django application will run EXPOSE 80 diff --git a/Palto/Palto/migrations/0001_initial.py b/Palto/Palto/migrations/0001_initial.py new file mode 100644 index 0000000..cd48c7a --- /dev/null +++ b/Palto/Palto/migrations/0001_initial.py @@ -0,0 +1,167 @@ +# Generated by Django 5.0.1 on 2024-01-15 17:37 + +import Palto.Palto.models +import django.contrib.auth.models +import django.contrib.auth.validators +import django.db.models.deletion +import django.utils.timezone +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='Absence', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('message', models.TextField()), + ('start', models.DateTimeField()), + ('end', models.DateTimeField()), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.CreateModel( + name='Department', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64, unique=True)), + ('email', models.EmailField(max_length=254)), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.CreateModel( + name='AbsenceAttachment', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('content', models.FileField(upload_to='absence/attachment/')), + ('absence', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='Palto.absence')), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.AddField( + model_name='absence', + name='department', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='Palto.department'), + ), + migrations.CreateModel( + name='StudentGroup', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128)), + ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_groups', to='Palto.department')), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.CreateModel( + name='User', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='TeachingUnit', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=64)), + ('email', models.EmailField(blank=True, max_length=254, null=True)), + ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teaching_units', to='Palto.department')), + ('student_groups', models.ManyToManyField(blank=True, related_name='studying_units', to='Palto.studentgroup')), + ('managers', models.ManyToManyField(blank=True, related_name='managing_units', to=settings.AUTH_USER_MODEL)), + ('teachers', models.ManyToManyField(blank=True, related_name='teaching_units', to=settings.AUTH_USER_MODEL)), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.CreateModel( + name='TeachingSession', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('start', models.DateTimeField()), + ('duration', models.DurationField()), + ('note', models.TextField(blank=True)), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teaching_sessions', to='Palto.studentgroup')), + ('unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sessions', to='Palto.teachingunit')), + ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teaching_sessions', to=settings.AUTH_USER_MODEL)), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.AddField( + model_name='studentgroup', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owning_groups', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='studentgroup', + name='students', + field=models.ManyToManyField(blank=True, related_name='student_groups', to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='StudentCard', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('uid', models.BinaryField(max_length=7)), + ('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_cards', to='Palto.department')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_cards', to=settings.AUTH_USER_MODEL)), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.AddField( + model_name='department', + name='managers', + field=models.ManyToManyField(blank=True, related_name='managing_departments', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='department', + name='students', + field=models.ManyToManyField(blank=True, related_name='studying_departments', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='department', + name='teachers', + field=models.ManyToManyField(blank=True, related_name='teaching_departments', to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='Attendance', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('date', models.DateTimeField()), + ('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attendances', to='Palto.teachingsession')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attended_sessions', to=settings.AUTH_USER_MODEL)), + ], + bases=(models.Model, Palto.Palto.models.ModelPermissionHelper), + ), + migrations.AddField( + model_name='absence', + name='student', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absences', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/Palto/Palto/views.py b/Palto/Palto/views.py index 3b0de87..adf0d1b 100644 --- a/Palto/Palto/views.py +++ b/Palto/Palto/views.py @@ -218,7 +218,7 @@ def new_absence_view(request: WSGIRequest): content=file ) - return redirect("Palto:homepage") # TODO(Faraphel): redirect to absence list + return redirect("Palto:absence_list") # render the page return render( diff --git a/Palto/settings.py b/Palto/settings.py index efe4b7a..c6c54a6 100644 --- a/Palto/settings.py +++ b/Palto/settings.py @@ -10,9 +10,12 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.2/ref/settings/ """ import os +import warnings from datetime import timedelta from pathlib import Path +from django.core.management.utils import get_random_secret_key + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -21,22 +24,16 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ["DJANGO_SECRET"] +SECRET_KEY = os.getenv("DJANGO_SECRET_KEY") +if not SECRET_KEY: + SECRET_KEY = get_random_secret_key() + warnings.warn('The Django secret key should be defined in the "DJANGO_SECRET_KEY" variable environment.') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = os.getenv("DEBUG", "false").lower() in ["1", "true"] -ALLOWED_HOSTS = [ - "127.0.0.1", - "localhost", - "0.0.0.0", -] - -INTERNAL_IPS = [ - "127.0.0.1", - "localhost", - "0.0.0.0", -] +ALLOWED_HOSTS = os.getenv("", "localhost 0.0.0.0").split(" ") +INTERNAL_IPS = ["localhost"] # Application definition @@ -94,12 +91,30 @@ WSGI_APPLICATION = 'Palto.wsgi.application' # https://docs.djangoproject.com/en/4.2/ref/settings/#databases DATABASES = { - 'default': { + "sqlite": { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } + 'NAME': BASE_DIR / os.getenv("DATABASE_SQLITE_FILENAME", "db.sqlite3"), + }, + "mysql": { + 'ENGINE': 'django.db.backends.mysql', + "NAME": os.getenv("MYSQL_POSTGRES_NAME"), + "USER": os.getenv("MYSQL_POSTGRES_USER"), + "PASSWORD": os.getenv("MYSQL_POSTGRES_PASSWORD"), + "HOST": os.getenv("MYSQL_POSTGRES_HOST", "localhost"), + "PORT": os.getenv("MYSQL_POSTGRES_PORT", "5432"), + }, + "postgres": { + "ENGINE": "django.db.backends.postgresql", + "NAME": os.getenv("DATABASE_POSTGRES_NAME"), + "USER": os.getenv("DATABASE_POSTGRES_USER"), + "PASSWORD": os.getenv("DATABASE_POSTGRES_PASSWORD"), + "HOST": os.getenv("DATABASE_POSTGRES_HOST", "localhost"), + "PORT": os.getenv("DATABASE_POSTGRES_PORT", "5432"), + }, } +DATABASES["default"] = DATABASES[os.getenv("DATABASE_ENGINE", "sqlite")] + # Password validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators diff --git a/Palto/wsgi.py b/Palto/wsgi.py index 1d5c335..8150fe1 100644 --- a/Palto/wsgi.py +++ b/Palto/wsgi.py @@ -8,11 +8,7 @@ https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ """ -import dotenv from django.core.wsgi import get_wsgi_application -from utils import env - -dotenv.load_dotenv(env.create_dotenv()) application = get_wsgi_application() diff --git a/README.md b/README.md index eb73612..dbf3b47 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ this session. # Installation +## Classic + 1. Install `python >= 3.11` 2. Create a virtual environment with `python -m venv ./.venv/`. The next steps will be inside it. 3. Install the dependencies with `python -m pip install -r ./requirements.txt`. @@ -16,3 +18,28 @@ this session. 5. Make the migrations with `python ./manage.py makemigrations`. 6. Apply the migrations to the database with `python ./manage.py migrate`. 7. Run the program by with `python ./manage.py runserver`. + +## Docker + +1. Start a terminal in the directory of the project. +2. Run `docker build`. +3. Change the environment variables to match your configuration. + +# Advanced Settings + +## Debug Mode + +By default, the server is launch in production mode. +This disables the automatic static files serving since they are considered as being already served by nginx or apache. +You can start with the environment variable `DEBUG=true` to start it in development mode. + +## Secret Key + +You should set a django secret key manually in the `DJANGO_SECRET_KEY` environment variable. You can get one by +opening a python interpreter with django and calling the function `django.core.management.utils.get_random_secret_key()`. + +## Database + +The database used by default is `sqlite`. This is not recommended to keep it since it won't be saved by docker after +a restart if no volume are set, and it is considered a slow database engine. Using a `postgres` database is recommended. +You can find more details about the database in the configuration `settings.py`. \ No newline at end of file diff --git a/manage.py b/manage.py index 3dae112..6626c44 100644 --- a/manage.py +++ b/manage.py @@ -3,15 +3,9 @@ import sys -import dotenv - -from utils import env - def main(): """Run administrative tasks.""" - dotenv.load_dotenv(env.create_dotenv()) - try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/requirements.txt b/requirements.txt index 0e80e38..7207d04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,3 @@ factory_boy # Other librairies markdown -python-dotenv diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/utils/env.py b/utils/env.py deleted file mode 100644 index de4c4af..0000000 --- a/utils/env.py +++ /dev/null @@ -1,23 +0,0 @@ -from pathlib import Path - -from django.core.management.utils import get_random_secret_key - - -path_dotenv = Path("./.env") - - -def create_dotenv(force: bool = False) -> Path: - # if not forced and the file already exist, ignore - if not force and path_dotenv.exists(): - return path_dotenv - - # otherwise create the file - path_dotenv.write_text( - ( - f"DJANGO_SETTINGS_MODULE='Palto.settings'\n" - f"DJANGO_SECRET={get_random_secret_key()!r}\n" - ), - encoding="utf-8" - ) - - return path_dotenv