From ae86a2e5fa87e84e7a51fa000cd25ecd521a9fa7 Mon Sep 17 00:00:00 2001 From: Emanuele Date: Wed, 19 Nov 2025 13:37:34 +0100 Subject: [PATCH] Deploy application in container using compose to bring up db --- cntmanage/cntmanage/asgi.py | 3 +- cntmanage/cntmanage/settings.py | 5 +- cntmanage/cntmanage/settings_prod.py | 142 +++++++++++++++++++++ cntmanage/cntmanage/urls.py | 2 + cntmanage/cntmanage/wsgi.py | 3 +- cntmanage/docker/docker-compose.yml | 20 ++- cntmanage/docker/entrypoint.sh | 20 +++ cntmanage/docker/flighslot.Dockerfile | 40 ++++++ cntmanage/flightslot/admins/student_adm.py | 2 +- cntmanage/pyproject.toml | 3 +- 10 files changed, 230 insertions(+), 10 deletions(-) create mode 100644 cntmanage/cntmanage/settings_prod.py create mode 100755 cntmanage/docker/entrypoint.sh create mode 100644 cntmanage/docker/flighslot.Dockerfile diff --git a/cntmanage/cntmanage/asgi.py b/cntmanage/cntmanage/asgi.py index fba326b..859e11a 100644 --- a/cntmanage/cntmanage/asgi.py +++ b/cntmanage/cntmanage/asgi.py @@ -11,6 +11,7 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cntmanage.settings') +settings = os.environ.get("DJANGO_SETTINGS_MODULE", "cntmanage.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings) application = get_asgi_application() diff --git a/cntmanage/cntmanage/settings.py b/cntmanage/cntmanage/settings.py index f3ad71c..0d3392d 100644 --- a/cntmanage/cntmanage/settings.py +++ b/cntmanage/cntmanage/settings.py @@ -28,7 +28,6 @@ DEBUG = True ALLOWED_HOSTS = [] - # Application definition INSTALLED_APPS = [ @@ -127,9 +126,9 @@ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' -USE_I18N = True +USE_I18N = False # Disable translation engine -USE_TZ = True +USE_TZ = True # Static files (CSS, JavaScript, Images) diff --git a/cntmanage/cntmanage/settings_prod.py b/cntmanage/cntmanage/settings_prod.py new file mode 100644 index 0000000..82fee23 --- /dev/null +++ b/cntmanage/cntmanage/settings_prod.py @@ -0,0 +1,142 @@ +""" +Django settings for techdb project. + +Generated by 'django-admin startproject' using Django 5.1.2. + +For more information on this file, see +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.1/ref/settings/ +""" + +from pathlib import Path +import os + +# 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.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-s%(9^y#!1*ge)7u%$vf3zp0lisgd%=(k@$13&ej13p5(ei71hi') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +# Allowed hosts list is necessary for when in production +ALLOWED_HOSTS = ['*'] + +# Using a secure-only session cookie makes it more difficult for network traffic sniffers to hijack user sessions. +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True + +# Application definition +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'nested_admin', + 'flightslot', + 'durationwidget', + 'colorfield', + 'import_export', + 'django_admin_action_forms', +] + +# Import Export plugin settings +from import_export.formats.base_formats import CSV +IMPORT_EXPORT_USE_TRANSACTIONS = True +IMPORT_EXPORT_SKIP_ADMIN_LOG = True +IMPORT_FORMATS = [CSV] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', + '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 = 'cntmanage.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': ['/var/www/templates'], + '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 = 'cntmanage.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ.get('DB_NAME','techstorage'), + 'USER': os.environ.get('DB_USER','tech'), + 'PASSWORD': os.environ.get('DB_PASSWORD','tech'), + 'HOST': os.environ.get('DB_HOST','postgresql'), + 'PORT': os.environ.get('DB_PORT','5432') + } +} + +# Password validation +# https://docs.djangoproject.com/en/5.1/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.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = False # Disable translation engine + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ +STATIC_URL = "static/" +STATIC_ROOT = "/var/www/static/" +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/cntmanage/cntmanage/urls.py b/cntmanage/cntmanage/urls.py index b269d16..c7d9f58 100644 --- a/cntmanage/cntmanage/urls.py +++ b/cntmanage/cntmanage/urls.py @@ -16,8 +16,10 @@ Including another URLconf """ from django.contrib import admin from django.urls import path +from django.views.generic import RedirectView urlpatterns = [ + path('', RedirectView.as_view(url='/admin/', permanent=False)), path('admin/', admin.site.urls), ] diff --git a/cntmanage/cntmanage/wsgi.py b/cntmanage/cntmanage/wsgi.py index 76fabc7..c1c5597 100644 --- a/cntmanage/cntmanage/wsgi.py +++ b/cntmanage/cntmanage/wsgi.py @@ -11,6 +11,7 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cntmanage.settings') +settings = os.environ.get("DJANGO_SETTINGS_MODULE", "cntmanage.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings) application = get_wsgi_application() diff --git a/cntmanage/docker/docker-compose.yml b/cntmanage/docker/docker-compose.yml index 7f5529a..98cbef7 100644 --- a/cntmanage/docker/docker-compose.yml +++ b/cntmanage/docker/docker-compose.yml @@ -1,8 +1,5 @@ # Use postgres/example user/password credentials -version: '3.9' - services: - postgresql: image: postgres:17.0 container_name: tech-postgresql @@ -19,3 +16,20 @@ services: POSTGRED_DB: techstorage PGDATA: /var/lib/postgresql/data + flightslot: + image: flightslot:dev + container_name: tech-flightslot + restart: unless-stopped + ports: + - 8000:8000 + depends_on: + - postgresql + environment: + - DJANGO_SETTINGS_MODULE=cntmanage.settings_prod + - SECRET_KEY=6WIjA!+mI+ZOWHaJm6v^8F4o,@-gliDtwkp*QFvpkFe"Oo0quq + - DB_NAME=techstorage + - DB_USER=tech + - DB_PASSWORD=tech + - DB_HOST=postgresql + - DB_PORT=5432 + \ No newline at end of file diff --git a/cntmanage/docker/entrypoint.sh b/cntmanage/docker/entrypoint.sh new file mode 100755 index 0000000..cd9c093 --- /dev/null +++ b/cntmanage/docker/entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e + +echo "📦 Starting Django deploy script..." + +echo "🔧 Running migrations..." +django-admin migrate --noinput + +echo "📁 Collecting static files..." +django-admin collectstatic --noinput + +echo "📁 Manually copying static files..." +cp -v /app/static/* /var/www/static/ + +echo "📁 Manually copying template files..." +cp -rv /app/templates/ /var/www/templates/ + +echo "🚀 Launching Flightslot..." +exec "$@" diff --git a/cntmanage/docker/flighslot.Dockerfile b/cntmanage/docker/flighslot.Dockerfile new file mode 100644 index 0000000..8a92d6f --- /dev/null +++ b/cntmanage/docker/flighslot.Dockerfile @@ -0,0 +1,40 @@ +### STAGE 1 - Builder image ### +# Builder container +FROM python:3.12 AS builder +# Install Poetry +RUN curl -sSL https://install.python-poetry.org | python3 - +ENV PATH="${PATH}:/root/.local/bin" +RUN env +# Create build directory +WORKDIR /build +# Copy project files +COPY . ./ +# Run poetry update to download dependencies +RUN poetry update --no-interaction --no-ansi +# Build project +RUN poetry build + +### STAGE 2 — Final image +FROM python:3.12-slim AS deploy + +WORKDIR /app + +# Copy application custom static files +RUN mkdir -p static +COPY ./static/cantorair.jpg ./static + +# Copy application custom templates for admin page +RUN mkdir -p /templates/admin +COPY ./templates/admin/* ./templates/admin/ + +# Copy and install application wheel package +COPY --from=builder /build/dist/*.whl ./ +RUN pip install --no-cache-dir *.whl +RUN pip install gunicorn whitenoise + +# Copy entryupoint bash script +COPY ./docker/entrypoint.sh ./ +ENTRYPOINT ["/app/entrypoint.sh"] + +# Command to be executed after entry point +CMD ["gunicorn", "cntmanage.wsgi:application", "--bind", "0.0.0.0:8000"] diff --git a/cntmanage/flightslot/admins/student_adm.py b/cntmanage/flightslot/admins/student_adm.py index f3adf2d..63b7e90 100644 --- a/cntmanage/flightslot/admins/student_adm.py +++ b/cntmanage/flightslot/admins/student_adm.py @@ -58,7 +58,7 @@ class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin): def password(self, obj: Student) -> SafeText: return SafeText(obj.default_password()) - @admin.action(description="Disable Students") + @admin.action(description="Deactivate Students") def disable_students(self, request: HttpRequest, queryset: QuerySet[Student]): for q in queryset.all(): if q.user: diff --git a/cntmanage/pyproject.toml b/cntmanage/pyproject.toml index f7542a6..6f403fb 100644 --- a/cntmanage/pyproject.toml +++ b/cntmanage/pyproject.toml @@ -1,11 +1,12 @@ [tool.poetry] name = "cntmanage" version = "0.1.0" -packages = [{include = "flightslot"}] +packages = [{include = "flightslot"}, {include = "cntmanage"}] description = "CantorAir Flight Scheduler" authors = ["Emanuele "] readme = "README.md" + [tool.poetry.dependencies] python = "^3.12" django = "^5.1.2"