GooDrive/backend/core/settings.py
Louis Mazin 71809b6a90 nickel
2026-10-15 13:56:12 +02:00

219 lines
8.6 KiB
Python

"""Configuration Django de GoodDrive (Django 5.2 / MySQL 8).
Les paramètres dépendent du fichier .env ou des variables d'environnement.
Les fichiers du Drive sont conservés sur disque et non dans la base MySQL.
"""
from datetime import timedelta
from pathlib import Path
from urllib.parse import quote
import dj_database_url
from decouple import config
# -----------------------------------------------------------------------------
# Chemins et configuration générale
# -----------------------------------------------------------------------------
BASE_DIR = Path(__file__).resolve().parent.parent
# Compatibilité avec l'actuel .env (DJANGO_SECRET_KEY), tout en acceptant
# SECRET_KEY comme dans la configuration d'origine de la boulangerie.
SECRET_KEY = config('SECRET_KEY', default=config('DJANGO_SECRET_KEY', default=''))
DEBUG = config('DEBUG', default=False, cast=bool)
if not SECRET_KEY or SECRET_KEY.startswith('REMPLACE_'):
if not DEBUG:
raise RuntimeError('Définis SECRET_KEY ou DJANGO_SECRET_KEY dans le .env.')
SECRET_KEY = 'dev-only-unsafe-secret-key-for-local-tests'
def csv_env(name, default=''):
"""Lit une liste séparée par des virgules en ignorant les valeurs vides."""
return [value.strip() for value in config(name, default=default).split(',') if value.strip()]
ALLOWED_HOSTS = csv_env('ALLOWED_HOSTS', 'localhost,127.0.0.1,backend')
# -----------------------------------------------------------------------------
# Applications et middleware
# -----------------------------------------------------------------------------
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Applications tierces
'rest_framework',
'rest_framework_simplejwt',
'corsheaders',
# Application GoodDrive
'drive',
]
# GoodDrive utilise le modèle utilisateur natif de Django.
# Ne pas ajouter AUTH_USER_MODEL = 'users.User' : l'app 'users' n'existe pas ici.
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'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 = 'core.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'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 = 'core.wsgi.application'
# -----------------------------------------------------------------------------
# Base de données : MySQL 8 en production, SQLite facultatif pour les tests
# -----------------------------------------------------------------------------
if config('USE_SQLITE', default=False, cast=bool):
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'test.sqlite3',
}
}
else:
# quote() évite de casser la connexion lorsque le mot de passe MySQL
# contient @, :, /, # ou d'autres caractères spéciaux d'URL.
user = quote(config('MYSQL_USER', default='gooddrive'), safe='')
password = quote(config('MYSQL_PASSWORD', default=''), safe='')
host = config('MYSQL_HOST', default='db')
port = config('MYSQL_PORT', default='3306')
database = quote(config('MYSQL_DATABASE', default='gooddrive'), safe='')
default_db_url = f'mysql://{user}:{password}@{host}:{port}/{database}'
DATABASES = {
'default': dj_database_url.config(
default=default_db_url,
conn_max_age=config('DB_CONN_MAX_AGE', default=60, cast=int),
),
}
if DATABASES['default']['ENGINE'] == 'django.db.backends.mysql':
DATABASES['default'].setdefault('OPTIONS', {}).setdefault('charset', 'utf8mb4')
# -----------------------------------------------------------------------------
# Authentification et validation des mots de passe
# -----------------------------------------------------------------------------
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'},
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'drive.authentication.PersistentJWTAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': config('DRF_ANON_THROTTLE_RATE', default='30/min'),
},
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=config('JWT_ACCESS_MINUTES', default=30, cast=int)),
'REFRESH_TOKEN_LIFETIME': timedelta(days=config('JWT_REFRESH_DAYS', default=7, cast=int)),
# Seulement pour convertir les anciens refresh JWT de 7 jours.
# Les nouvelles sessions persistantes vivent en base et sont révocables.
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': False,
'AUTH_HEADER_TYPES': ('Bearer',),
}
# -----------------------------------------------------------------------------
# Internationalisation
# -----------------------------------------------------------------------------
LANGUAGE_CODE = 'fr-fr'
TIME_ZONE = 'Europe/Paris'
USE_I18N = True
USE_TZ = True
# -----------------------------------------------------------------------------
# Fichiers statiques et stockage privé GoodDrive
# -----------------------------------------------------------------------------
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STORAGES = {
'default': {
'BACKEND': 'django.core.files.storage.FileSystemStorage',
},
'staticfiles': {
'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage',
},
}
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Les documents privés ne doivent pas être servis directement par STATIC_URL
# ou MEDIA_URL : les téléchargements sont contrôlés par les vues de GoodDrive.
DRIVE_STORAGE_ROOT = Path(config('DRIVE_STORAGE_ROOT', default='/storage')).resolve()
FILE_UPLOAD_TEMP_DIR = str(Path(config('FILE_UPLOAD_TEMP_DIR', default=str(DRIVE_STORAGE_ROOT / '.uploads'))).resolve())
DRIVE_STORAGE_ROOT.mkdir(parents=True, exist_ok=True)
Path(FILE_UPLOAD_TEMP_DIR).mkdir(parents=True, exist_ok=True)
# Taille maximale d'UN fichier (MiB) ; uploads directs et fractionnés.
DRIVE_MAX_FILE_SIZE = config('DRIVE_MAX_FILE_SIZE_MB', default=10240, cast=int) * 1024 * 1024
# Limite de mémoire des champs non fichiers ; fichiers streamés vers disque.
DATA_UPLOAD_MAX_MEMORY_SIZE = config('DATA_UPLOAD_MAX_MEMORY_SIZE_MB', default=5, cast=int) * 1024 * 1024
FILE_UPLOAD_MAX_MEMORY_SIZE = 0
# -----------------------------------------------------------------------------
# CORS et sécurité derrière un proxy
# -----------------------------------------------------------------------------
# Le frontend appelle normalement /api sur le même domaine via Vite/nginx :
# aucune origine CORS supplémentaire n'est nécessaire par défaut.
CORS_ALLOWED_ORIGINS = csv_env('CORS_ALLOWED_ORIGINS')
CORS_ALLOW_ALL_ORIGINS = config('CORS_ALLOW_ALL_ORIGINS', default=False, cast=bool)
CSRF_TRUSTED_ORIGINS = csv_env('CSRF_TRUSTED_ORIGINS')
SECURE_CONTENT_TYPE_NOSNIFF = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SAMESITE = 'Lax'
# À activer uniquement derrière un reverse proxy de confiance qui définit
# X-Forwarded-Proto ; ne pas faire confiance à cet en-tête en accès direct.
if config('TRUST_PROXY_SSL_HEADER', default=False, cast=bool):
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = config('SESSION_COOKIE_SECURE', default=False, cast=bool)
CSRF_COOKIE_SECURE = config('CSRF_COOKIE_SECURE', default=False, cast=bool)
SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=False, cast=bool)