Corp/backend/eusrr_backend/settings.py
Игорь Надеин 6cfbc72ac2 Добавлена динамическая генерация ссылок на медиа на основе текущего домена
- Создан context processor media_domain() для автоматического определения MEDIA_URL
- Обновлены шаблоны для использования MEDIA_URL_DYNAMIC
- Добавлены lazy loading и async decoding для оптимизации загрузки изображений
- Теперь медиа-файлы корректно загружаются как на локальном, так и на глобальном домене
2025-12-05 09:45:45 +03:00

423 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
from datetime import timedelta
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
BASE_DIR = Path(__file__).resolve().parent.parent
# -----------------------------------------------------------------------------
# БАЗОВЫЕ НАСТРОЙКИ
# -----------------------------------------------------------------------------
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "dev-insecure-key")
DEBUG = os.getenv("DEBUG", "false").lower() == "true"
def _split_env_list(value: str) -> list[str]:
return [x.strip() for x in (value or "").split(",") if x.strip()]
ALLOWED_HOSTS = _split_env_list(os.getenv("ALLOWED_HOSTS", "localhost,127.0.0.1"))
INSTALLED_APPS = [
"daphne",
"channels",
"django.contrib.admin",
"django_bootstrap5",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.postgres",
"widget_tweaks",
"simple_history",
"rest_framework",
"employees.apps.EmployeesConfig",
"api.apps.ApiConfig",
"hikcentral.apps.HikcentralConfig",
"calendar_app.apps.CalendarAppConfig",
"documents.apps.DocumentsConfig",
"requests_app.apps.RequestsAppConfig",
"feed.apps.FeedConfig",
"realtime.apps.RealtimeConfig", # WebSocket consumers для real-time
"communications.apps.CommunicationsConfig",
"notifications.apps.NotificationsConfig",
"search.apps.SearchConfig",
"bots",
"finance.apps.FinanceConfig",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"simple_history.middleware.HistoryRequestMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"api.middleware.JWTRefreshMiddleware", # Автообновление JWT токенов
"eusrr_backend.middleware.AuthRequiredMiddleware",
"eusrr_backend.middleware.EmailVerificationMiddleware",
"eusrr_backend.middleware.CacheControlMiddleware", # Cache-Control headers
]
ROOT_URLCONF = "eusrr_backend.urls"
TEMPLATES_DIR = BASE_DIR / "templates"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [TEMPLATES_DIR],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"communications.context_processors.chat_unread_total",
"eusrr_backend.context_processors.branding",
"core.context_processors.media_domain",
],
},
},
]
WSGI_APPLICATION = "eusrr_backend.wsgi.application"
ASGI_APPLICATION = "eusrr_backend.asgi.application"
# -----------------------------------------------------------------------------
# БАЗА ДАННЫХ
# -----------------------------------------------------------------------------
USE_SQLITE = os.getenv("USE_SQLITE", "false").lower() == "true"
DATABASES = {
"default": {},
"hikcentral": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "cms",
"USER": "postgres",
"PASSWORD": None,
"HOST": "127.0.0.1",
"PORT": "5432",
"OPTIONS": {"options": "-c search_path=platform"},
},
}
if USE_SQLITE:
DATABASES["default"] = {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
else:
DATABASES["default"] = {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.getenv("POSTGRES_DB", "django"),
"USER": os.getenv("POSTGRES_USER", "django"),
"PASSWORD": os.getenv("POSTGRES_PASSWORD", ""),
"HOST": os.getenv("DB_HOST", "127.0.0.1"),
"PORT": os.getenv("DB_PORT", "5432"),
}
# -----------------------------------------------------------------------------
# АВТОРИЗАЦИЯ / ПАРОЛИ
# -----------------------------------------------------------------------------
# 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 = "ru"
TIME_ZONE = "Europe/Moscow"
USE_I18N = True
USE_TZ = True
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [BASE_DIR / "static"]
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
AUTH_USER_MODEL = "employees.Employee"
API_LOGIN_URL_NAME = "auth_front:login"
LOGIN_URL = "auth_front:login"
LOGIN_REDIRECT_URL = "/"
REGISTRATION_AUTO_LOGIN = True
PASSWORD_RESET_TIMEOUT = 60 * 60 * 24
PHONE_DEFAULT_REGION = os.getenv("PHONE_DEFAULT_REGION", "RU")
PHONENUMBER_DEFAULT_REGION = "RU"
# Медиа файлы
MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
# Используем полный URL для медиа на продакшене
USE_HTTPS = os.getenv("USE_HTTPS", "False").lower() == "true"
DOMAIN = os.getenv("DOMAIN", "127.0.0.1:9000")
if USE_HTTPS:
MEDIA_URL = f"https://{DOMAIN}/media/"
else:
MEDIA_URL = "/media/" # относительный URL для локальной разработки
# -----------------------------------------------------------------------------
# ЛОГИРОВАНИЕ
# -----------------------------------------------------------------------------
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "[{levelname}] {asctime} {name}:{lineno} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "verbose",
},
},
"loggers": {
"documents": {"handlers": ["console"], "level": "DEBUG", "propagate": False},
"bots": {"handlers": ["console"], "level": "DEBUG", "propagate": False},
"communications": {"handlers": ["console"], "level": "DEBUG", "propagate": False},
"django": {"handlers": ["console"], "level": "INFO", "propagate": False},
"aiogram": {"handlers": ["console"], "level": "INFO", "propagate": False},
# полезно видеть ошибки и отладку ldap3
"ldap3": {"handlers": ["console"], "level": "WARNING", "propagate": False},
},
}
# -----------------------------------------------------------------------------
# БЕЗОПАСНОСТЬ И ПРОКСИ
# -----------------------------------------------------------------------------
CSRF_TRUSTED_ORIGINS = _split_env_list(
os.getenv("CSRF_TRUSTED_ORIGINS", "https://*.sytes.net")
)
# Настройки CSRF для AJAX запросов
CSRF_COOKIE_HTTPONLY = False # Позволяет JavaScript читать cookie
CSRF_COOKIE_SAMESITE = 'Lax'
CSRF_USE_SESSIONS = False
# -----------------------------------------------------------------------------
# EMAIL
# -----------------------------------------------------------------------------
EMAIL_BACKEND = os.getenv(
"EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend"
)
EMAIL_HOST = os.getenv("EMAIL_HOST", "")
EMAIL_PORT = int(os.getenv("EMAIL_PORT", "465"))
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "")
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "")
EMAIL_USE_SSL = os.getenv("EMAIL_USE_SSL", "true").lower() == "true"
# Если SSL включён (465), TLS должен быть False
EMAIL_USE_TLS = (
False if EMAIL_USE_SSL else (os.getenv("EMAIL_USE_TLS", "false").lower() == "true")
)
DEFAULT_FROM_EMAIL = os.getenv(
"DEFAULT_FROM_EMAIL", EMAIL_HOST_USER or "webmaster@localhost"
)
# -----------------------------------------------------------------------------
# NOTIFICATIONS
# -----------------------------------------------------------------------------
# Порог для определения массовой рассылки (количество получателей)
# При массовой рассылке уведомления создаются быстро, а отправка идёт в фоне
NOTIFICATION_BULK_THRESHOLD = int(os.getenv("NOTIFICATION_BULK_THRESHOLD", "10"))
# -----------------------------------------------------------------------------
# CHANNELS & CACHE
# -----------------------------------------------------------------------------
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": (
f"redis://{os.getenv('REDIS_HOST', '127.0.0.1')}:"
f"{os.getenv('REDIS_PORT', '6379')}/1"
),
"OPTIONS": {
"db": "1", # Используем отдельную БД для кэша (Channels - 0)
},
"KEY_PREFIX": "eusrr_cache",
"TIMEOUT": 300, # 5 минут по умолчанию
}
}
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [
(
os.getenv("REDIS_HOST", "127.0.0.1"),
int(os.getenv("REDIS_PORT", "6379")),
)
],
},
}
}
# -----------------------------------------------------------------------------
# DRF / JWT
# -----------------------------------------------------------------------------
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework_simplejwt.authentication.JWTAuthentication"
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
"rest_framework.permissions.DjangoModelPermissions",
],
"DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.AnonRateThrottle"],
"DEFAULT_THROTTLE_RATES": {"anon": "60/min"},
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=int(os.getenv("JWT_ACCESS_MIN", "30"))),
"REFRESH_TOKEN_LIFETIME": timedelta(days=int(os.getenv("JWT_REFRESH_DAYS", "7"))),
"ALGORITHM": "HS256",
"AUTH_HEADER_TYPES": ("Bearer",),
"LEEWAY": 30,
}
# Автообновление JWT токенов: за сколько минут до истечения обновлять
JWT_REFRESH_THRESHOLD_MINUTES = int(os.getenv("JWT_REFRESH_THRESHOLD_MIN", "5"))
API_BASE_URL = os.getenv("API_BASE_URL", "http://127.0.0.1:9000/api")
# -----------------------------------------------------------------------------
# АУТЕНТИФИКАЦИОННЫЕ БЭКЕНДЫ
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# AUTHENTICATION_BACKENDS
# -----------------------------------------------------------------------------
# Порядок важен: первый успешный бэкенд останавливает цепочку
AUTHENTICATION_BACKENDS = [
"eusrr_backend.auth_backends.LDAP3Backend", # работает только если LDAP_ENABLED=True
"eusrr_backend.auth_backends.EmailOrPhoneBackend", # фоллбэк для режима без LDAP
"eusrr_backend.auth_backends.SuperuserOnlyBackend", # экстренный доступ для админа
"eusrr_backend.auth_backends.PositionRoleBackend", # расчёт прав на основе должностей
"django.contrib.auth.backends.ModelBackend", # стандартный Django бэкенд
]
# -----------------------------------------------------------------------------
# LDAP (двусторонний обмен + LWW)
# -----------------------------------------------------------------------------
LDAP_ENABLED = os.getenv("LDAP_ENABLED", "true").lower() == "true"
# Основное подключение
LDAP_URI = os.getenv("LDAP_URI", "ldaps://dcii.robotail.local:636")
LDAP_BIND_DN = os.getenv("LDAP_BIND_DN", "")
LDAP_BIND_PASSWORD = os.getenv("LDAP_BIND_PASSWORD", "")
# TLS/CA
LDAP_CA_CERTS = os.getenv("LDAP_CA_CERTS", "") # путь к CA bundle/серту (если нужен)
LDAP_TLS_REQUIRED = os.getenv("LDAP_TLS_REQUIRED", "true").lower() == "true"
# Где искать пользователей
LDAP_USER_BASE = os.getenv("LDAP_USER_BASE", "OU=company,DC=robotail,DC=local")
# Где создавать новых пользователей (может отличаться от базы поиска)
LDAP_USERS_BASE = os.getenv("LDAP_USERS_BASE", LDAP_USER_BASE)
# Базовый DN для операций создания (если не указан department_dn)
LDAP_BASE_DN = os.getenv("LDAP_BASE_DN", LDAP_USERS_BASE)
# UPN-суффикс для создания пользователей (userPrincipalName) - домен БЕЗ @
LDAP_UPN_SUFFIX = os.getenv("LDAP_USER_UPN_SUFFIX", "robotail.local")
LDAP_USER_FILTER = os.getenv(
"LDAP_USER_FILTER", "(&(objectCategory=person)(objectClass=user))"
)
# Для AD по умолчанию; для OpenLDAP можно обнулить в .env
LDAP_ACTIVE_FILTER = os.getenv(
"LDAP_ACTIVE_FILTER",
"(!(userAccountControl:1.2.840.113556.1.4.803:=2))", # битовый NOT DISABLED
)
LDAP_ATTR_MAIL = os.getenv("LDAP_ATTR_MAIL", "mail")
LDAP_ATTR_GIVENNAME = os.getenv("LDAP_ATTR_GIVENNAME", "givenName")
LDAP_ATTR_SN = os.getenv("LDAP_ATTR_SN", "sn")
LDAP_ATTR_PHONE = os.getenv("LDAP_ATTR_PHONE", "telephoneNumber")
LDAP_PHONE_ATTRS = tuple(
_split_env_list(os.getenv("LDAP_PHONE_ATTRS", "mobile,telephoneNumber"))
)
LDAP_DEPT_ATTR = os.getenv("LDAP_DEPT_ATTR", "") # например, departmentNumber
LDAP_SYNC_GROUPS = os.getenv("LDAP_SYNC_GROUPS", "false").lower() == "true"
LDAP_GROUP_ATTR = os.getenv("LDAP_GROUP_ATTR", "memberOf")
# Пример: {"CN=HR,OU=Groups,DC=...,DC=...": "hr"}
LDAP_GROUP_MAP = {}
LDAP_GROUPS_EXCLUSIVE = os.getenv("LDAP_GROUPS_EXCLUSIVE", "false").lower() == "true"
# WRITE-BACK
LDAP_WRITE_ENABLED = os.getenv("LDAP_WRITE_ENABLED", "false").lower() == "true"
LDAP_WRITE_DN = os.getenv("LDAP_WRITE_DN", LDAP_BIND_DN)
LDAP_WRITE_PASSWORD = os.getenv("LDAP_WRITE_PASSWORD", LDAP_BIND_PASSWORD)
LDAP_WRITE_TIMEOUT = int(os.getenv("LDAP_WRITE_TIMEOUT", "5"))
# UPN суффикс для создания пользователей (домен БЕЗ @, например robotail.local)
LDAP_UPN_SUFFIX = os.getenv("LDAP_USER_UPN_SUFFIX", "robotail.local")
# Белый список: локальные поля -> LDAP-атрибуты
LDAP_WRITE_ATTRS = {
"first_name": LDAP_ATTR_GIVENNAME,
"last_name": LDAP_ATTR_SN,
# фактическое имя локального телефонного поля подставляет код
"phone": LDAP_ATTR_PHONE,
# "photo": "jpegPhoto",
}
# LWW (Last Writer Wins) — сравнение меток изменения
# Для AD обычно whenChanged, для OpenLDAP — modifyTimestamp
LDAP_ASSERT_ATTR_AD = "whenChanged"
LDAP_ASSERT_ATTR_OL = "modifyTimestamp"
LDAP_ASSERT_ATTR = os.getenv("LDAP_ASSERT_ATTR", LDAP_ASSERT_ATTR_AD)
# Локальное поле «последнее изменение» (ваше поле модели, напр. updated_at)
LOCAL_ASSERT_FIELD = os.getenv("LOCAL_ASSERT_FIELD", "updated_at")
LDAP_CREATE_EXTRA_ATTRS = {
"sAMAccountName": "{username20}",
"userPrincipalName": "{upn}",
"givenName": "{first_name_or_dot}",
"sn": "{last_name_or_dot}",
"displayName": "{cn}",
"mail": "{email}",
}
LDAP_SYNC_MODE = os.getenv("LDAP_SYNC_MODE", "lww") # lww|ldap|django
LDAP_PURGE = os.getenv("LDAP_PURGE", "false").lower() == "true"
LDAP_CONNECT_TIMEOUT = int(os.getenv("LDAP_CONNECT_TIMEOUT", "5"))
LDAP_OPERATION_TIMEOUT = int(os.getenv("LDAP_OPERATION_TIMEOUT", "10"))
LDAP_USERS_BASE = os.getenv("LDAP_USERS_BASE")
LDAP_USER_BASE = os.getenv("LDAP_USER_BASE")
LDAP_DEPARTMENTS_BASE = os.getenv(
"LDAP_DEPARTMENTS_BASE", "OU=Departments,OU=company,DC=robotail,DC=local"
)
LDAP_GROUPS_BASE = os.getenv("LDAP_GROUPS_BASE")
LDAP_AUTO_CREATE = os.getenv("LDAP_AUTO_CREATE", "False")
LDAP_RESPECT_IS_ACTIVE = True
LDAP_RESPECT_AD_DISABLED = True
LDAP_REGISTRATION_CREATE = True
LDAP_POSITIONS_BASE = os.getenv("LDAP_POSITIONS_BASE")
BRAND_NAME = os.getenv("BRAND_NAME", "HiRo")
BRAND_LOGO = "img/logo.png"
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
TELEGRAM_BOT_USERNAME = os.getenv("TELEGRAM_BOT_USERNAME", "eusrr_bot")