220 lines
8.6 KiB
Python
220 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',
|
|
'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',
|
|
]
|
|
|
|
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': (
|
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
|
),
|
|
'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)),
|
|
# Le frontend actuel ne stocke pas de nouveau refresh token après renouvellement.
|
|
# Activer la rotation demanderait de modifier aussi frontend/src/api/client.js.
|
|
'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='/data/files')).resolve()
|
|
FILE_UPLOAD_TEMP_DIR = str(DRIVE_STORAGE_ROOT.parent / 'tmp')
|
|
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)
|