""" 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 from utils.admin import admin_url_generator # 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.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 ' } }, 'SWAGGER_UI_SETTINGS': { 'deepLinking': True, 'persistAuthorization': True, 'displayOperationId': True, }, } # 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 = reverse_lazy("home") # 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, )