Postgres support (DB_* env), LoginEvent audit table, Choice.value 300, deploy.sh pg_dump backup

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 11:48:48 +03:30
parent e00f6b80eb
commit 042df25738
11 changed files with 261 additions and 38 deletions

View File

@@ -314,37 +314,60 @@ WSGI_APPLICATION = 'config.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
# timeout بالاتر از پیش‌فرض: وقتی دو کانکشنِ هم‌زمان (مثلِ تستِ
# concurrency واقعیِ campaigns) به قفلِ نوشتنِ SQLite برخورد
# می‌کنن، این باعث می‌شه کانکشنِ منتظر تا ۲۰ ثانیه صبر کنه به‌جای
# اینکه فوراً OperationalError('database table is locked') بده.
'OPTIONS': {
'timeout': 20,
# هر atomic() قفلِ نوشتن را از اول می‌گیرد (BEGIN IMMEDIATE).
# SQLite از SELECT ... FOR UPDATE پشتیبانی نمی‌کند و جنگو
# select_for_update را رویش بی‌صدا نادیده می‌گیرد؛ پس محافظ‌های
# «قفلِ ردیف» در پرداخت، تخفیف، قلب و سهمیه‌ی دستیار بدونِ این
# عملاً قفلی نداشتند. با IMMEDIATE کلِ تراکنش سریالی می‌شود (قفلِ
# کلِ دیتابیس، نه ردیف) — روی SQLite درشت‌تر ولی درست. هزینه:
# تراکنش‌ها پشتِ هم صف می‌کشند؛ فراخوانی‌های شبکه‌ای (AI، زرین‌پال)
# عمداً بیرون از atomic() هستند. بعد از Postgres این را بردارید.
'transaction_mode': 'IMMEDIATE',
},
# کانکشنِ DB به‌ازای هر request باز/بسته نشه؛ تا این تعداد ثانیه
# persistent بمونه. با SQLite بی‌اثرِ عملی ولی بی‌ضرره؛ بعد از
# مهاجرت به Postgres جلوی هزینه‌ی TCP+auth در هر request رو می‌گیره.
'CONN_MAX_AGE': int(os.environ.get('DB_CONN_MAX_AGE', '60')),
# تست‌ها روی فایل، مثلِ پروداکشن. دیتابیسِ پیش‌فرضِ تست در حافظه با
# shared-cache است که قفلش جدول‌به‌جدول و بی‌انتظار است («table is
# locked») — رفتاری که روی فایلِ واقعی پیش نمی‌آید؛ تستِ هم‌زمانیِ
# campaigns با BEGIN IMMEDIATE فقط همین‌جا می‌افتاد.
'TEST': {'NAME': str(BASE_DIR / '.test-db.sqlite3')},
# Postgres وقتی DB_NAME ست باشد؛ وگرنه SQLite (وضعِ فعلیِ سرور تا روزِ انتقال).
# ست‌بودنِ DB_NAME با ناقص‌بودنِ بقیه (کاربر/رمز) عمداً خطا می‌دهد، نه اتصالِ بی‌رمز.
DB_NAME = os.environ.get('DB_NAME', '')
if DB_NAME:
_missing = [k for k in ('DB_USER', 'DB_PASSWORD') if not os.environ.get(k)]
if _missing:
raise RuntimeError('DB_NAME ست شده ولی %s خالی است.' % ', '.join(_missing))
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': DB_NAME,
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': os.environ.get('DB_HOST', '127.0.0.1'),
'PORT': os.environ.get('DB_PORT', '5432'),
'CONN_MAX_AGE': int(os.environ.get('DB_CONN_MAX_AGE', '60')),
# قفلِ ردیف روی Postgres واقعی است (select_for_update)؛ transaction_mode
# فقط برای SQLite بود. با تست‌های هم‌زمانی روی Postgres سنجیده می‌شود.
}
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
# timeout بالاتر از پیش‌فرض: وقتی دو کانکشنِ هم‌زمان (مثلِ تستِ
# concurrency واقعیِ campaigns) به قفلِ نوشتنِ SQLite برخورد
# می‌کنن، این باعث می‌شه کانکشنِ منتظر تا ۲۰ ثانیه صبر کنه به‌جای
# اینکه فوراً OperationalError('database table is locked') بده.
'OPTIONS': {
'timeout': 20,
# هر atomic() قفلِ نوشتن را از اول می‌گیرد (BEGIN IMMEDIATE).
# SQLite از SELECT ... FOR UPDATE پشتیبانی نمی‌کند و جنگو
# select_for_update را رویش بی‌صدا نادیده می‌گیرد؛ پس محافظ‌های
# «قفلِ ردیف» در پرداخت، تخفیف، قلب و سهمیه‌ی دستیار بدونِ این
# عملاً قفلی نداشتند. با IMMEDIATE کلِ تراکنش سریالی می‌شود (قفلِ
# کلِ دیتابیس، نه ردیف) — روی SQLite درشت‌تر ولی درست. هزینه:
# تراکنش‌ها پشتِ هم صف می‌کشند؛ فراخوانی‌های شبکه‌ای (AI، زرین‌پال)
# عمداً بیرون از atomic() هستند. بعد از Postgres این را بردارید.
'transaction_mode': 'IMMEDIATE',
},
# کانکشنِ DB به‌ازای هر request باز/بسته نشه؛ تا این تعداد ثانیه
# persistent بمونه. با SQLite بی‌اثرِ عملی ولی بی‌ضرره؛ بعد از
# مهاجرت به Postgres جلوی هزینه‌ی TCP+auth در هر request رو می‌گیره.
'CONN_MAX_AGE': int(os.environ.get('DB_CONN_MAX_AGE', '60')),
# تست‌ها روی فایل، مثلِ پروداکشن. دیتابیسِ پیش‌فرضِ تست در حافظه با
# shared-cache است که قفلش جدول‌به‌جدول و بی‌انتظار است («table is
# locked») — رفتاری که روی فایلِ واقعی پیش نمی‌آید؛ تستِ هم‌زمانیِ
# campaigns با BEGIN IMMEDIATE فقط همین‌جا می‌افتاد.
'TEST': {'NAME': str(BASE_DIR / '.test-db.sqlite3')},
}
}
# In production a shared cache is required for rate limiting to work across
# workers/instances; falls back to LocMemCache only when DEBUG=True.