You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

395 lines
13 KiB

"""
Django base settings for project template.
Built on Django 5.0+, Django REST Framework, Celery, and Django Unfold.
"""
import os
from pathlib import Path
from django.templatetags.static import static
from django.urls import reverse_lazy
import environ
from django.utils.translation import gettext_lazy as _
import sentry_sdk
# Build paths inside the project: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = environ.Env()
env_file = os.path.join(BASE_DIR, '.env')
if os.path.exists(env_file):
environ.Env.read_env(env_file)
# Core Security & Debug
SECRET_KEY = env('DJANGO_SECRET_KEY', default='django-insecure-template-secret-key-replace-in-production')
DEBUG = env.bool('DJANGO_DEBUG', default=True)
ALLOWED_HOSTS = [host.strip() for host in env('DJANGO_ALLOWED_HOSTS', default='127.0.0.1,localhost,*').split(',') if host.strip()]
CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in env('DJANGO_TRUSTED_ORIGINS', default='http://localhost:8000,http://127.0.0.1:8000').split(',') if origin.strip()]
X_FRAME_OPTIONS = 'SAMEORIGIN'
# Application definition
LOCAL_APPS = [
'apps.account.apps.AccountConfig',
'apps.profiles.apps.ProfilesConfig',
'apps.geo_map.apps.GeoMapConfig',
'apps.cms.apps.CMSConfig',
'apps.projects.apps.ProjectsConfig',
'apps.chat.apps.ChatConfig',
'apps.events.apps.EventsConfig',
'apps.meetings.apps.MeetingsConfig',
'apps.api.apps.ApiConfig',
'dynamic_preferences',
]
THIRD_PARTY_APPS = [
'rest_framework',
'rest_framework.authtoken',
'rest_framework_simplejwt',
'drf_spectacular',
'drf_spectacular_sidecar',
'drf_yasg',
'phonenumber_field',
'corsheaders',
'django_filters',
'django_celery_beat',
]
INSTALLED_APPS = [
"unfold",
"unfold.contrib.filters",
"unfold.contrib.forms",
"unfold.contrib.inlines",
"whitenoise.runserver_nostatic",
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django.contrib.sitemaps',
*THIRD_PARTY_APPS,
*LOCAL_APPS,
]
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'apps.account.custom_user_login.CustomLoginBackend',
]
AUTH_USER_MODEL = "account.User"
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.middleware.gzip.GZipMiddleware',
"whitenoise.middleware.WhiteNoiseMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'config.language_code_middleware.language_middleware',
'config.enhanced_auth_middleware.enhanced_auth_middleware',
]
ROOT_URLCONF = 'config.urls'
APPEND_SLASH = True
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR / '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',
'django.template.context_processors.i18n',
"utils.admin.variables",
],
},
},
]
WSGI_APPLICATION = 'config.wsgi.application'
ASGI_APPLICATION = 'config.asgi.application'
# Database configuration
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('POSTGRES_DB', default='app_db'),
'USER': env('POSTGRES_USER', default='postgres'),
'PASSWORD': env('POSTGRES_PASSWORD', default='postgres'),
'HOST': env('POSTGRES_HOST', default='postgres'),
'PORT': env('POSTGRES_PORT', default='5432'),
'ATOMIC_REQUESTS': True,
},
}
# Redis & Caching
REDIS_URL = env('REDIS_URL', default='redis://redis:6379/0')
CACHES = {
'default': {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_URL,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# Celery Task Queue
CELERY_BROKER_URL = REDIS_URL
CELERY_RESULT_BACKEND = REDIS_URL
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TIMEZONE = 'UTC'
CELERY_BROKER_TRANSPORT = 'redis'
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
'cleanup_expired_tokens_daily': {
'task': 'cleanup_expired_tokens_task',
'schedule': crontab(minute=0, hour=3),
},
}
# Password validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 6,
}
},
]
# Django REST Framework
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'utils.pagination.StandardResultsSetPagination',
'PAGE_SIZE': 20,
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler',
}
# SimpleJWT Authentication Settings
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
}
# drf-spectacular OpenAPI 3.0 Documentation Settings
SPECTACULAR_SETTINGS = {
'TITLE': 'API سامانه تعاملات بین‌الملل',
'DESCRIPTION': 'مستندات نقاط پایانی سامانه تعاملات بین‌الملل آستان قدس رضوی (فاز دوم)',
'VERSION': '2.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'COMPONENT_SPLIT_REQUEST': True,
'SWAGGER_UI_DIST': 'SIDECAR',
'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
'REDOC_DIST': 'SIDECAR',
'SECURITY': [{
'jwtAuth': []
}],
'SECURITY_SCHEMES': {
'jwtAuth': {
'type': 'apiKey',
'in': 'header',
'name': 'Authorization',
'description': 'توکن JWT را به این صورت وارد کنید: Bearer <JWT_TOKEN>'
}
},
'SWAGGER_UI_SETTINGS': {
'deepLinking': True,
'persistAuthorization': True,
'displayOperationId': True,
},
'ENUM_NAME_OVERRIDES': {
'VerificationDocumentStatusEnum': 'apps.account.models.verification.VerificationDocument.Status',
'CMSPostStatusEnum': 'apps.cms.models.post.Post.Status',
'CMSPostLanguageEnum': 'apps.cms.models.post.Post.LanguageChoices',
'CMSCommentStatusEnum': 'apps.cms.models.post.PostComment.Status',
'ProjectStatusEnum': 'apps.projects.models.project.Project.Status',
'MeetingStatusEnum': 'apps.meetings.models.meeting.MeetingStatus',
'EventRegistrationStatusEnum': 'apps.events.models.event.RegistrationStatus',
},
}
# Centrifugo Real-Time Messaging Settings
CENTRIFUGO_API_URL = env('CENTRIFUGO_API_URL', default='http://centrifugo:8000/api')
CENTRIFUGO_SECRET = env('CENTRIFUGO_SECRET', default='super_secret_centrifugo_token_key_hmac_256')
CENTRIFUGO_API_KEY = env('CENTRIFUGO_API_KEY', default='centrifugo_internal_api_access_key')
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [
'http://localhost:5173',
'http://127.0.0.1:5173',
'http://localhost:3000',
'http://127.0.0.1:3000',
'https://shiadeepconnect.org',
]
# Internationalization
LANGUAGE_CODE = 'en'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
('fa', _('Persian')),
('en', _('English')),
('ar', _('Arabic')),
('ur', _('Urdu')),
('ru', _('Russian')),
]
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
]
# Static & Media Files
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_ROOT = BASE_DIR / 'media'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
PHONENUMBER_DEFAULT_REGION = env("PHONENUMBER_DEFAULT_REGION", default="US")
PHONENUMBER_DB_FORMAT = 'INTERNATIONAL'
PHONENUMBER_DEFAULT_FORMAT = 'INTERNATIONAL'
# Sessions
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
LOGIN_URL = "admin:login"
LOGIN_REDIRECT_URL = "/admin/"
# Unfold Admin Interface Settings
UNFOLD = {
"SITE_TITLE": _("Admin Portal"),
"SITE_HEADER": _("Admin Portal"),
"SITE_SUBHEADER": _("Management Console"),
"SITE_SYMBOL": "speed",
"ALLOW_UNICODE_SLUGS": True,
"SHOW_HISTORY": True,
"SHOW_LANGUAGES": True,
"ENVIRONMENT": "utils.environment_callback",
"DASHBOARD_CALLBACK": "utils.admin.dashboard_callback",
"SHOW_BACK_BUTTON": True,
"THEME": "light",
"STYLES": [
lambda request: static("css/styles.css"),
],
"TABS": [
{
"page": "accounts",
"models": ["account.user", "auth.group"],
"items": [
{
"title": _("Users"),
"icon": "people",
"link": lambda request: admin_url_generator(request, "account_user_changelist"),
},
{
"title": _("Groups"),
"icon": "shield",
"link": lambda request: admin_url_generator(request, "auth_group_changelist"),
},
],
},
],
"SIDEBAR": {
"show_search": True,
"show_all_applications": True,
"navigation": [
{
"title": _("Overview"),
"separator": False,
"items": [
{
"title": _("Dashboard"),
"icon": "dashboard",
"link": lambda request: admin_url_generator(request, "index"),
},
],
},
{
"title": _("User Management"),
"separator": True,
"items": [
{
"title": _("All Users"),
"icon": "people",
"link": lambda request: admin_url_generator(request, "account_user_changelist"),
},
{
"title": _("Groups & Permissions"),
"icon": "shield",
"link": lambda request: admin_url_generator(request, "auth_group_changelist"),
"permission": lambda request: request.user.is_staff,
},
],
},
{
"title": _("System Settings"),
"separator": True,
"items": [
{
"title": _("Global Preferences"),
"icon": "tune",
"link": lambda request: admin_url_generator(request, "dynamic_preferences_globalpreferencemodel_changelist"),
},
{
"title": _("App Versions"),
"icon": "system_update",
"link": lambda request: admin_url_generator(request, "api_appversion_changelist"),
},
{
"title": _("Support Inquiries"),
"icon": "mail",
"link": lambda request: admin_url_generator(request, "api_supportmessage_changelist"),
},
],
},
],
},
}
# Sentry SDK Error Tracking (Configurable via ENV)
SENTRY_DSN = env('SENTRY_DSN', default='')
if SENTRY_DSN:
sentry_sdk.init(
dsn=SENTRY_DSN,
send_default_pii=True,
)