From 15978661f83e06c50d4b20a27eaa334e73000b75 Mon Sep 17 00:00:00 2001 From: Simonas Kareiva Date: Tue, 12 May 2026 17:25:44 +0300 Subject: [PATCH] Scaffold main apps --- apps/__init__.py | 0 apps/accounts/apps.py | 2 +- apps/dashboard/apps.py | 2 +- apps/submissions/apps.py | 2 +- hamprint/asgi.py | 2 +- hamprint/settings.py | 117 --------------------------- hamprint/settings/__init__.py | 0 hamprint/settings/base.py | 146 ++++++++++++++++++++++++++++++++++ hamprint/settings/dev.py | 9 +++ hamprint/settings/prod.py | 13 +++ hamprint/wsgi.py | 2 +- 11 files changed, 173 insertions(+), 122 deletions(-) create mode 100644 apps/__init__.py delete mode 100644 hamprint/settings.py create mode 100644 hamprint/settings/__init__.py create mode 100644 hamprint/settings/base.py create mode 100644 hamprint/settings/dev.py create mode 100644 hamprint/settings/prod.py diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/apps.py b/apps/accounts/apps.py index fb0257e..e3d765b 100644 --- a/apps/accounts/apps.py +++ b/apps/accounts/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class AccountsConfig(AppConfig): - name = "accounts" + name = "apps.accounts" diff --git a/apps/dashboard/apps.py b/apps/dashboard/apps.py index 45b7fa2..7159695 100644 --- a/apps/dashboard/apps.py +++ b/apps/dashboard/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class DashboardConfig(AppConfig): - name = "dashboard" + name = "apps.dashboard" diff --git a/apps/submissions/apps.py b/apps/submissions/apps.py index f5e7f04..7b99cd6 100644 --- a/apps/submissions/apps.py +++ b/apps/submissions/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class SubmissionsConfig(AppConfig): - name = "submissions" + name = "apps.submissions" diff --git a/hamprint/asgi.py b/hamprint/asgi.py index 7cce10e..4d0f240 100644 --- a/hamprint/asgi.py +++ b/hamprint/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hamprint.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hamprint.settings.prod") application = get_asgi_application() diff --git a/hamprint/settings.py b/hamprint/settings.py deleted file mode 100644 index 58cc046..0000000 --- a/hamprint/settings.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Django settings for hamprint project. - -Generated by 'django-admin startproject' using Django 6.0.5. - -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-u0^726zo0+d#dbuhxc+^3^miv8#o^mlydn6j97%^%=2u!%nayn" - -# 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 = "hamprint.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 = "hamprint.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/hamprint/settings/__init__.py b/hamprint/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hamprint/settings/base.py b/hamprint/settings/base.py new file mode 100644 index 0000000..3e0425d --- /dev/null +++ b/hamprint/settings/base.py @@ -0,0 +1,146 @@ +"""Base settings shared by dev and prod. Per-environment overrides live in +dev.py / prod.py. Configuration is driven by environment variables loaded from +`.env` at the repo root via django-environ.""" + +from pathlib import Path + +import dj_database_url +import environ + +BASE_DIR = Path(__file__).resolve().parent.parent.parent + +env = environ.Env( + DEBUG=(bool, False), + ALLOWED_HOSTS=(list, []), +) +environ.Env.read_env(BASE_DIR / ".env") + +SECRET_KEY = env( + "SECRET_KEY", + default="django-insecure-u0^726zo0+d#dbuhxc+^3^miv8#o^mlydn6j97%^%=2u!%nayn", +) +DEBUG = env("DEBUG") +ALLOWED_HOSTS = env("ALLOWED_HOSTS") + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", + # Third-party + "allauth", + "allauth.account", + "allauth.socialaccount", + "allauth.socialaccount.providers.google", + "anymail", + "crispy_forms", + "crispy_tailwind", + # Local + "apps.submissions", + "apps.dashboard", + "apps.accounts", +] + +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", + "allauth.account.middleware.AccountMiddleware", +] + +SITE_ID = 1 + +ROOT_URLCONF = "hamprint.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "templates"], + "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 = "hamprint.wsgi.application" + +DATABASES = { + "default": dj_database_url.parse( + env("DATABASE_URL", default=f"sqlite:///{BASE_DIR / 'db.sqlite3'}"), + conn_max_age=600, + ), +} + +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"}, +] + +AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + "allauth.account.auth_backends.AuthenticationBackend", +] + +LANGUAGE_CODE = "en-us" +TIME_ZONE = "UTC" +USE_I18N = True +USE_TZ = True + +STATIC_URL = "static/" +STATIC_ROOT = BASE_DIR / "staticfiles" +STATICFILES_DIRS = [BASE_DIR / "static"] if (BASE_DIR / "static").exists() else [] + +MEDIA_URL = "media/" +MEDIA_ROOT = BASE_DIR / "media" + +STORAGES = { + "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# Email --- Mailjet via django-anymail. The env var name MAILJET_API_SECRET is +# mapped onto anymail's MAILJET_SECRET_KEY so our two env vars stay symmetrical. +EMAIL_BACKEND = "anymail.backends.mailjet.EmailBackend" +ANYMAIL = { + "MAILJET_API_KEY": env("MAILJET_API_KEY", default=""), + "MAILJET_SECRET_KEY": env("MAILJET_API_SECRET", default=""), +} +DEFAULT_FROM_EMAIL = env( + "DEFAULT_FROM_EMAIL", default="hamprint " +) + +CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind" +CRISPY_TEMPLATE_PACK = "tailwind" + +# Allauth. We run our own confirmation flow for guests, so allauth's own email +# verification stays off for Google-authenticated users (they're already verified). +ACCOUNT_EMAIL_VERIFICATION = "none" +ACCOUNT_LOGIN_METHODS = {"email"} +ACCOUNT_SIGNUP_FIELDS = ["email*"] +LOGIN_REDIRECT_URL = "/my-prints/" +ACCOUNT_LOGOUT_REDIRECT_URL = "/" +SOCIALACCOUNT_PROVIDERS = { + "google": { + "SCOPE": ["profile", "email"], + "AUTH_PARAMS": {"access_type": "online"}, + } +} diff --git a/hamprint/settings/dev.py b/hamprint/settings/dev.py new file mode 100644 index 0000000..d41d87f --- /dev/null +++ b/hamprint/settings/dev.py @@ -0,0 +1,9 @@ +"""Development settings.""" + +from .base import * # noqa: F401, F403 + +DEBUG = True +ALLOWED_HOSTS = ["localhost", "127.0.0.1", "0.0.0.0"] + +# Print emails to the console in dev so we don't burn Mailjet quota. +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" diff --git a/hamprint/settings/prod.py b/hamprint/settings/prod.py new file mode 100644 index 0000000..bbe8696 --- /dev/null +++ b/hamprint/settings/prod.py @@ -0,0 +1,13 @@ +"""Production settings.""" + +from .base import * # noqa: F401, F403 + +DEBUG = False + +# Trust the upstream reverse proxy (hamlab.lt's TLS terminator) for X-Forwarded-Proto. +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True +SECURE_HSTS_SECONDS = 60 * 60 * 24 * 30 # 30 days +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_HSTS_PRELOAD = False diff --git a/hamprint/wsgi.py b/hamprint/wsgi.py index 45a8003..1292348 100644 --- a/hamprint/wsgi.py +++ b/hamprint/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hamprint.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hamprint.settings.prod") application = get_wsgi_application()