"""
Configuracion de Django para el servicio de notificaciones.

Toda la configuracion sensible o dependiente del entorno se lee de variables
de entorno (o de un archivo `.env` local). No debe existir ningun secreto
escrito en este archivo: ver `.env.example` y `DESPLIEGUE.md`.

ATENCION AL DESPLEGAR: la version anterior llevaba la SECRET_KEY y las
credenciales de MySQL escritas aqui. Ahora son obligatorias por entorno, asi
que hay que crear el `.env` en el servidor ANTES de desplegar o el proceso no
arranca. El procedimiento esta en DESPLIEGUE.md.

Documentacion: https://docs.djangoproject.com/en/5.2/ref/settings/
"""

from datetime import timedelta
from pathlib import Path

import environ
from django.core.exceptions import ImproperlyConfigured

BASE_DIR = Path(__file__).resolve().parent.parent

# ---------------------------------------------------------------------------
# Carga de entorno
# ---------------------------------------------------------------------------
env = environ.Env(
    DJANGO_DEBUG=(bool, False),
    DJANGO_ALLOWED_HOSTS=(list, []),
    DJANGO_CSRF_TRUSTED_ORIGINS=(list, []),
    CORS_ALLOWED_ORIGINS=(list, []),
    DATABASE_URL=(str, "sqlite:///" + str(BASE_DIR / "db.sqlite3")),
    DATABASE_CONN_MAX_AGE=(int, 60),
    # --- Seguridad HTTP ---
    SECURE_SSL_REDIRECT=(bool, True),
    SECURE_HSTS_SECONDS=(int, 31536000),
    BEHIND_TLS_PROXY=(bool, True),
    NUM_PROXIES=(int, 1),
    REDIS_CACHE_URL=(str, ""),
    # --- Autenticacion ---
    ACCESS_TOKEN_TTL_SECONDS=(int, 300),
    # --- Limites de peticiones ---
    THROTTLE_RATE_AUTH=(str, "10/min"),
    THROTTLE_RATE_SEND=(str, "60/min"),
    THROTTLE_RATE_ANON=(str, "20/min"),
    THROTTLE_RATE_USER=(str, "2000/day"),
    # --- Celery ---
    CELERY_BROKER_URL=(str, "redis://localhost:6379/0"),
    CELERY_RESULT_BACKEND=(str, "redis://localhost:6379/1"),
    CELERY_TASK_ALWAYS_EAGER=(bool, False),
    # --- Politica comun de notificaciones ---
    NOTIFICATIONS_STORE_BODY=(bool, False),
    NOTIFICATIONS_RETENTION_DAYS=(int, 90),
    # --- Canal de correo ---
    EMAIL_DEFAULT_PROVIDER=(str, "sendgrid"),
    EMAIL_ENABLED_PROVIDERS=(list, ["sendgrid", "gmail"]),
    EMAIL_ALLOWED_FROM_DOMAINS=(list, []),
    EMAIL_ALLOWED_FROM_ADDRESSES=(list, []),
    EMAIL_DEFAULT_FROM=(str, ""),
    EMAIL_DEFAULT_FROM_NAME=(str, "Notificaciones Zurco"),
    EMAIL_DEFAULT_REPLY_TO=(str, ""),
    EMAIL_MAX_RECIPIENTS=(int, 50),
    EMAIL_MAX_BODY_BYTES=(int, 512 * 1024),
    EMAIL_MAX_ATTACHMENT_BYTES=(int, 7 * 1024 * 1024),
    EMAIL_MAX_TOTAL_ATTACHMENT_BYTES=(int, 20 * 1024 * 1024),
    EMAIL_MAX_ATTACHMENTS=(int, 10),
    # --- SendGrid ---
    SENDGRID_API_KEY=(str, ""),
    SENDGRID_WEBHOOK_PUBLIC_KEY=(str, ""),
    SENDGRID_SANDBOX_MODE=(bool, False),
    SENDGRID_TIMEOUT_SECONDS=(int, 15),
    SENDGRID_WEBHOOK_TOLERANCE_SECONDS=(int, 600),
    # --- Gmail ---
    GMAIL_CREDENTIALS_FILE=(str, ""),
    GMAIL_TOKEN_FILE=(str, ""),
    GMAIL_SERVICE_ACCOUNT_FILE=(str, ""),
    GMAIL_SENDER=(str, "me"),
    GMAIL_TIMEOUT_SECONDS=(int, 20),
    # --- Canales de mensajeria (SMS y WhatsApp) ---
    MESSAGING_PROVIDER=(str, "twilio"),
    MESSAGING_MAX_RECIPIENTS=(int, 100),
    MESSAGING_MAX_BODY_CHARS=(int, 1600),
    DEFAULT_PHONE_COUNTRY_CODE=(str, ""),
    WHATSAPP_REPLY_PROMPT_DAYS=(int, 1),
    # --- Twilio ---
    TWILIO_ACCOUNT_SID=(str, ""),
    TWILIO_AUTH_TOKEN=(str, ""),
    TWILIO_SMS_FROM=(str, ""),
    TWILIO_WHATSAPP_FROM=(str, ""),
    TWILIO_MESSAGING_SERVICE_SID=(str, ""),
    TWILIO_STATUS_CALLBACK_BASE_URL=(str, ""),
    # --- Observabilidad ---
    LOG_LEVEL=(str, "INFO"),
    LOG_FORMAT=(str, "json"),
    EXPOSE_API_DOCS=(bool, False),
)

ENV_FILE = BASE_DIR / ".env"
if ENV_FILE.exists():
    env.read_env(str(ENV_FILE))

DEBUG = env("DJANGO_DEBUG")

# ---------------------------------------------------------------------------
# Clave secreta
#
# Sin valor por defecto a proposito: la clave anterior estaba escrita en el
# repositorio y por tanto es publica. Generar una nueva con:
#   python -c "from django.core.management.utils import get_random_secret_key as k; print(k())"
# ---------------------------------------------------------------------------
SECRET_KEY = env("DJANGO_SECRET_KEY", default=None)
if not SECRET_KEY:
    raise ImproperlyConfigured(
        "Falta la variable de entorno DJANGO_SECRET_KEY. Generala con:\n"
        '  python -c "from django.core.management.utils import '
        'get_random_secret_key as k; print(k())"\n'
        "Ver DESPLIEGUE.md."
    )
if not DEBUG and SECRET_KEY.startswith("django-insecure-"):
    raise ImproperlyConfigured(
        "DJANGO_SECRET_KEY sigue siendo una clave de desarrollo. "
        "Genera una clave nueva antes de desplegar."
    )

ALLOWED_HOSTS = env("DJANGO_ALLOWED_HOSTS")
if not ALLOWED_HOSTS:
    if DEBUG:
        ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]"]
    else:
        raise ImproperlyConfigured(
            "DJANGO_ALLOWED_HOSTS es obligatorio cuando DJANGO_DEBUG=False."
        )

CSRF_TRUSTED_ORIGINS = env("DJANGO_CSRF_TRUSTED_ORIGINS")


# ---------------------------------------------------------------------------
# Aplicaciones
# ---------------------------------------------------------------------------
# Vida de los tokens de acceso. Antes no caducaban: un token emitido y nunca
# usado seguia valido indefinidamente. Cinco minutos es de sobra para el flujo
# real, que es pedir token y enviar acto seguido.
ACCESS_TOKEN_TTL_SECONDS = env("ACCESS_TOKEN_TTL_SECONDS")

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "corsheaders",
    "rest_framework",
    "drf_spectacular",
    "authentication",
    "keys",
    "notifications",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "corsheaders.middleware.CorsMiddleware",
    "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",
    "core.middleware.RequestIDMiddleware",
]

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"
ASGI_APPLICATION = "core.asgi.application"


# ---------------------------------------------------------------------------
# Base de datos
#
# Una unica variable:  mysql://usuario:password@host:3306/base
# ---------------------------------------------------------------------------
DATABASES = {"default": env.db("DATABASE_URL")}
DATABASES["default"]["CONN_MAX_AGE"] = env("DATABASE_CONN_MAX_AGE")
DATABASES["default"]["CONN_HEALTH_CHECKS"] = True

if DATABASES["default"]["ENGINE"] == "django.db.backends.mysql":
    options = DATABASES["default"].setdefault("OPTIONS", {})
    options.update({"charset": "utf8mb4", "sql_mode": "STRICT_TRANS_TABLES"})

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"


# ---------------------------------------------------------------------------
# Cache
#
# Los limites de peticiones se contabilizan aqui. Con la cache local en
# memoria cada proceso lleva su propia cuenta, asi que el limite real acaba
# siendo el configurado multiplicado por el numero de procesos.
# ---------------------------------------------------------------------------
_redis_cache_url = env("REDIS_CACHE_URL")
if _redis_cache_url:
    CACHES = {
        "default": {
            "BACKEND": "django.core.cache.backends.redis.RedisCache",
            "LOCATION": _redis_cache_url,
        }
    }
else:
    CACHES = {
        "default": {
            "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
            "LOCATION": "notificaciones",
        }
    }


# ---------------------------------------------------------------------------
# Validacion de contrasenias (usuarios del panel de administracion)
# ---------------------------------------------------------------------------
AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
        "OPTIONS": {"min_length": 12},
    },
    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
    {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]


# ---------------------------------------------------------------------------
# Seguridad HTTP
# ---------------------------------------------------------------------------
if env("BEHIND_TLS_PROXY"):
    SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

SECURE_SSL_REDIRECT = env("SECURE_SSL_REDIRECT") and not DEBUG
SECURE_HSTS_SECONDS = env("SECURE_HSTS_SECONDS") if not DEBUG else 0
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin"

SESSION_COOKIE_SECURE = not DEBUG
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
CSRF_COOKIE_SECURE = not DEBUG
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = "Lax"

X_FRAME_OPTIONS = "DENY"

DATA_UPLOAD_MAX_MEMORY_SIZE = env("EMAIL_MAX_TOTAL_ATTACHMENT_BYTES") + (2 * 1024 * 1024)
FILE_UPLOAD_MAX_MEMORY_SIZE = 5 * 1024 * 1024
DATA_UPLOAD_MAX_NUMBER_FIELDS = 200
DATA_UPLOAD_MAX_NUMBER_FILES = env("EMAIL_MAX_ATTACHMENTS")


# ---------------------------------------------------------------------------
# CORS
# ---------------------------------------------------------------------------
CORS_ALLOW_ALL_ORIGINS = False
CORS_ALLOWED_ORIGINS = env("CORS_ALLOWED_ORIGINS")
CORS_ALLOW_CREDENTIALS = False
CORS_ALLOW_HEADERS = [
    "accept",
    "accept-encoding",
    "authorization",
    "content-type",
    "origin",
    "user-agent",
    "x-api-key",
    "x-request-id",
    "x-requested-with",
]
CORS_ALLOW_METHODS = ["GET", "POST", "OPTIONS"]
CORS_PREFLIGHT_MAX_AGE = 3600
CORS_EXPOSE_HEADERS = ["x-request-id"]


# ---------------------------------------------------------------------------
# Django REST Framework
#
# DEFAULT_PERMISSION_CLASSES es lo que impide que un endpoint sin
# `permission_classes` quede abierto: el valor por defecto de DRF es AllowAny.
# ---------------------------------------------------------------------------
REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",
    ],
    "DEFAULT_THROTTLE_CLASSES": [
        "rest_framework.throttling.AnonRateThrottle",
        "rest_framework.throttling.UserRateThrottle",
    ],
    "DEFAULT_THROTTLE_RATES": {
        "anon": env("THROTTLE_RATE_ANON"),
        "user": env("THROTTLE_RATE_USER"),
        "auth": env("THROTTLE_RATE_AUTH"),
        "send": env("THROTTLE_RATE_SEND"),
    },
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
    "EXCEPTION_HANDLER": "core.exceptions.api_exception_handler",
    "DEFAULT_RENDERER_CLASSES": [
        "rest_framework.renderers.JSONRenderer",
    ],
    # Numero de proxies de confianza delante de la aplicacion. Sin esto, los
    # limites de peticiones se esquivan falsificando X-Forwarded-For.
    "NUM_PROXIES": env("NUM_PROXIES"),
}

if DEBUG:
    REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append(
        "rest_framework.renderers.BrowsableAPIRenderer"
    )

EXPOSE_API_DOCS = env("EXPOSE_API_DOCS")

SPECTACULAR_SETTINGS = {
    "TITLE": "API de notificaciones",
    "DESCRIPTION": (
        "Servicio de notificaciones por correo, SMS y WhatsApp con auditoria "
        "de envios y webhooks de eventos."
    ),
    "VERSION": "1.1.0",
    "SERVE_INCLUDE_SCHEMA": False,
    "COMPONENT_SPLIT_REQUEST": True,
    "SCHEMA_PATH_PREFIX": "/(auth|notifications)/v[0-9]",
}


# ---------------------------------------------------------------------------
# Celery
# ---------------------------------------------------------------------------
CELERY_BROKER_URL = env("CELERY_BROKER_URL")
CELERY_RESULT_BACKEND = env("CELERY_RESULT_BACKEND")
CELERY_TASK_ALWAYS_EAGER = env("CELERY_TASK_ALWAYS_EAGER")
CELERY_TASK_EAGER_PROPAGATES = False
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
CELERY_TIMEZONE = "America/Mexico_City"
# Si el worker muere a mitad de un envio, la tarea vuelve a la cola en lugar
# de perderse.
CELERY_TASK_ACKS_LATE = True
CELERY_TASK_REJECT_ON_WORKER_LOST = True
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
CELERY_TASK_TIME_LIMIT = 120
CELERY_TASK_SOFT_TIME_LIMIT = 90
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
CELERY_RESULT_EXPIRES = timedelta(days=1)


# ---------------------------------------------------------------------------
# Politica comun de notificaciones
# ---------------------------------------------------------------------------
# Guardar el cuerpo en la auditoria implica almacenar datos personales.
NOTIFICATIONS_STORE_BODY = env("NOTIFICATIONS_STORE_BODY")
NOTIFICATIONS_RETENTION_DAYS = env("NOTIFICATIONS_RETENTION_DAYS")


# ---------------------------------------------------------------------------
# Canal de correo
# ---------------------------------------------------------------------------
EMAIL_DEFAULT_PROVIDER = env("EMAIL_DEFAULT_PROVIDER")
EMAIL_ENABLED_PROVIDERS = env("EMAIL_ENABLED_PROVIDERS")

# Allowlist de remitentes. Si ambas listas estan vacias se rechaza cualquier
# envio: obliga a declarar desde que direcciones puede enviar el servicio e
# impide que una credencial comprometida suplante dominios ajenos.
EMAIL_ALLOWED_FROM_DOMAINS = [
    d.strip().lower().lstrip("@")
    for d in env("EMAIL_ALLOWED_FROM_DOMAINS")
    if d.strip()
]
EMAIL_ALLOWED_FROM_ADDRESSES = [
    a.strip().lower() for a in env("EMAIL_ALLOWED_FROM_ADDRESSES") if a.strip()
]

# Remitente que usa el endpoint de plantilla, que no lo recibe del cliente.
EMAIL_DEFAULT_FROM = env("EMAIL_DEFAULT_FROM")
EMAIL_DEFAULT_FROM_NAME = env("EMAIL_DEFAULT_FROM_NAME")
EMAIL_DEFAULT_REPLY_TO = env("EMAIL_DEFAULT_REPLY_TO")

EMAIL_MAX_RECIPIENTS = env("EMAIL_MAX_RECIPIENTS")
EMAIL_MAX_BODY_BYTES = env("EMAIL_MAX_BODY_BYTES")
EMAIL_MAX_ATTACHMENT_BYTES = env("EMAIL_MAX_ATTACHMENT_BYTES")
EMAIL_MAX_TOTAL_ATTACHMENT_BYTES = env("EMAIL_MAX_TOTAL_ATTACHMENT_BYTES")
EMAIL_MAX_ATTACHMENTS = env("EMAIL_MAX_ATTACHMENTS")

# Extensiones que nunca se aceptan como adjunto, sin importar el tipo MIME
# declarado.
EMAIL_BLOCKED_ATTACHMENT_EXTENSIONS = {
    "ade", "adp", "apk", "app", "appx", "bat", "cab", "chm", "cmd", "com",
    "cpl", "dll", "dmg", "exe", "hta", "ins", "iso", "isp", "jar", "js",
    "jse", "lib", "lnk", "mde", "msc", "msi", "msix", "msp", "mst", "nsh",
    "pif", "ps1", "psm1", "reg", "scr", "sct", "shb", "sys", "vb", "vbe",
    "vbs", "vxd", "wsc", "wsf", "wsh",
}

# Tipos MIME reales aceptados, detectados con libmagic y no por el
# Content-Type que declara el cliente.
EMAIL_ALLOWED_ATTACHMENT_MIME_TYPES = {
    "application/json",
    "application/msword",
    "application/pdf",
    "application/rtf",
    "application/vnd.ms-excel",
    "application/vnd.ms-powerpoint",
    "application/vnd.oasis.opendocument.presentation",
    "application/vnd.oasis.opendocument.spreadsheet",
    "application/vnd.oasis.opendocument.text",
    "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    "application/zip",
    "image/bmp",
    "image/gif",
    "image/jpeg",
    "image/png",
    "image/tiff",
    "image/webp",
    "text/calendar",
    "text/csv",
    "text/plain",
}

# --- SendGrid ---
SENDGRID_API_KEY = env("SENDGRID_API_KEY")
SENDGRID_WEBHOOK_PUBLIC_KEY = env("SENDGRID_WEBHOOK_PUBLIC_KEY")
SENDGRID_SANDBOX_MODE = env("SENDGRID_SANDBOX_MODE")
SENDGRID_TIMEOUT_SECONDS = env("SENDGRID_TIMEOUT_SECONDS")
SENDGRID_WEBHOOK_TOLERANCE_SECONDS = env("SENDGRID_WEBHOOK_TOLERANCE_SECONDS")

# --- Gmail ---
GMAIL_CREDENTIALS_FILE = env("GMAIL_CREDENTIALS_FILE")
GMAIL_TOKEN_FILE = env("GMAIL_TOKEN_FILE")
GMAIL_SERVICE_ACCOUNT_FILE = env("GMAIL_SERVICE_ACCOUNT_FILE")
GMAIL_SENDER = env("GMAIL_SENDER")
GMAIL_TIMEOUT_SECONDS = env("GMAIL_TIMEOUT_SECONDS")


# ---------------------------------------------------------------------------
# Twilio (SMS y WhatsApp)
#
# Estaban escritos en el codigo. Se leen de entorno; el
# `auth_token` que estuvo en el repositorio hay que rotarlo en la consola de
# Twilio.
# ---------------------------------------------------------------------------
MESSAGING_PROVIDER = env("MESSAGING_PROVIDER")
MESSAGING_MAX_RECIPIENTS = env("MESSAGING_MAX_RECIPIENTS")
MESSAGING_MAX_BODY_CHARS = env("MESSAGING_MAX_BODY_CHARS")

# Codigo de pais que se antepone cuando el numero llega sin el, por ejemplo
# "+52". Vacio exige que el cliente mande siempre E.164, que es lo mas seguro:
# adivinar el pais lleva a mandar mensajes al sitio equivocado.
DEFAULT_PHONE_COUNTRY_CODE = env("DEFAULT_PHONE_COUNTRY_CODE")

# Cada cuantos dias se vuelve a preguntar al destinatario de WhatsApp si
# quiere seguir recibiendo notificaciones.
WHATSAPP_REPLY_PROMPT_DAYS = env("WHATSAPP_REPLY_PROMPT_DAYS")

TWILIO_ACCOUNT_SID = env("TWILIO_ACCOUNT_SID")
TWILIO_AUTH_TOKEN = env("TWILIO_AUTH_TOKEN")
TWILIO_SMS_FROM = env("TWILIO_SMS_FROM")
TWILIO_WHATSAPP_FROM = env("TWILIO_WHATSAPP_FROM")
TWILIO_MESSAGING_SERVICE_SID = env("TWILIO_MESSAGING_SERVICE_SID")

# URL publica base del servicio. Twilio la usa para notificar los cambios de
# estado de cada mensaje. Vacia desactiva los avisos de entrega.
TWILIO_STATUS_CALLBACK_BASE_URL = env("TWILIO_STATUS_CALLBACK_BASE_URL")


# ---------------------------------------------------------------------------
# Internacionalizacion
# ---------------------------------------------------------------------------
LANGUAGE_CODE = "es-mx"
TIME_ZONE = "America/Mexico_City"
USE_I18N = True
USE_TZ = True


# ---------------------------------------------------------------------------
# Archivos estaticos
# ---------------------------------------------------------------------------
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"


# ---------------------------------------------------------------------------
# Registro de eventos
# ---------------------------------------------------------------------------
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "filters": {
        "redact_sensitive": {"()": "core.logging_utils.SensitiveDataFilter"},
        "request_id": {"()": "core.logging_utils.RequestIDFilter"},
    },
    "formatters": {
        "json": {"()": "core.logging_utils.JSONFormatter"},
        "console": {
            "format": "[{asctime}] {levelname} {name} [{request_id}] {message}",
            "style": "{",
        },
    },
    "handlers": {
        "stdout": {
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout",
            "formatter": env("LOG_FORMAT"),
            "filters": ["request_id", "redact_sensitive"],
        },
    },
    "root": {"handlers": ["stdout"], "level": env("LOG_LEVEL")},
    "loggers": {
        "django": {"handlers": ["stdout"], "level": "INFO", "propagate": False},
        "django.security": {
            "handlers": ["stdout"],
            "level": "WARNING",
            "propagate": False,
        },
        "django.request": {
            "handlers": ["stdout"],
            "level": "ERROR",
            "propagate": False,
        },
        "notifications": {
            "handlers": ["stdout"],
            "level": env("LOG_LEVEL"),
            "propagate": False,
        },
        "core": {
            "handlers": ["stdout"],
            "level": env("LOG_LEVEL"),
            "propagate": False,
        },
        "celery": {"handlers": ["stdout"], "level": "INFO", "propagate": False},
    },
}
