229 lines
9.5 KiB
Python
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",
|
|
},
|
|
}
|