schon/evibes/settings/base.py
Egor fureunoir Gorbunov 376c73ba26 Features: 1) Add tab support for inline admin classes; 2) Introduce new settings for taskboard URL and support contact;
Fixes: 1) Remove redundant imports from admin.py;

Extra: 1) Update inline classes to inherit from TabularInline; 2) Add unfold.contrib modules to INSTALLED_APPS; 3) Reorder imports in admin.py for consistency.
2025-11-15 02:29:23 +03:00

492 lines
14 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, 11, 9)
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("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",
"constance",
"unfold",
"unfold.contrib.filters",
"unfold.contrib.forms",
"unfold.contrib.inlines",
"unfold.contrib.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",
"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
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"
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", ""),
("DKK", "kr"),
("EUR", ""),
("GBP", "£"),
("IDR", "Rp"),
("ILS", ""),
("INR", ""),
("IRR", ""),
("JPY", "¥"),
("KRW", ""),
("KZT", ""),
("NOK", "kr"),
("PLN", ""),
("RON", "lei"),
("RUB", ""),
("SEK", "kr"),
("THB", "฿"),
("TRY", ""),
("USD", "$"),
("VND", ""),
)
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"