Add initial Django project

This commit is contained in:
Joakim Hellsén 2026-04-23 06:04:47 +02:00
commit fa6af127c1
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
15 changed files with 1103 additions and 4 deletions

0
config/__init__.py Normal file
View file

189
config/settings.py Normal file
View file

@ -0,0 +1,189 @@
import logging
import os
import sys
from pathlib import Path
from typing import Any
from dotenv import load_dotenv
from config.settings_utils import get_data_dir
logger: logging.Logger = logging.getLogger("tussilago.settings")
load_dotenv()
DEBUG: bool = os.getenv("DJANGO_DEBUG", "False").lower() == "true"
TESTING: bool = "test" in sys.argv or "PYTEST_VERSION" in os.environ
BASE_DIR: Path = Path(__file__).resolve().parent.parent
DATA_DIR: Path = get_data_dir()
DATA_DIR.mkdir(exist_ok=True)
DB_DIR: Path = DATA_DIR / "db"
DB_DIR.mkdir(exist_ok=True)
SECRET_KEY: str = os.getenv("DJANGO_SECRET_KEY", default="")
if not SECRET_KEY:
logger.error("DJANGO_SECRET_KEY environment variable is not set.")
sys.exit(1) # Exit with a non-zero status code to indicate an error
TIME_ZONE: str = "UTC"
USE_I18N: bool = False
USE_THOUSAND_SEPARATOR: bool = True
DATE_FORMAT = "j F Y" # 31 January 2006
TIME_FORMAT = "H:i" # 15:04
DATETIME_FORMAT = "j F Y H:i" # 31 January 2006 15:04
YEAR_MONTH_FORMAT = "F Y" # January 2006
MONTH_DAY_FORMAT = "j F" # 31 January
SHORT_DATE_FORMAT = "Y-m-d" # 2006-10-25
SHORT_DATETIME_FORMAT = "Y-m-d H:i" # 2006-10-25 15:04
FIRST_DAY_OF_WEEK = 1 # Monday
DATE_INPUT_FORMATS: list[str] = [
"%Y-%m-%d", # 2006-10-25
"%m/%d/%Y", # 10/25/2006
"%m/%d/%y", # 10/25/06
]
DECIMAL_SEPARATOR = ","
THOUSAND_SEPARATOR = "\xa0" # non-breaking space
NUMBER_GROUPING = 3
URLIZE_ASSUME_HTTPS: bool = True
USE_TZ: bool = True
ROOT_URLCONF: str = "config.urls"
ALLOWED_HOSTS: list[str] = []
WSGI_APPLICATION: str = "config.wsgi.application"
ADMINS: list[tuple[str, str]] = [("Joakim Hellsén", "tlovinator@gmail.com")]
STATIC_ROOT: Path = DATA_DIR / "staticfiles"
STATIC_ROOT.mkdir(exist_ok=True)
STATIC_URL = "static/"
STATICFILES_DIRS: list[Path] = [BASE_DIR / "static"]
MEDIA_ROOT: Path = DATA_DIR / "media"
MEDIA_ROOT.mkdir(exist_ok=True)
MEDIA_URL = "/media/"
if DEBUG:
INTERNAL_IPS: list[str] = ["127.0.0.1", "localhost"]
INSTALLED_APPS: list[str] = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
MIDDLEWARE: list[str] = [
"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",
]
TEMPLATES: list[dict[str, str | bool | list[str] | dict[str, list[str]]]] = [
{
"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",
],
},
},
]
DATABASES: dict[str, dict[str, str | Path | dict[str, str | int]]] = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": DB_DIR / "tussilago.sqlite3",
"OPTIONS": {
"transaction_mode": "IMMEDIATE",
"timeout": 5, # seconds
"init_command": """
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
PRAGMA mmap_size=134217728;
PRAGMA journal_size_limit=27103364;
PRAGMA cache_size=2000;
""",
},
},
}
AUTH_PASSWORD_VALIDATORS: list[dict[str, str]] = [
{
"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",
},
]
# Logging configuration
LOGGING: dict[str, Any] = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
},
},
"loggers": {
"": {
"handlers": ["console"],
"level": "INFO",
"propagate": True,
},
"tussilago": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": False,
},
"django": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
"django.utils.autoreload": {
"handlers": ["console"],
"level": "INFO",
"propagate": True,
},
},
}
if DEBUG or TESTING:
MIDDLEWARE.extend((
"zeal.middleware.zeal_middleware",
"debug_toolbar.middleware.DebugToolbarMiddleware",
"django_browser_reload.middleware.BrowserReloadMiddleware",
))
INSTALLED_APPS.extend((
"zeal",
"debug_toolbar",
"django_watchfiles",
"django_browser_reload",
))
# Customize the config to support htmx boosting.
DEBUG_TOOLBAR_CONFIG: dict[str, str] = {"ROOT_TAG_EXTRA_ATTRS": "hx-preserve"}

23
config/settings_utils.py Normal file
View file

@ -0,0 +1,23 @@
from typing import TYPE_CHECKING
from platformdirs import user_data_path
if TYPE_CHECKING:
from pathlib import Path
def get_data_dir() -> Path:
"""Get the directory where the application data will be stored.
This directory is created if it does not exist.
Returns:
Path: The directory where the application data will be stored.
"""
return user_data_path(
appauthor="TheLovinator",
appname="Tussilago",
ensure_exists=True,
roaming=True,
use_site_for_root=True,
)

32
config/urls.py Normal file
View file

@ -0,0 +1,32 @@
from typing import TYPE_CHECKING
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include
from django.urls import path
if TYPE_CHECKING:
from django.urls import URLPattern
from django.urls.resolvers import URLResolver
urlpatterns: list[URLPattern | URLResolver] = [
path(route="admin/", view=admin.site.urls),
]
if settings.DEBUG:
# Serve static files during development
urlpatterns += static(
prefix=settings.STATIC_URL,
document_root=settings.STATIC_ROOT,
)
urlpatterns += [
path(
route="__debug__/",
view=include(arg="debug_toolbar.urls"),
),
path(
route="__reload__/",
view=include(arg="django_browser_reload.urls"),
),
]

14
config/wsgi.py Normal file
View file

@ -0,0 +1,14 @@
import os
from typing import TYPE_CHECKING
from django.core.wsgi import get_wsgi_application
if TYPE_CHECKING:
from django.core.handlers.wsgi import WSGIHandler
os.environ.setdefault(
key="DJANGO_SETTINGS_MODULE",
value="config.settings",
)
application: WSGIHandler = get_wsgi_application()