requirements.txt stand durchgehend auf ">=", die gebaute Version hing also am Kalendertag statt am Repo. Genau daran ist der letzte Deploy gescheitert. Jetzt exakt die Versionen, gegen die die Suite grün ist und die produktiv laufen — Updates werden damit zu einer bewussten Änderung mit CI-Lauf. Django bleibt auf 6.1: dorthin ist die Instanz ohnehin schon gedriftet, es läuft, und 4.2 LTS ist seit April 2026 aus dem Support. Dabei ist aufgefallen, dass Django 5.1 STATICFILES_STORAGE entfernt hat. Die Einstellung stand noch da und wurde stillschweigend ignoriert, womit whitenoise auf StaticFilesStorage zurückfiel und Assets unkomprimiert auslieferte — app.js mit 251 KB statt 62 KB bei jedem kalten Laden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
152 lines
5.1 KiB
Python
152 lines
5.1 KiB
Python
import mimetypes
|
|
import os
|
|
from pathlib import Path
|
|
from dotenv import load_dotenv
|
|
|
|
mimetypes.add_type('application/javascript', '.js')
|
|
mimetypes.add_type('text/css', '.css')
|
|
|
|
# Load .env file from the project root
|
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
load_dotenv(BASE_DIR / '.env')
|
|
|
|
SECRET_KEY = os.environ.get('SECRET_KEY', 'insecure-default-key-change-in-production')
|
|
|
|
DEBUG = os.environ.get('DEBUG', 'True') == 'True'
|
|
|
|
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost 127.0.0.1').split()
|
|
|
|
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', '').split()
|
|
|
|
INSTALLED_APPS = [
|
|
'django.contrib.admin',
|
|
'django.contrib.auth',
|
|
'django.contrib.contenttypes',
|
|
'django.contrib.sessions',
|
|
'django.contrib.messages',
|
|
'django.contrib.staticfiles',
|
|
'radio',
|
|
'accounts',
|
|
'podcasts',
|
|
'books',
|
|
'gpodder',
|
|
]
|
|
|
|
EBOOK_MAX_BYTES = 50 * 1024 * 1024 # 50 MB
|
|
|
|
# Encrypted uploads are base64-encoded (~33% overhead) so allow ~75 MB body
|
|
DATA_UPLOAD_MAX_MEMORY_SIZE = 75 * 1024 * 1024
|
|
|
|
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',
|
|
'accounts.middleware.ApiTokenAuthMiddleware',
|
|
'django.contrib.messages.middleware.MessageMiddleware',
|
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
]
|
|
|
|
ROOT_URLCONF = 'diora.urls'
|
|
|
|
TEMPLATES = [
|
|
{
|
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
'DIRS': [BASE_DIR / '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',
|
|
'diora.context_processors.build_info',
|
|
'diora.context_processors.upload_limits',
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
WSGI_APPLICATION = 'diora.wsgi.application'
|
|
|
|
DATABASES = {
|
|
'default': {
|
|
'ENGINE': 'django.db.backends.sqlite3',
|
|
'NAME': BASE_DIR / 'data' / os.environ.get('DIORA_DB_NAME', 'db.sqlite3'),
|
|
'OPTIONS': {'timeout': 20},
|
|
}
|
|
}
|
|
|
|
PODCAST_MAX_EPISODES_PER_FEED = int(os.environ.get('PODCAST_MAX_EPISODES_PER_FEED', '200'))
|
|
|
|
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'},
|
|
]
|
|
|
|
LANGUAGE_CODE = 'en-us'
|
|
TIME_ZONE = 'UTC'
|
|
USE_I18N = True
|
|
USE_TZ = True
|
|
|
|
STATIC_URL = '/static/'
|
|
STATICFILES_DIRS = [BASE_DIR / 'static']
|
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
|
|
|
# Django 5.1 removed STATICFILES_STORAGE in favour of STORAGES. The old setting
|
|
# sat here being silently ignored, so whitenoise fell back to plain
|
|
# StaticFilesStorage and served everything uncompressed — app.js went out at
|
|
# 251 KB instead of 62 KB on every cold load.
|
|
#
|
|
# The hashed filenames this also generates are inert: no template uses
|
|
# {% static %}, they all hardcode /static/… paths. Cache busting comes from the
|
|
# service worker's CACHE version instead (static/js/sw.js), which is why it is
|
|
# bumped on each release.
|
|
STORAGES = {
|
|
'default': {
|
|
'BACKEND': 'django.core.files.storage.FileSystemStorage',
|
|
},
|
|
'staticfiles': {
|
|
'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage',
|
|
},
|
|
}
|
|
|
|
MEDIA_URL = '/media/'
|
|
MEDIA_ROOT = BASE_DIR / 'media'
|
|
|
|
BG_MAX_BYTES = 5 * 1024 * 1024 # 5 MB
|
|
HIGHLIGHTS_MAX_BYTES = 700 * 1024 # 700 KB
|
|
BOOKMARKS_MAX_BYTES = 100 * 1024 # 100 KB
|
|
|
|
VOLUME_DEFAULT = 204 # out of 255
|
|
ITUNES_TIMEOUT = 6 # seconds
|
|
BOOK_METADATA_TIMEOUT = 6 # seconds (DNB / Open Library shelf lookup)
|
|
WEBDAV_TIMEOUT = 15 # seconds (cloud import browse/fetch)
|
|
WEBDAV_MAX_SOURCES_PER_USER = 10
|
|
PODCAST_INBOX_PAGE_SIZE = 200
|
|
|
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
|
|
LOGIN_URL = '/accounts/login/'
|
|
LOGIN_REDIRECT_URL = '/'
|
|
LOGOUT_REDIRECT_URL = '/'
|
|
|
|
# Last.fm
|
|
LASTFM_API_KEY = os.environ.get('LASTFM_API_KEY', '')
|
|
LASTFM_API_SECRET = os.environ.get('LASTFM_API_SECRET', '')
|
|
|
|
# Amazon affiliate
|
|
AMAZON_AFFILIATE_TAG = os.environ.get('AMAZON_AFFILIATE_TAG', 'diora-20')
|
|
AMAZON_AFFILIATE_ENABLED = os.environ.get('AMAZON_AFFILIATE_ENABLED', 'True') == 'True'
|
|
|
|
# WebDAV cloud import (ebooks). Users supply the target URL, so by default the
|
|
# server refuses to talk to non-public addresses — otherwise any registered
|
|
# account could probe the internal network through it. Set to True only when
|
|
# every account on this instance is trusted and the WebDAV server is on the LAN.
|
|
WEBDAV_ALLOW_PRIVATE_HOSTS = os.environ.get('WEBDAV_ALLOW_PRIVATE_HOSTS', 'False') == 'True'
|
|
|
|
BUILD_TIME = os.environ.get('BUILD_TIME', '')
|