Files
shopship/config/settings.py
T
2026-08-24 15:54:08 +03:30

229 lines
9.5 KiB
Python

# config/settings.py
"""
تنظیمات جنگو برای بک‌اند «پیامک‌بان».
همه‌ی مقادیر حساس/محیطی از .env خوانده می‌شوند (با python-decouple) تا برای
بالا آوردن روی هر سروری فقط کافی باشد یک فایل .env ساخته شود؛ نیازی به تغییر
کد نیست.
"""
from datetime import timedelta
from pathlib import Path
from decouple import Config, Csv, RepositoryEnv
BASE_DIR = Path(__file__).resolve().parent.parent
# مسیر .env را عمداً صریح می‌دهیم (نه decouple.config پیش‌فرض که بر اساس
# فریم صدازننده حدس می‌زند) — آن حدسِ مبتنی‌بر عمقِ استک، بسته به این‌که
# جنگو از کجا اجرا شود (manage.py check در برابر gunicorn در برابر
# manage.py shell)، می‌تواند مسیر اشتباه را پیدا کند و بی‌صدا از مقادیر
# default() استفاده کند — یعنی .env واقعی هیچ‌وقت خوانده نمی‌شود.
_env_path = BASE_DIR / ".env"
if not _env_path.exists():
raise RuntimeError(
f"فایل .env پیدا نشد ({_env_path}) — یک‌بار «cp .env.example .env» بزن و مقادیر واقعی را پر کن."
)
config = Config(RepositoryEnv(str(_env_path)))
# -------------------- امنیت و محیط اجرا --------------------
SECRET_KEY = config("SECRET_KEY", default="django-insecure-change-me-in-production")
DEBUG = config("DEBUG", default=False, cast=bool)
ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="localhost,127.0.0.1", cast=Csv())
# -------------------- اپلیکیشن‌ها --------------------
DJANGO_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
THIRD_PARTY_APPS = [
"corsheaders",
"rest_framework",
"rest_framework_simplejwt",
"rest_framework_simplejwt.token_blacklist",
]
LOCAL_APPS = [
"apps.account",
"apps.banker",
"apps.messaging",
"apps.bizapps",
"apps.billing",
"apps.notifications",
"apps.landing",
"apps.orders",
]
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"
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",
],
},
},
]
# -------------------- دیتابیس --------------------
# پیش‌فرض SQLite (بدون نیاز به .env برای اجرای محلی)؛ برای پروداکشن/داکر با
# گذاشتن DB_ENGINE=django.db.backends.postgresql در .env به PostgreSQL سوییچ کن.
if config("DB_ENGINE", default="django.db.backends.sqlite3") == "django.db.backends.sqlite3":
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
else:
DATABASES = {
"default": {
"ENGINE": config("DB_ENGINE"),
"NAME": config("DB_NAME"),
"USER": config("DB_USER"),
"PASSWORD": config("DB_PASSWORD"),
"HOST": config("DB_HOST", default="localhost"),
"PORT": config("DB_PORT", default="5432"),
}
}
AUTH_PASSWORD_VALIDATORS = [
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
]
AUTH_USER_MODEL = "account.User"
# -------------------- بین‌الملل --------------------
LANGUAGE_CODE = "fa-ir"
TIME_ZONE = "Asia/Tehran"
USE_I18N = True
USE_TZ = True
# -------------------- فایل‌های استاتیک --------------------
# استاتیک (CSS/JS پنل ادمین و غیره) را WhiteNoise middleware سرو می‌کند —
# چه DEBUG=True چه False، بدون نیاز به تغییری در urls.py.
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
# -------------------- لندینگ‌پیج --------------------
# پوشه‌ی landing/ (HTML/CSS ساده، بدون build step) دقیقا از ریشه‌ی دامنه
# سرو می‌شود — یعنی landing/index.html روی «/»، landing/articles/index.html
# روی «/articles/» و غیره؛ همان WhiteNoise که استاتیک را سرو می‌کند، این
# را هم سرو می‌کند، بدون نیاز به هیچ view یا route دستی در urls.py.
WHITENOISE_ROOT = BASE_DIR / "landing"
WHITENOISE_INDEX_FILE = True
# -------------------- فایل‌های رسانه (آپلودی) --------------------
# فعلا هیچ مدلی ImageField/FileField ندارد، ولی مسیرش را از الان آماده
# می‌کنیم. برخلاف استاتیک، WhiteNoise رسانه را سرو نمی‌کند — سروش در
# urls.py و فقط برای DEBUG=True است (dev)؛ در production باید Nginx/سرور
# وب مستقیم از MEDIA_ROOT سرو کند، نه خودِ جنگو.
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# -------------------- CORS --------------------
CORS_ALLOWED_ORIGINS = config("CORS_ALLOWED_ORIGINS", default="", cast=Csv())
CORS_ALLOW_ALL_ORIGINS = config("CORS_ALLOW_ALL_ORIGINS", default=False, cast=bool)
# دامنه‌های مجاز برای پنل ادمین (فرم‌های session-cookie/CSRF جنگو، برخلاف
# JWT API که این محدودیت را ندارد) — بدون این، پشت HTTPS/دامنه‌ی واقعی، لاگین
# ادمین با خطای «CSRF verification failed» رد می‌شود.
CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default="", cast=Csv())
# -------------------- Django REST Framework --------------------
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.ScopedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"otp_send": "5/hour",
"otp_verify": "10/hour",
"backup": "30/hour",
"messaging_send": "30/hour",
"billing_write": "30/hour",
},
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(days=config("JWT_ACCESS_LIFETIME_DAYS", default=1, cast=int)),
"REFRESH_TOKEN_LIFETIME": timedelta(days=config("JWT_REFRESH_LIFETIME_DAYS", default=30, cast=int)),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True,
"USER_ID_FIELD": "id",
"USER_ID_CLAIM": "user_id",
}
# -------------------- پیامک (OTP) --------------------
# فقط دو مقدار لازم است: نام قالب/پترن پیام و آدرس سرویس ارسال (توکن).
# اگر بخواهی خودت payload را با سرویس‌دهنده‌ات هماهنگ کنی، apps/account/send_sms.py را ببین.
SMS_PATTERN_NAME = config("SMS_PATTERN_NAME", default="")
SMS_TOKEN_URL = config("SMS_TOKEN_URL", default="")
# -------------------- ارسال گروهی چندکاناله (apps.messaging) --------------------
# هرکدام خالی بماند، همان کانال به‌جای ارسال خودکار needs_manual برمی‌گرداند
# (صف deep-link سمت اپ) — نه خطا، دقیقا مثل رفتار SMS_TOKEN_URL بالا.
# پیامک بالک با متن آزاد — جدا از SMS_PATTERN_NAME/SMS_TOKEN_URL که فقط OTP است.
KAVENEGAR_API_KEY = config("KAVENEGAR_API_KEY", default="")
KAVENEGAR_SENDER_LINE = config("KAVENEGAR_SENDER_LINE", default="")
# WhatsApp Business Cloud API رسمی (Meta) — نیاز به Template تاییدشده دارد.
WHATSAPP_ACCESS_TOKEN = config("WHATSAPP_ACCESS_TOKEN", default="")
WHATSAPP_PHONE_NUMBER_ID = config("WHATSAPP_PHONE_NUMBER_ID", default="")
WHATSAPP_TEMPLATE_NAME = config("WHATSAPP_TEMPLATE_NAME", default="")
# Bot API رسمی هرکدام — فقط برای مخاطبینی که قبلا با بات چت را شروع کرده‌اند.
TELEGRAM_BOT_TOKEN = config("TELEGRAM_BOT_TOKEN", default="")
BALE_BOT_TOKEN = config("BALE_BOT_TOKEN", default="")
EITAA_BOT_TOKEN = config("EITAA_BOT_TOKEN", default="")
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {"class": "logging.StreamHandler"},
},
"root": {
"handlers": ["console"],
"level": "INFO",
},
}