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.
 
 

729 lines
28 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
from corsheaders.defaults import default_headers
# 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,http://localhost:8080,http://127.0.0.1:8080,http://localhost:5173,http://127.0.0.1:5173,http://localhost:3000,http://127.0.0.1:3000').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.lms.apps.LMSConfig',
'apps.tickets.apps.TicketsConfig',
'apps.dynamic_forms.apps.DynamicFormsConfig',
'apps.donations.apps.DonationsConfig',
'apps.diplomacy.apps.DiplomacyConfig',
'apps.analytics.apps.AnalyticsConfig',
'apps.search.apps.SearchConfig',
'apps.api.apps.ApiConfig',
'dynamic_preferences',
]
THIRD_PARTY_APPS = [
'rest_framework',
'rest_framework.authtoken',
'rest_framework_simplejwt',
'drf_spectacular',
'drf_spectacular_sidecar',
'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 = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.middleware.gzip.GZipMiddleware',
"whitenoise.middleware.WhiteNoiseMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware',
'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',
'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',
'EventCategoryEnum': 'apps.events.models.event.EventCategory',
'LMSCourseCategoryEnum': 'apps.lms.models.course.CourseCategory',
'LMSCourseLevelEnum': 'apps.lms.models.course.CourseLevel',
'ContentLanguageEnum': 'apps.lms.models.course.CourseLanguage',
'LMSLessonTypeEnum': 'apps.lms.models.course.Lesson.LessonType',
'TicketCategoryEnum': 'apps.tickets.models.ticket.TicketCategory',
'TicketPriorityEnum': 'apps.tickets.models.ticket.TicketPriority',
'TicketStatusEnum': 'apps.tickets.models.ticket.TicketStatus',
'WorkflowStatusEnum': 'apps.dynamic_forms.models.form.WorkflowStatus',
'DonationCampaignCategoryEnum': 'apps.donations.models.donation.CampaignCategory',
'DonationCampaignPriorityEnum': 'apps.donations.models.donation.CampaignPriority',
'PaymentStatusEnum': 'apps.donations.models.donation.PaymentStatus',
'DiplomacyReportStatusEnum': 'apps.diplomacy.models.report.ReportStatus',
'InstitutionTypeEnum': 'apps.profiles.models.institution.Institution.InstitutionType',
'SearchEntityTypeEnum': 'apps.search.serializers.SearchEntityType',
},
}
# 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:8080',
'http://127.0.0.1:8080',
'http://localhost:5173',
'http://127.0.0.1:5173',
'http://localhost:3000',
'http://127.0.0.1:3000',
'https://shiadeepconnect.org',
]
CORS_ALLOW_HEADERS = list(default_headers) + [
'accept-language',
'content-disposition',
'x-csrftoken',
'x-requested-with',
]
# 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/"
def admin_url_generator(request, url_name):
from django.urls import reverse
try:
return reverse(f"admin:{url_name}")
except Exception:
return "#"
# 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"),
],
"SCRIPTS": [
lambda request: static("js/admin_row_click.js"),
],
"TABS": [
{
"page": "accounts",
"models": ["account.user", "account.role", "auth.group"],
"items": [
{
"title": _("Users"),
"icon": "people",
"link": lambda request: admin_url_generator(request, "account_user_changelist"),
},
{
"title": _("Roles & Permissions"),
"icon": "admin_panel_settings",
"link": lambda request: admin_url_generator(request, "account_role_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 & Access Management"),
"separator": True,
"items": [
{
"title": _("All Users"),
"icon": "person",
"link": lambda request: admin_url_generator(request, "account_user_changelist"),
},
{
"title": _("Roles & Permissions"),
"icon": "admin_panel_settings",
"link": lambda request: admin_url_generator(request, "account_role_changelist"),
"permission": lambda request: request.user.can_access_admin_panel(),
},
{
"title": _("Django Groups"),
"icon": "shield",
"link": lambda request: admin_url_generator(request, "auth_group_changelist"),
"permission": lambda request: request.user.is_staff,
},
{
"title": _("Verification Documents"),
"icon": "verified",
"link": lambda request: admin_url_generator(request, "account_verificationdocument_changelist"),
},
{
"title": _("Login History"),
"icon": "history",
"link": lambda request: admin_url_generator(request, "account_loginhistory_changelist"),
},
{
"title": _("Location History"),
"icon": "location_on",
"link": lambda request: admin_url_generator(request, "account_locationhistory_changelist"),
},
{
"title": _("User Notifications"),
"icon": "notifications",
"link": lambda request: admin_url_generator(request, "account_notification_changelist"),
},
{
"title": _("Notification Templates"),
"icon": "mark_email_read",
"link": lambda request: admin_url_generator(request, "account_notificationtemplate_changelist"),
},
],
},
{
"title": _("Institutions & Profiles"),
"separator": True,
"items": [
{
"title": _("Institutions"),
"icon": "account_balance",
"link": lambda request: admin_url_generator(request, "profiles_institution_changelist"),
},
{
"title": _("Institution Members"),
"icon": "badge",
"link": lambda request: admin_url_generator(request, "profiles_institutionmember_changelist"),
},
{
"title": _("Historical Timelines"),
"icon": "timeline",
"link": lambda request: admin_url_generator(request, "profiles_institutiontimeline_changelist"),
},
{
"title": _("Media Gallery"),
"icon": "perm_media",
"link": lambda request: admin_url_generator(request, "profiles_institutionmedia_changelist"),
},
{
"title": _("Followers"),
"icon": "group_add",
"link": lambda request: admin_url_generator(request, "profiles_institutionfollower_changelist"),
},
],
},
{
"title": _("Content Management (CMS)"),
"separator": True,
"items": [
{
"title": _("Posts & Articles"),
"icon": "article",
"link": lambda request: admin_url_generator(request, "cms_post_changelist"),
},
{
"title": _("Categories"),
"icon": "category",
"link": lambda request: admin_url_generator(request, "cms_postcategory_changelist"),
},
{
"title": _("Tags"),
"icon": "label",
"link": lambda request: admin_url_generator(request, "cms_posttag_changelist"),
},
{
"title": _("Comments"),
"icon": "comment",
"link": lambda request: admin_url_generator(request, "cms_postcomment_changelist"),
},
{
"title": _("Likes"),
"icon": "thumb_up",
"link": lambda request: admin_url_generator(request, "cms_postlike_changelist"),
},
{
"title": _("Media Assets"),
"icon": "photo_library",
"link": lambda request: admin_url_generator(request, "cms_mediaasset_changelist"),
},
],
},
{
"title": _("Projects & Kanban"),
"separator": True,
"items": [
{
"title": _("Projects"),
"icon": "assignment",
"link": lambda request: admin_url_generator(request, "projects_project_changelist"),
},
{
"title": _("Kanban Tasks"),
"icon": "task_alt",
"link": lambda request: admin_url_generator(request, "projects_kanbantask_changelist"),
},
{
"title": _("Kanban Columns"),
"icon": "view_column",
"link": lambda request: admin_url_generator(request, "projects_kanbancolumn_changelist"),
},
{
"title": _("Project Documents"),
"icon": "description",
"link": lambda request: admin_url_generator(request, "projects_projectdocument_changelist"),
},
],
},
{
"title": _("Events & Calendar"),
"separator": True,
"items": [
{
"title": _("Events"),
"icon": "event",
"link": lambda request: admin_url_generator(request, "events_event_changelist"),
},
{
"title": _("Event Registrations"),
"icon": "how_to_reg",
"link": lambda request: admin_url_generator(request, "events_eventregistration_changelist"),
},
],
},
{
"title": _("Meetings & Scheduling"),
"separator": True,
"items": [
{
"title": _("Meeting Requests"),
"icon": "video_camera_front",
"link": lambda request: admin_url_generator(request, "meetings_meetingrequest_changelist"),
},
],
},
{
"title": _("Learning Management (LMS)"),
"separator": True,
"items": [
{
"title": _("Courses"),
"icon": "school",
"link": lambda request: admin_url_generator(request, "lms_course_changelist"),
},
{
"title": _("Course Modules"),
"icon": "view_module",
"link": lambda request: admin_url_generator(request, "lms_coursemodule_changelist"),
},
{
"title": _("Lessons"),
"icon": "menu_book",
"link": lambda request: admin_url_generator(request, "lms_lesson_changelist"),
},
{
"title": _("Quiz Questions"),
"icon": "quiz",
"link": lambda request: admin_url_generator(request, "lms_quizquestion_changelist"),
},
{
"title": _("Student Enrollments"),
"icon": "co_present",
"link": lambda request: admin_url_generator(request, "lms_courseenrollment_changelist"),
},
{
"title": _("Lesson Progress"),
"icon": "trending_up",
"link": lambda request: admin_url_generator(request, "lms_lessonprogress_changelist"),
},
{
"title": _("Digital Certificates"),
"icon": "card_membership",
"link": lambda request: admin_url_generator(request, "lms_digitalcertificate_changelist"),
},
],
},
{
"title": _("Ticketing & Support"),
"separator": True,
"items": [
{
"title": _("Support Tickets"),
"icon": "confirmation_number",
"link": lambda request: admin_url_generator(request, "tickets_ticket_changelist"),
},
{
"title": _("Ticket Messages"),
"icon": "forum",
"link": lambda request: admin_url_generator(request, "tickets_ticketmessage_changelist"),
},
{
"title": _("Support Inquiries"),
"icon": "contact_support",
"link": lambda request: admin_url_generator(request, "api_supportmessage_changelist"),
},
],
},
{
"title": _("Dynamic Forms & Surveys"),
"separator": True,
"items": [
{
"title": _("Form Definitions"),
"icon": "dynamic_form",
"link": lambda request: admin_url_generator(request, "dynamic_forms_formdefinition_changelist"),
},
{
"title": _("Form Responses"),
"icon": "checklist",
"link": lambda request: admin_url_generator(request, "dynamic_forms_formresponse_changelist"),
},
],
},
{
"title": _("Donations & Financials"),
"separator": True,
"items": [
{
"title": _("Donation Campaigns"),
"icon": "volunteer_activism",
"link": lambda request: admin_url_generator(request, "donations_donationcampaign_changelist"),
},
{
"title": _("Transactions"),
"icon": "payments",
"link": lambda request: admin_url_generator(request, "donations_donationtransaction_changelist"),
},
],
},
{
"title": _("Diplomacy & Field Reports"),
"separator": True,
"items": [
{
"title": _("Representative Reports"),
"icon": "public",
"link": lambda request: admin_url_generator(request, "diplomacy_representativereport_changelist"),
},
],
},
{
"title": _("Real-Time Communication"),
"separator": True,
"items": [
{
"title": _("Chat Rooms"),
"icon": "chat",
"link": lambda request: admin_url_generator(request, "chat_chatroom_changelist"),
},
{
"title": _("Chat Messages"),
"icon": "message",
"link": lambda request: admin_url_generator(request, "chat_chatmessage_changelist"),
},
],
},
{
"title": _("System & Automation"),
"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": _("Celery Periodic Tasks"),
"icon": "schedule",
"link": lambda request: admin_url_generator(request, "django_celery_beat_periodictask_changelist"),
},
{
"title": _("Crontab Schedules"),
"icon": "alarm",
"link": lambda request: admin_url_generator(request, "django_celery_beat_crontabschedule_changelist"),
},
{
"title": _("Interval Schedules"),
"icon": "timer",
"link": lambda request: admin_url_generator(request, "django_celery_beat_intervalschedule_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,
)
# Resend Email Service
RESEND_API_KEY = env('RESEND_API_KEY', default='')
RESEND_FROM_EMAIL = env('RESEND_FROM_EMAIL', default='[email protected]')