""" 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 ' } }, '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": _("Regions"), "icon": "public", "link": lambda request: admin_url_generator(request, "profiles_region_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='onboarding@resend.dev')