This commit is contained in:
2026-08-31 15:11:52 +03:30
parent 236002a87c
commit 463b812e3e
63 changed files with 3254 additions and 1476 deletions
+88 -70
View File
@@ -1,11 +1,10 @@
# config/settings.py
"""
تنظیمات جنگو برای بک‌اند «پیامک‌بان».
همه‌ی مقادیر حساس/محیطی از .env خوانده می‌شوند (با python-decouple) تا برای
بالا آوردن روی هر سروری فقط کافی باشد یک فایل .env ساخته شود؛ نیازی به تغییر
کد نیست.
تنظیمات جنگو برای پروژه «شاپ‌شیپ».
همه‌ی مقادیر حساس/محیطی از .env خوانده می‌شوند.
"""
import os
from datetime import timedelta
from pathlib import Path
@@ -13,24 +12,26 @@ 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
# ============================================
_env_path = BASE_DIR / ".env"
if not _env_path.exists():
raise RuntimeError(
f"فایل .env پیدا نشد ({_env_path}) — یک‌بار «cp .env.example .env» بزن و مقادیر واقعی را پر کن."
f"❌ فایل .env پیدا نشد ({_env_path}) — لطفاً یک فایل .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)
# ============================================
# امنیت و محیط اجرا
# ============================================
SECRET_KEY = config("SECRET_KEY")
DEBUG = False
ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="localhost,127.0.0.1", cast=Csv())
# -------------------- اپلیکیشن‌ها --------------------
# ============================================
# اپلیکیشن‌ها
# ============================================
DJANGO_APPS = [
"django.contrib.admin",
"django.contrib.auth",
@@ -60,6 +61,9 @@ LOCAL_APPS = [
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
# ============================================
# Middleware
# ============================================
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
@@ -75,6 +79,9 @@ MIDDLEWARE = [
ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"
# ============================================
# Templates
# ============================================
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
@@ -91,44 +98,43 @@ TEMPLATES = [
},
]
# -------------------- دیتابیس --------------------
# پیش‌فرض 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"),
}
# ============================================
# Database
# ============================================
DATABASES = {
"default": {
"ENGINE": config("DB_ENGINE", default="django.db.backends.sqlite3"),
"NAME": config("DB_NAME", default=BASE_DIR / "db.sqlite3"),
"USER": config("DB_USER", default=""),
"PASSWORD": config("DB_PASSWORD", default=""),
"HOST": config("DB_HOST", default=""),
"PORT": config("DB_PORT", default=""),
}
}
# ============================================
# Auth
# ============================================
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"},
]
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 = {
@@ -137,34 +143,30 @@ STORAGES = {
},
}
# -------------------- لندینگ‌پیج --------------------
# پوشه‌ی landing/ (HTML/CSS ساده، بدون build step) دقیقا از ریشه‌ی دامنه
# سرو می‌شود — یعنی landing/index.html روی «/»، landing/articles/index.html
# روی «/articles/» و غیره؛ همان WhiteNoise که استاتیک را سرو می‌کند، این
# را هم سرو می‌کند، بدون نیاز به هیچ view یا route دستی در urls.py.
# ============================================
# Landing Page
# ============================================
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
# ============================================
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 --------------------
# ============================================
# Django REST Framework
# ============================================
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
@@ -182,6 +184,9 @@ REST_FRAMEWORK = {
},
}
# ============================================
# Simple JWT
# ============================================
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)),
@@ -191,38 +196,51 @@ SIMPLE_JWT = {
"USER_ID_CLAIM": "user_id",
}
# -------------------- پیامک (OTP) --------------------
# فقط دو مقدار لازم است: نام قالب/پترن پیام و آدرس سرویس ارسال (توکن).
# اگر بخواهی خودت payload را با سرویس‌دهنده‌ات هماهنگ کنی، apps/account/send_sms.py را ببین.
SMS_PATTERN_NAME = config("SMS_PATTERN_NAME", default="")
# ============================================
# ✅ تنظیمات پیامک (فقط OTP با کاوه‌نگار)
# ============================================
KAVENEGAR_API_KEY = config("KAVENEGAR_API_KEY", default="")
SMS_PATTERN_NAME = config("SMS_PATTERN_NAME", default="shopshipotp")
# برای سازگاری با کدهای قدیمی (استفاده نمی‌شود)
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"},
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"apps.account": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": True,
},
"django": {
"handlers": ["console"],
"level": "INFO",
"propagate": True,
},
},
"root": {
"handlers": ["console"],
"level": "INFO",
},
}
}