404 lines
No EOL
13 KiB
Python
404 lines
No EOL
13 KiB
Python
"""
|
|
Django settings for streetup project.
|
|
|
|
Generated by 'django-admin startproject' using Django 5.1.4.
|
|
|
|
For more information on this file, see
|
|
https://docs.djangoproject.com/en/5.1/topics/settings/
|
|
|
|
For the full list of settings and their values, see
|
|
https://docs.djangoproject.com/en/5.1/ref/settings/
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from os import getenv
|
|
from dotenv import load_dotenv
|
|
import os
|
|
import platform
|
|
# Bypass WMI queries on Windows which can hang indefinitely due to OS/WMI service issues
|
|
if os.name == 'nt':
|
|
platform.win32_ver = lambda: ('10', '10.0.19045', '', 'Multiprocessor Free')
|
|
|
|
from datetime import timedelta
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
|
|
load_dotenv(BASE_DIR / ".env", override=False)
|
|
|
|
|
|
# Quick-start development settings - unsuitable for production
|
|
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
|
|
|
# SECURITY WARNING: keep the secret key used in production secret!
|
|
SECRET_KEY = getenv("SECRET_KEY")
|
|
|
|
# SECURITY WARNING: don't run with debug turned on in production!
|
|
DEBUG = getenv("DEBUG") == "True"
|
|
|
|
ROOT_URL = getenv("ROOT_URL", "http://localhost:8000")
|
|
|
|
ALLOWED_HOSTS = getenv("ALLOWED_HOSTS").split(',')
|
|
CSRF_TRUSTED_ORIGINS = getenv("CSRF_TRUSTED_ORIGINS").split(',')
|
|
|
|
# Application definition
|
|
|
|
INSTALLED_APPS = [
|
|
'common.apps.CommonConfig',
|
|
'home.apps.HomeConfig',
|
|
'assets.apps.AssetsConfig',
|
|
'interventions.apps.InterventionsConfig',
|
|
'stock.apps.StockConfig',
|
|
'contracts.apps.ContractsConfig',
|
|
'controls.apps.ControlsConfig',
|
|
'projects.apps.ProjectsConfig',
|
|
'observations.apps.ObservationsConfig',
|
|
'documents.apps.DocumentsConfig',
|
|
'mobile.apps.MobileConfig',
|
|
'api.apps.ApiConfig',
|
|
'sign.apps.SignConfig',
|
|
'maps.apps.MapsConfig',
|
|
'analytics.apps.AnalyticsConfig',
|
|
'notifications.apps.NotificationsConfig',
|
|
'road_detection.apps.RoadDetectionConfig',
|
|
'authorizations.apps.AuthorizationsConfig',
|
|
'reporting.apps.ReportingConfig',
|
|
'panoramax.apps.PanoramaxConfig',
|
|
'django.contrib.admin',
|
|
'django.contrib.auth',
|
|
'django.contrib.contenttypes',
|
|
'django.contrib.sessions',
|
|
'django.contrib.messages',
|
|
'django.contrib.staticfiles',
|
|
"django.contrib.gis",
|
|
"django.contrib.postgres",
|
|
"crispy_forms",
|
|
"crispy_bootstrap5",
|
|
"rest_framework",
|
|
]
|
|
|
|
MIDDLEWARE = [
|
|
'django.middleware.gzip.GZipMiddleware',
|
|
'django.middleware.security.SecurityMiddleware',
|
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
'django.middleware.locale.LocaleMiddleware',
|
|
'django.middleware.common.CommonMiddleware',
|
|
'django.middleware.csrf.CsrfViewMiddleware',
|
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
'django.contrib.auth.middleware.LoginRequiredMiddleware',
|
|
'django.contrib.messages.middleware.MessageMiddleware',
|
|
'common.middleware.AutoLogoutMiddleware', # Déconnexion automatique après X heures
|
|
'common.middleware.RequestLoggingMiddleware', # Capture des erreurs dans les logs
|
|
'analytics.middleware.AnalyticsMiddleware', # Monitoring de l'utilisation de l'application
|
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
]
|
|
|
|
ROOT_URLCONF = 'streetup.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',
|
|
'common.context_processors.linked_accounts',
|
|
'common.context_processors.current_app_view',
|
|
'common.context_processors.environment_info',
|
|
'common.context_processors.organization_info',
|
|
'notifications.context_processors.unread_notifications_count',
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
WSGI_APPLICATION = 'streetup.wsgi.application'
|
|
|
|
|
|
# Database
|
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
|
|
|
DATABASES = {
|
|
"default": {
|
|
"ENGINE": "django.contrib.gis.db.backends.postgis",
|
|
'OPTIONS': {
|
|
'options': f'-c search_path={getenv("DB_SCHEMA","public")},public'
|
|
},
|
|
'NAME': getenv("DB_NAME"),
|
|
'USER': getenv("DB_USER"),
|
|
'PASSWORD': getenv("DB_PASSWORD"),
|
|
'HOST': getenv("DB_HOST"),
|
|
'PORT': int(getenv("DB_PORT", 5432)),
|
|
},
|
|
}
|
|
|
|
|
|
# Password validation
|
|
# https://docs.djangoproject.com/en/5.1/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/5.1/topics/i18n/
|
|
|
|
LANGUAGES = [
|
|
('nl', 'Nederlands'),
|
|
('en', 'English'),
|
|
('fr', 'Français'),
|
|
]
|
|
|
|
LANGUAGE_CODE = 'en-us'
|
|
|
|
USE_TZ = True
|
|
# TIME_ZONE = 'UTC'
|
|
TIME_ZONE = 'Europe/Brussels'
|
|
|
|
USE_I18N = True
|
|
|
|
|
|
|
|
LOCALE_PATHS = [
|
|
BASE_DIR / "locale",
|
|
]
|
|
|
|
# Static files (CSS, JavaScript, Images)
|
|
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
|
|
|
STATIC_URL = 'static/'
|
|
STATIC_ROOT = BASE_DIR / "static"
|
|
STATICFILES_DIRS = []
|
|
|
|
|
|
MEDIA_URL = "/media/"
|
|
MEDIA_ROOT = BASE_DIR / "media"
|
|
PRIVATE_MEDIA_ROOT = BASE_DIR / "private_media"
|
|
PRIVATE_MEDIA_URL = "/private-media/"
|
|
|
|
# Durée de validité des tokens signés temporaires pour l'accès aux fichiers privés (ex. documents)
|
|
PRIVATE_FILES_TOKEN_MAX_AGE = 172800 # 48 heures
|
|
|
|
|
|
|
|
|
|
STORAGES = {
|
|
"default": {
|
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
|
},
|
|
"staticfiles": {
|
|
"BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
|
|
},
|
|
}
|
|
|
|
|
|
|
|
# Default primary key field type
|
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
|
|
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
|
|
|
|
GDAL_LIBRARY_PATH = getenv("GDAL_LIBRARY_PATH")
|
|
GEOS_LIBRARY_PATH = getenv("GEOS_LIBRARY_PATH")
|
|
|
|
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
|
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
|
|
|
|
|
|
|
|
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
EMAIL_HOST = getenv("EMAIL_HOST")
|
|
EMAIL_USE_TLS = False
|
|
EMAIL_USE_SSL = True
|
|
EMAIL_PORT = getenv("EMAIL_PORT")
|
|
EMAIL_HOST_USER = getenv("EMAIL_HOST_USER")
|
|
EMAIL_HOST_PASSWORD = getenv("EMAIL_HOST_PASSWORD")
|
|
DEFAULT_FROM_EMAIL = 'no-reply@streetup.be'
|
|
|
|
# Override email recipients for testing (set to None in production)
|
|
# If set, all emails will be sent to this address instead of real recipients
|
|
EMAIL_OVERRIDE = getenv("EMAIL_OVERRIDE", "kdeterme@sprb.brussels")
|
|
|
|
# Organization Configuration
|
|
PRODUCT_NAME = getenv("PRODUCT_NAME", "StreetUp")
|
|
ORGANIZATION_NAME = getenv("ORGANIZATION_NAME", _("Brussels Mobility"))
|
|
ORGANIZATION_NAME_FR = getenv("ORGANIZATION_NAME_FR", "Bruxelles Mobilité")
|
|
ORGANIZATION_NAME_NL = getenv("ORGANIZATION_NAME_NL", "Brussel Mobiliteit")
|
|
|
|
ORGANIZATION_LOGO_HEADER = getenv("ORGANIZATION_LOGO_HEADER", "common/android-192.png")
|
|
ORGANIZATION_LOGO_PNG = getenv("ORGANIZATION_LOGO_PNG", "common/SPRB-GOB-FR-NL-LOGO-RGB.png")
|
|
ORGANIZATION_LOGO_SVG = getenv("ORGANIZATION_LOGO_SVG", "common/sprb.svg")
|
|
|
|
def _clean_hex(hex_str):
|
|
try:
|
|
hex_str = hex_str.strip().strip('"').strip("'")
|
|
h = hex_str.lstrip('#')
|
|
if len(h) == 8:
|
|
h = h[:6]
|
|
return f"#{h}"
|
|
except Exception:
|
|
return "#2f3e8b"
|
|
|
|
ORGANIZATION_PRIMARY_COLOR_HEX = _clean_hex(getenv("ORGANIZATION_PRIMARY_COLOR_HEX", "#2f3e8b"))
|
|
|
|
def _hex_to_rgb(hex_str):
|
|
try:
|
|
h = hex_str.lstrip('#')
|
|
return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
|
|
except Exception:
|
|
return (47, 62, 139)
|
|
|
|
ORGANIZATION_PRIMARY_COLOR_RGB = _hex_to_rgb(ORGANIZATION_PRIMARY_COLOR_HEX)
|
|
|
|
|
|
|
|
LOGGING = {
|
|
"version": 1,
|
|
"disable_existing_loggers": False,
|
|
"handlers": {
|
|
"streetup_file": {
|
|
"level": "WARNING",
|
|
"class": "logging.FileHandler",
|
|
"filename": getenv("LOG_FILE_PATH"),
|
|
},
|
|
"streetup_file_info": {
|
|
"level": "INFO",
|
|
"class": "logging.FileHandler",
|
|
"filename": getenv("LOG_FILE_PATH"),
|
|
},
|
|
},
|
|
"root": {
|
|
"handlers": ["streetup_file"],
|
|
"level": "WARNING",
|
|
},
|
|
"loggers": {
|
|
"django": {
|
|
"handlers": ["streetup_file"],
|
|
"level": "WARNING",
|
|
"propagate": False,
|
|
},
|
|
"home.perf": {
|
|
"handlers": ["streetup_file_info"],
|
|
"level": "INFO",
|
|
"propagate": False,
|
|
},
|
|
},
|
|
}
|
|
|
|
REST_FRAMEWORK_ENABLED = True
|
|
REST_FRAMEWORK = {
|
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
|
#"rest_framework.authentication.SessionAuthentication",
|
|
"api.auth.HybridTokenAuthentication",
|
|
],
|
|
"DEFAULT_PERMISSION_CLASSES": [
|
|
"rest_framework.permissions.IsAuthenticated",
|
|
],
|
|
}
|
|
|
|
|
|
# SIMPLE_JWT = {
|
|
# "ALGORITHM": "RS256",
|
|
# "SIGNING_KEY": open("/etc/keys/jwt-private.pem").read(),
|
|
# "VERIFYING_KEY": open("/etc/keys/jwt-public.pem").read(),
|
|
# "ACCESS_TOKEN_LIFETIME": timedelta(minutes=20),
|
|
# "AUTH_HEADER_TYPES": ("Bearer",),
|
|
# "AUDIENCE": "pygeoapi",
|
|
# }
|
|
|
|
# Schools app settings
|
|
SCHOOLS_GEOCODING_CHUNK_SIZE = 3 # Concurrent geocoding requests per block
|
|
SCHOOLS_GEOCODING_API_LIMIT = 1 # Number of results to request from Photon API per address
|
|
SCHOOLS_GEOCODING_INTERNAL_THRESHOLD = 0.5 # Minimum fuzzy match score (0-1) to accept internal DB result
|
|
SCHOOLS_GEOCODING_USE_INTERNAL = True # Whether to try internal DB before Photon API
|
|
|
|
# Road Detection (YOLO11)
|
|
# Pour utiliser un modèle fine-tuné sur les nids de poule, définir le chemin ici.
|
|
# Exemple: ROAD_DETECTION_MODEL_PATH = BASE_DIR / "media" / "road_detection" / "models" / "pothole_yolo11n.pt"
|
|
ROAD_DETECTION_MODEL_PATH = getenv("ROAD_DETECTION_MODEL_PATH", "yolo11n.pt")
|
|
|
|
# Cartography configuration
|
|
MAP_STYLE_LIGHT = getenv("MAP_STYLE_LIGHT")
|
|
MAP_STYLE_STANDARD = getenv("MAP_STYLE_STANDARD")
|
|
MAP_RASTER_TILE_URL = getenv("MAP_RASTER_TILE_URL")
|
|
MAP_ATTRIBUTION = getenv("MAP_ATTRIBUTION")
|
|
|
|
MAP_DEFAULT_CENTER_LON = float(getenv("MAP_DEFAULT_CENTER_LON", 4.3572))
|
|
MAP_DEFAULT_CENTER_LAT = float(getenv("MAP_DEFAULT_CENTER_LAT", 50.8477))
|
|
MAP_DEFAULT_ZOOM = int(getenv("MAP_DEFAULT_ZOOM", 11))
|
|
|
|
def _parse_extent(extent_str):
|
|
if not extent_str:
|
|
return None
|
|
try:
|
|
parts = [float(x.strip()) for x in extent_str.split(',')]
|
|
if len(parts) == 4:
|
|
return [[parts[0], parts[1]], [parts[2], parts[3]]]
|
|
return None
|
|
except Exception:
|
|
return None
|
|
|
|
MAP_DEFAULT_EXTENT = _parse_extent(getenv("MAP_DEFAULT_EXTENT"))
|
|
|
|
# Support & Follow-up Contacts
|
|
EMAIL_TECH_SUPPORT = getenv("EMAIL_TECH_SUPPORT", "bm.perm.tech@sprb.brussels")
|
|
EMAIL_TECH_SUPPORT_PHONE = getenv("EMAIL_TECH_SUPPORT_PHONE", "02/204.22.86")
|
|
EMAIL_INTERNAL_FOLLOWUP = getenv("EMAIL_INTERNAL_FOLLOWUP", "kdeterme@sprb.brussels")
|
|
EMAIL_AUTHORIZATIONS_BCC = getenv("EMAIL_AUTHORIZATIONS_BCC", "autorisation.voiries@sprb.brussels")
|
|
|
|
# Orthophoto WMS Layer Configuration
|
|
MAP_ORTHO_WMS_URL = getenv("MAP_ORTHO_WMS_URL", "https://geoservices-urbis.irisnet.be/geoserver/urbisgrid/wms?service=WMS&version=1.3.0&request=GetMap&layers=Ortho&styles=&format=image/png&transparent=true&width=256&height=256&crs=EPSG:3857&bbox={bbox-epsg-3857}")
|
|
MAP_ORTHO_WMS_NAME = getenv("MAP_ORTHO_WMS_NAME", "Urbis (Bruxelles)")
|
|
MAP_ORTHO_WMS_ATTRIBUTION = getenv("MAP_ORTHO_WMS_ATTRIBUTION", "© Brussels UrbIS")
|
|
|
|
# Panoramax API configuration
|
|
PANORAMAX_API_URL = getenv("PANORAMAX_API_URL", "https://panoramax.streetup.be/api")
|
|
PANORAMAX_API_KEY = getenv("PANORAMAX_API_KEY", "")
|
|
PANORAMAX_IMPORT_JPEG_QUALITY = int(getenv("PANORAMAX_IMPORT_JPEG_QUALITY", "98"))
|
|
|
|
|
|
# AWS S3 Settings for Panoramax private bucket redirects (Option B)
|
|
PANORAMAX_AWS_ACCESS_KEY_ID = getenv("PANORAMAX_AWS_ACCESS_KEY_ID")
|
|
PANORAMAX_AWS_SECRET_ACCESS_KEY = getenv("PANORAMAX_AWS_SECRET_ACCESS_KEY")
|
|
PANORAMAX_AWS_S3_ENDPOINT_URL = getenv("PANORAMAX_AWS_S3_ENDPOINT_URL", "https://s3.rbx.io.cloud.ovh.net")
|
|
PANORAMAX_AWS_S3_BUCKET = getenv("PANORAMAX_AWS_S3_BUCKET")
|
|
PANORAMAX_AWS_S3_PREFIX = getenv("PANORAMAX_AWS_S3_PREFIX", "photos")
|
|
|
|
# Optimization for tests: use a fast password hasher and preserve test database by default
|
|
import sys
|
|
import os
|
|
if 'test' in sys.argv:
|
|
PASSWORD_HASHERS = [
|
|
'django.contrib.auth.hashers.MD5PasswordHasher',
|
|
]
|
|
STORAGES = {
|
|
"default": {
|
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
|
},
|
|
"staticfiles": {
|
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
|
},
|
|
}
|
|
if '--recreate-db' in sys.argv:
|
|
sys.argv.remove('--recreate-db')
|
|
os.environ['RECREATE_DB'] = '1'
|
|
TEST_RUNNER = 'streetup.runner.KeepDBDiscoverRunner' |