diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..97780d1 --- /dev/null +++ b/.env.template @@ -0,0 +1,16 @@ +# Django +DJANGO_SETTINGS_MODULE=website.settings +DJANGO_SECRET_KEY=CWHZCAZBNV57tDkwGHJwTUu3PCSnGG45 +DEBUG=TRUE +ALLOWED_HOSTS=["test-vps.woutervermeer.com"] + +# Database (PostgreSQL) +POSTGRES_DB=app +POSTGRES_USER=app +POSTGRES_PASSWORD=3yF8g5Bb7XXpNHgcrhwUqYFnPHRlHlIV +POSTGRES_HOST=db +POSTGRES_PORT=5432 + +# Gunicorn +GUNICORN_WORKERS=3 +GUNICORN_TIMEOUT=120 diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..911ff80 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,17 @@ +name: Gitea build & run. +run-name: ${{ gitea.actor }} +on: + push: + branches: + - main + pull_request: + +jobs: + docker: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Build and run + run: docker compose up --abort-on-container-exit --exit-code-from web diff --git a/Dockerfile b/Dockerfile index 3447842..ec95d30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,13 @@ FROM python:3.14-alpine -RUN mkdir /app - WORKDIR /app -RUN pip install --upgrade pip - COPY requirements.txt /app/ COPY gunicorn.conf.py /app/ -COPY ./app/ /app/ +COPY ./src/ /app/ -RUN pip install --no-cache-dir -r requirements.txt +RUN pip install --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt EXPOSE 8000 diff --git a/app/website/settings.py b/app/website/settings.py deleted file mode 100644 index cc91025..0000000 --- a/app/website/settings.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Django settings for website project. - -Generated by 'django-admin startproject' using Django 6.0.3. - -For more information on this file, see -https://docs.djangoproject.com/en/6.0/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/6.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/6.0/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-c$q7wdq+u@ow74wp!&zzkxdylkueu)(+34e%!e0du&bjwoqz9z' - -# 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 = 'website.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'website.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/6.0/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} - - -# Password validation -# https://docs.djangoproject.com/en/6.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/6.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/6.0/howto/static-files/ - -STATIC_URL = 'static/' diff --git a/compose.yaml b/compose.yaml index 792c9cb..13cc024 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,9 +1,43 @@ services: web: build: . + depends_on: + - db ports: - 8000:8000 environment: - - PYTHONDONTWRITEBYTECODE=1 - - PYTHONUNBUFFERED=1 + PYTHONDONTWRITEBYTECODE: 1 + PYTHONUNBUFFERED: 1 + DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_MODULE} + env_file: + .env restart: unless-stopped + + db: + image: postgres:18 + environment: + POSTGRES_DB: ${DB_NAME} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql + restart: unless-stopped + + adminer: + image: adminer + depends_on: + - db + restart: always + ports: + - 8080:8080 + + test: + build: . + command: python manage.py test + depends_on: + - db + profiles: + - test + +volumes: + postgres_data: diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index a59e57d..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - web: - volumes: - - ./app:/app - diff --git a/requirements.txt b/requirements.txt index 76a2a45..d340205 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ Django==6.0.3 gunicorn==25.1.0 +psycopg [binary] ==3.3.3 diff --git a/app/manage.py b/src/manage.py similarity index 100% rename from app/manage.py rename to src/manage.py diff --git a/app/website/__init__.py b/src/website/__init__.py similarity index 100% rename from app/website/__init__.py rename to src/website/__init__.py diff --git a/app/website/asgi.py b/src/website/asgi.py similarity index 100% rename from app/website/asgi.py rename to src/website/asgi.py diff --git a/src/website/settings.py b/src/website/settings.py new file mode 100644 index 0000000..f7bd626 --- /dev/null +++ b/src/website/settings.py @@ -0,0 +1,122 @@ +""" +Django settings for website project. + +Generated by 'django-admin startproject' using Django 6.0.3. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/6.0/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/6.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-c$q7wdq+u@ow74wp!&zzkxdylkueu)(+34e%!e0du&bjwoqz9z" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = os.getenv("DEBUG") + +ALLOWED_HOSTS = os.getenv("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 = "website.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "website.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/6.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": os.getenv("POSTGRES_NAME"), + "USER": os.getenv("POSTGRES_USER"), + "PASSWORD": os.getenv("POSTGRES_PASSWORD"), + "HOST": os.getenv("POSTGRES_HOST"), + "PORT": os.getenv("POSTGRES_PORT"), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/6.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/6.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/6.0/howto/static-files/ + +STATIC_URL = "static/" diff --git a/app/website/urls.py b/src/website/urls.py similarity index 100% rename from app/website/urls.py rename to src/website/urls.py diff --git a/app/website/wsgi.py b/src/website/wsgi.py similarity index 100% rename from app/website/wsgi.py rename to src/website/wsgi.py