Fixes: None; Extra: Remove unused dependencies (`mypy`, `isort`, `celery-stubs`, etc.), update `uv.lock` with dependency changes, and upgrade versions for `debugpy`, `jupyterlab`, and `ruff`.
537 lines
15 KiB
Python
537 lines
15 KiB
Python
import logging
|
|
from datetime import datetime
|
|
from os import getenv, name
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
|
|
EVIBES_VERSION = "2025.4"
|
|
RELEASE_DATE = datetime(2025, 12, 5)
|
|
|
|
PROJECT_NAME = getenv("EVIBES_PROJECT_NAME", "eVibes")
|
|
TASKBOARD_URL = getenv(
|
|
"EVIBES_TASKBOARD_URL", "https://plane.wiseless.xyz/spaces/issues/dd33cb0ab9b04ef08a10f7eefae6d90c/?board=kanban"
|
|
)
|
|
SUPPORT_CONTACT = getenv("EVIBES_SUPPORT_CONTACT", "https://t.me/fureunoir")
|
|
|
|
BASE_DIR: Path = Path(__file__).resolve().parent.parent.parent
|
|
INITIALIZED: bool = (BASE_DIR / ".initialized").exists()
|
|
|
|
SECRET_KEY: str = getenv("SECRET_KEY", "SUPER_SECRET_KEY")
|
|
DEBUG: bool = bool(int(getenv("DEBUG", "1")))
|
|
DEBUG_DATABASE: bool = bool(int(getenv("DEBUG_DATABASE", "0")))
|
|
DEBUG_CELERY: bool = bool(int(getenv("DEBUG_DATABASE", "0")))
|
|
|
|
BASE_DOMAIN: str = getenv("EVIBES_BASE_DOMAIN", "localhost")
|
|
STOREFRONT_DOMAIN: str = getenv("EVIBES_STOREFRONT_DOMAIN", "localhost")
|
|
|
|
ALLOW_MESSAGING: bool = bool(int(getenv("EVIBES_ALLOW_MESSAGING", "0")))
|
|
|
|
ALLOWED_HOSTS: set[str] = {
|
|
"app",
|
|
"worker",
|
|
"beat",
|
|
"localhost",
|
|
"127.0.0.1",
|
|
}
|
|
|
|
if DEBUG:
|
|
ALLOWED_HOSTS.add("*")
|
|
else:
|
|
for entry in getenv("ALLOWED_HOSTS", "").split(" "):
|
|
ALLOWED_HOSTS.add(entry)
|
|
|
|
ALLOWED_HOSTS: tuple[str, ...] = tuple(ALLOWED_HOSTS)
|
|
|
|
CSRF_TRUSTED_ORIGINS: set[str] = {
|
|
"http://127.0.0.1",
|
|
"http://localhost",
|
|
}
|
|
|
|
for entry in getenv("CSRF_TRUSTED_ORIGINS", "").split(" "):
|
|
CSRF_TRUSTED_ORIGINS.add(entry)
|
|
|
|
CSRF_TRUSTED_ORIGINS: tuple[str, ...] = tuple(CSRF_TRUSTED_ORIGINS)
|
|
|
|
if DEBUG:
|
|
CORS_ALLOW_ALL_ORIGINS = True
|
|
else:
|
|
CORS_ALLOWED_ORIGINS: set[str] = {
|
|
"http://127.0.0.1",
|
|
"http://localhost",
|
|
}
|
|
for entry in getenv("CORS_ALLOWED_ORIGINS", "").split(" "):
|
|
CORS_ALLOWED_ORIGINS.add(entry)
|
|
|
|
CORS_ALLOWED_ORIGINS: tuple[str, ...] = tuple(CORS_ALLOWED_ORIGINS)
|
|
|
|
CORS_ALLOW_METHODS = (
|
|
"DELETE",
|
|
"GET",
|
|
"OPTIONS",
|
|
"PATCH",
|
|
"POST",
|
|
"PUT",
|
|
)
|
|
|
|
CORS_ALLOW_HEADERS = (
|
|
"accept",
|
|
"accept-encoding",
|
|
"accept-language",
|
|
"authorization",
|
|
"baggage",
|
|
"connection",
|
|
"content-type",
|
|
"dnt",
|
|
"host",
|
|
"origin",
|
|
"referer",
|
|
"sec-fetch-dest",
|
|
"sec-fetch-mode",
|
|
"sec-fetch-site",
|
|
"sec-gpc",
|
|
"sentry-trace",
|
|
"user-agent",
|
|
"x-csrftoken",
|
|
"x-evibes-auth",
|
|
"x-requested-with",
|
|
)
|
|
|
|
USE_X_FORWARDED_HOST = True
|
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") if not DEBUG else None
|
|
X_FRAME_OPTIONS = "SAMEORIGIN"
|
|
|
|
UNSAFE_CACHE_KEYS: list[str] = []
|
|
|
|
SITE_ID: int = 1
|
|
|
|
INSTALLED_APPS: list[str] = [
|
|
"django_prometheus",
|
|
"unfold",
|
|
"unfold.contrib.filters",
|
|
"unfold.contrib.forms",
|
|
"unfold.contrib.inlines",
|
|
"unfold_markdown",
|
|
"unfold.contrib.constance",
|
|
"unfold.contrib.import_export",
|
|
"constance",
|
|
"modeltranslation",
|
|
"django.contrib.admin",
|
|
"django.contrib.admindocs",
|
|
"django.contrib.auth",
|
|
"django.contrib.contenttypes",
|
|
"django.contrib.sessions",
|
|
"django.contrib.messages",
|
|
"django.contrib.staticfiles",
|
|
"django.contrib.sitemaps",
|
|
"django.contrib.gis",
|
|
"django.contrib.humanize",
|
|
"health_check",
|
|
"health_check.db",
|
|
"health_check.cache",
|
|
"health_check.storage",
|
|
"health_check.contrib.migrations",
|
|
"health_check.contrib.celery_ping",
|
|
"health_check.contrib.psutil",
|
|
"health_check.contrib.redis",
|
|
"health_check.contrib.db_heartbeat",
|
|
"health_check.contrib.mail",
|
|
"cacheops",
|
|
"django_celery_beat",
|
|
"django_celery_results",
|
|
"django_extensions",
|
|
"django_redis",
|
|
"django_summernote",
|
|
"widget_tweaks",
|
|
"mptt",
|
|
"rest_framework",
|
|
"rest_framework_simplejwt",
|
|
"rest_framework_simplejwt.token_blacklist",
|
|
"drf_spectacular_websocket",
|
|
"drf_spectacular",
|
|
"drf_spectacular_sidecar",
|
|
"django_json_widget",
|
|
"django_elasticsearch_dsl",
|
|
"djangoql",
|
|
"import_export",
|
|
"dbbackup",
|
|
"corsheaders",
|
|
"constance.backends.database",
|
|
"django_mailbox",
|
|
"graphene_django",
|
|
"channels",
|
|
"engine.core",
|
|
"engine.payments",
|
|
"engine.vibes_auth",
|
|
"engine.blog",
|
|
]
|
|
|
|
if DEBUG:
|
|
wn_app_index = INSTALLED_APPS.index("django.contrib.staticfiles") - 1
|
|
INSTALLED_APPS.insert(wn_app_index, "whitenoise.runserver_nostatic")
|
|
|
|
MIDDLEWARE: list[str] = [
|
|
"evibes.middleware.BlockInvalidHostMiddleware",
|
|
"evibes.middleware.RateLimitMiddleware",
|
|
"django_prometheus.middleware.PrometheusBeforeMiddleware",
|
|
"django.middleware.security.SecurityMiddleware",
|
|
"whitenoise.middleware.WhiteNoiseMiddleware",
|
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
"corsheaders.middleware.CorsMiddleware",
|
|
"evibes.middleware.CustomCommonMiddleware",
|
|
"django.middleware.csrf.CsrfViewMiddleware",
|
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
"django.contrib.messages.middleware.MessageMiddleware",
|
|
"django.contrib.admindocs.middleware.XViewMiddleware",
|
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
"evibes.middleware.CustomLocaleMiddleware",
|
|
"djangorestframework_camel_case.middleware.CamelCaseMiddleWare",
|
|
"django_prometheus.middleware.PrometheusAfterMiddleware",
|
|
]
|
|
|
|
TEMPLATES: list[dict[str, str | list[str | Path] | dict[str, str | list[str]] | Path | bool]] = [
|
|
{
|
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
"DIRS": [
|
|
BASE_DIR / "engine/vibes_auth/templates",
|
|
BASE_DIR / "engine/core/templates",
|
|
BASE_DIR / "engine/payments/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",
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
USE_I18N: bool = True
|
|
|
|
LOCALE_PATHS: tuple[Path, ...] = (
|
|
(BASE_DIR / "evibes/locale"),
|
|
(BASE_DIR / "engine/blog/locale"),
|
|
(BASE_DIR / "engine/core/locale"),
|
|
(BASE_DIR / "engine/payments/locale"),
|
|
(BASE_DIR / "engine/vibes_auth/locale"),
|
|
)
|
|
|
|
LANGUAGES: tuple[tuple[str, str], ...] = (
|
|
("ar-ar", "العربية"),
|
|
("cs-cz", "Česky"),
|
|
("da-dk", "Dansk"),
|
|
("de-de", "Deutsch"),
|
|
("en-gb", "English (British)"),
|
|
("en-us", "English (American)"),
|
|
("es-es", "Español"),
|
|
("fa-ir", "فارسی"),
|
|
("fr-fr", "Français"),
|
|
("he-il", "עברית"),
|
|
("hi-in", "हिंदी"),
|
|
("hr-hr", "Hrvatski"),
|
|
("id-id", "Bahasa Indonesia"),
|
|
("it-it", "Italiano"),
|
|
("ja-jp", "日本語"),
|
|
("kk-kz", "Қазақ"),
|
|
("ko-kr", "한국어"),
|
|
("nl-nl", "Nederlands"),
|
|
("no-no", "Norsk"),
|
|
("pl-pl", "Polska"),
|
|
("pt-br", "Português"),
|
|
("ro-ro", "Română"),
|
|
("ru-ru", "Русский"),
|
|
("sv-se", "Svenska"),
|
|
("th-th", "ไทย"),
|
|
("tr-tr", "Türkçe"),
|
|
("vi-vn", "Tiếng Việt"),
|
|
("zh-hans", "简体中文"),
|
|
)
|
|
|
|
LANGUAGE_CODE: str = "en-gb"
|
|
|
|
LANGUAGES_FLAGS: dict[str, str] = {
|
|
"ar-ar": "🇸🇦",
|
|
"cs-cz": "🇨🇿",
|
|
"da-dk": "🇩🇰",
|
|
"de-de": "🇩🇪",
|
|
"en-gb": "🇬🇧",
|
|
"en-us": "🇺🇸",
|
|
"es-es": "🇪🇸",
|
|
"fa-ir": "🇮🇷",
|
|
"fr-fr": "🇫🇷",
|
|
"he-il": "🇮🇱",
|
|
"hi-in": "🇮🇳",
|
|
"hr-hr": "🇭🇷",
|
|
"id-id": "🇮🇩",
|
|
"it-it": "🇮🇹",
|
|
"ja-jp": "🇯🇵",
|
|
"kk-kz": "🇰🇿",
|
|
"ko-kr": "🇰🇷",
|
|
"nl-nl": "🇳🇱",
|
|
"no-no": "🇳🇴",
|
|
"pl-pl": "🇵🇱",
|
|
"pt-br": "🇧🇷",
|
|
"ro-ro": "🇷🇴",
|
|
"ru-ru": "🇷🇺",
|
|
"sv-se": "🇸🇪",
|
|
"th-th": "🇹🇭",
|
|
"tr-tr": "🇹🇷",
|
|
"vi-vn": "🇻🇳",
|
|
"zh-hans": "🇨🇳",
|
|
}
|
|
|
|
CURRENCIES_BY_LANGUAGES: tuple[tuple[str, str], ...] = (
|
|
("ar-ar", "AED"),
|
|
("cs-cz", "CZK"),
|
|
("da-dk", "DKK"),
|
|
("de-de", "EUR"),
|
|
("en-gb", "GBP"),
|
|
("en-us", "USD"),
|
|
("es-es", "EUR"),
|
|
("fa-ir", "IRR"),
|
|
("fr-fr", "EUR"),
|
|
("he-il", "ILS"),
|
|
("hi-in", "INR"),
|
|
("hr-hr", "EUR"),
|
|
("id-id", "IDR"),
|
|
("it-it", "EUR"),
|
|
("ja-jp", "JPY"),
|
|
("kk-kz", "KZT"),
|
|
("ko-kr", "KRW"),
|
|
("nl-nl", "EUR"),
|
|
("no-no", "NOK"),
|
|
("pl-pl", "PLN"),
|
|
("pt-br", "BRL"),
|
|
("ro-ro", "RON"),
|
|
("ru-ru", "RUB"),
|
|
("sv-se", "SEK"),
|
|
("th-th", "THB"),
|
|
("tr-tr", "TRY"),
|
|
("vi-vn", "VND"),
|
|
("zh-hans", "CNY"),
|
|
)
|
|
|
|
CURRENCIES_WITH_SYMBOLS: tuple[tuple[str, str], ...] = (
|
|
("AED", "د.إ"),
|
|
("BRL", "R$"),
|
|
("CNY", "¥"),
|
|
("CZK", "Kč"),
|
|
("DKK", "kr"),
|
|
("EUR", "€"),
|
|
("GBP", "£"),
|
|
("IDR", "Rp"),
|
|
("ILS", "₪"),
|
|
("INR", "₹"),
|
|
("IRR", "﷼"),
|
|
("JPY", "¥"),
|
|
("KRW", "₩"),
|
|
("KZT", "₸"),
|
|
("NOK", "kr"),
|
|
("PLN", "zł"),
|
|
("RON", "lei"),
|
|
("RUB", "₽"),
|
|
("SEK", "kr"),
|
|
("THB", "฿"),
|
|
("TRY", "₺"),
|
|
("USD", "$"),
|
|
("VND", "₫"),
|
|
)
|
|
|
|
LANGUAGE_URL_OVERRIDES: dict[str, str] = {code.split("-")[0]: code for code, _ in LANGUAGES if "-" in code}
|
|
|
|
CURRENCY_CODE: str = dict(CURRENCIES_BY_LANGUAGES).get(LANGUAGE_CODE) # type: ignore[assignment]
|
|
|
|
MODELTRANSLATION_FALLBACK_LANGUAGES: tuple[str, ...] = (LANGUAGE_CODE, "en-us", "de-de")
|
|
|
|
ROOT_URLCONF: str = "evibes.urls"
|
|
|
|
WSGI_APPLICATION: str = "evibes.wsgi.application"
|
|
|
|
ASGI_APPLICATION: str = "evibes.asgi.application"
|
|
|
|
DEFAULT_AUTO_FIELD: str = "django.db.models.BigAutoField"
|
|
|
|
TIME_ZONE: str = getenv("TIME_ZONE", "Europe/London")
|
|
|
|
WHITENOISE_MANIFEST_STRICT: bool = False
|
|
|
|
STATIC_URL: str = f"https://api.{BASE_DOMAIN}/static/" if INITIALIZED else "static/"
|
|
STATIC_ROOT: Path = BASE_DIR / "static"
|
|
|
|
MEDIA_URL: str = f"https://api.{BASE_DOMAIN}/media/" if INITIALIZED else "media/"
|
|
MEDIA_ROOT: Path = BASE_DIR / "media"
|
|
|
|
AUTH_USER_MODEL: str = "vibes_auth.User"
|
|
|
|
AUTH_PASSWORD_VALIDATORS: list[dict[str, str | int]] = [
|
|
{
|
|
"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",
|
|
},
|
|
]
|
|
|
|
APPEND_SLASH: bool = True
|
|
|
|
REDIS_PASSWORD: str = getenv("REDIS_PASSWORD", default="")
|
|
REDIS_URL: str = f"redis://:{REDIS_PASSWORD}@redis:6379/0"
|
|
|
|
CHANNEL_LAYERS: dict[str, dict[str, str | dict[str, list[str]]]] = {
|
|
"default": {
|
|
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
|
"CONFIG": {
|
|
"hosts": [getenv("CHANNEL_REDIS_URL", REDIS_URL)],
|
|
},
|
|
},
|
|
}
|
|
|
|
INTERNAL_IPS: list[str] = [
|
|
"127.0.0.1",
|
|
]
|
|
|
|
if getenv("SENTRY_DSN"):
|
|
import sentry_sdk
|
|
from sentry_sdk.integrations.celery import CeleryIntegration
|
|
from sentry_sdk.integrations.django import DjangoIntegration
|
|
from sentry_sdk.integrations.logging import LoggingIntegration
|
|
from sentry_sdk.integrations.redis import RedisIntegration
|
|
from sentry_sdk.types import Event, Hint
|
|
|
|
def scrub_sensitive(data: dict[str, Any] | list[Any] | str) -> dict[str, Any] | list[Any] | str | None:
|
|
if isinstance(data, dict):
|
|
# noinspection PyShadowingNames
|
|
cleaned: dict[str, Any] = {}
|
|
for key, value in data.items():
|
|
if key.lower() in ("password", "confirm_password"):
|
|
cleaned[key] = "[FILTERED]"
|
|
else:
|
|
cleaned[key] = scrub_sensitive(value)
|
|
return cleaned
|
|
if isinstance(data, list):
|
|
return [scrub_sensitive(item) for item in data]
|
|
return data
|
|
|
|
def before_send(event: Event, hint: Hint) -> Event:
|
|
if hint:
|
|
pass
|
|
request = event.get("request", {})
|
|
data = request.get("data", {})
|
|
if data:
|
|
request["data"] = scrub_sensitive(data) # type: ignore [arg-type]
|
|
event["request"] = request
|
|
return event
|
|
|
|
ignore_errors: list[str] = []
|
|
|
|
sentry_sdk.init(
|
|
dsn=getenv("SENTRY_DSN"),
|
|
integrations=[
|
|
DjangoIntegration(),
|
|
LoggingIntegration(level=logging.INFO, event_level=logging.ERROR),
|
|
CeleryIntegration(),
|
|
RedisIntegration(),
|
|
],
|
|
environment="development" if DEBUG else "production",
|
|
release=f"evibes@{EVIBES_VERSION}",
|
|
traces_sample_rate=1.0 if DEBUG else 0.2,
|
|
profiles_sample_rate=1.0 if DEBUG else 0.1,
|
|
max_request_body_size="always",
|
|
before_send=before_send,
|
|
ignore_errors=ignore_errors,
|
|
debug=False,
|
|
)
|
|
|
|
SESSION_COOKIE_HTTPONLY: bool = True
|
|
CSRF_COOKIE_HTTPONLY: bool = True
|
|
LANGUAGE_COOKIE_HTTPONLY: bool = True
|
|
|
|
DATA_UPLOAD_MAX_NUMBER_FIELDS: int = 8888
|
|
|
|
RATELIMIT_EXCEPTION_CLASS: str = "evibes.utils.misc.RatelimitedError"
|
|
|
|
ADMINS: list[tuple[str, ...]] = [("Egor Gorbunov", "contact@fureunoir.com")]
|
|
|
|
STORAGES: dict[str, Any] = {
|
|
"default": {
|
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
|
},
|
|
"staticfiles": {
|
|
"BACKEND": "whitenoise.storage.CompressedStaticFilesStorage"
|
|
if DEBUG
|
|
else "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
|
},
|
|
}
|
|
|
|
if getenv("DBBACKUP_HOST") and getenv("DBBACKUP_USER") and getenv("DBBACKUP_PASS"):
|
|
dbbackup_server_type = getenv("DBBACKUP_TYPE", "sftp")
|
|
project_name = getenv("EVIBES_PROJECT_NAME", "evibes_common").lower().replace(" ", "_")
|
|
|
|
raw_path = getenv("DBBACKUP_PATH", f"/backups/{project_name}/")
|
|
cleaned = raw_path.strip("/")
|
|
remote_dir = f"{cleaned}/"
|
|
|
|
match dbbackup_server_type:
|
|
case "sftp":
|
|
STORAGES.update(
|
|
{
|
|
"dbbackup": {
|
|
"BACKEND": "storages.backends.sftpstorage.SFTPStorage",
|
|
"OPTIONS": {
|
|
"host": getenv("DBBACKUP_HOST"),
|
|
"root_path": f"/{remote_dir}",
|
|
"params": {
|
|
"username": getenv("DBBACKUP_USER"),
|
|
"password": getenv("DBBACKUP_PASS"),
|
|
"allow_agent": False,
|
|
"look_for_keys": False,
|
|
},
|
|
"interactive": False,
|
|
"file_mode": 0o600,
|
|
"dir_mode": 0o700,
|
|
},
|
|
}
|
|
}
|
|
)
|
|
|
|
case "ftp":
|
|
STORAGES.update(
|
|
{
|
|
"dbbackup": {
|
|
"BACKEND": "evibes.ftpstorage.AbsoluteFTPStorage",
|
|
"OPTIONS": {
|
|
"location": (
|
|
f"ftp://{getenv('DBBACKUP_USER')}:{getenv('DBBACKUP_PASS')}@{getenv('DBBACKUP_HOST')}:21/{raw_path}"
|
|
),
|
|
},
|
|
}
|
|
}
|
|
)
|
|
|
|
case _:
|
|
raise ImproperlyConfigured(f"Invalid DBBACKUP_TYPE: {dbbackup_server_type}")
|
|
else:
|
|
STORAGES.update(
|
|
{
|
|
"dbbackup": {
|
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
|
"OPTIONS": {
|
|
"location": "/app/backups/",
|
|
},
|
|
}
|
|
}
|
|
)
|
|
|
|
if name == "nt":
|
|
GDAL_LIBRARY_PATH = r"C:\OSGeo4W\bin\gdal311.dll"
|
|
GEOS_LIBRARY_PATH = r"C:\OSGeo4W\bin\geos_c.dll"
|