""" Base settings for PX360 project. This file contains settings common to all environments. """ import os from pathlib import Path import environ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent # Initialize environment variables env = environ.Env( DEBUG=(bool, False), ALLOWED_HOSTS=(list, []), ) # Read .env file if it exists environ.Env.read_env(os.path.join(BASE_DIR, '.env')) # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = env('SECRET_KEY', default='django-insecure-change-this-in-production') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env('DEBUG') ALLOWED_HOSTS = env('ALLOWED_HOSTS') # Application definition DJANGO_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] THIRD_PARTY_APPS = [ 'rest_framework', 'rest_framework_simplejwt', 'django_filters', 'drf_spectacular', 'django_celery_beat', ] LOCAL_APPS = [ 'apps.core', 'apps.accounts', 'apps.organizations', 'apps.journeys', 'apps.surveys', 'apps.complaints', 'apps.feedback', 'apps.callcenter', 'apps.social', 'apps.px_action_center', 'apps.analytics', 'apps.physicians', 'apps.projects', 'apps.integrations', 'apps.notifications', 'apps.ai_engine', 'apps.dashboard', 'apps.appreciation', 'apps.observations', ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', # i18n support 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'config.urls' 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', 'apps.core.context_processors.sidebar_counts', ], }, }, ] WSGI_APPLICATION = 'config.wsgi.application' # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases DATABASES = { 'default': env.db('DATABASE_URL', default='postgresql://px360:px360@localhost:5432/px360') } # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Custom User Model AUTH_USER_MODEL = 'accounts.User' # Internationalization # https://docs.djangoproject.com/en/5.0/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Riyadh' USE_I18N = True USE_TZ = True # Languages supported (Arabic and English) LANGUAGES = [ ('en', 'English'), ('ar', 'Arabic'), ] # Locale paths for translation files LOCALE_PATHS = [ BASE_DIR / 'locale', ] # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ STATIC_URL = '/static/' STATIC_ROOT = BASE_DIR / 'staticfiles' STATICFILES_DIRS = [BASE_DIR / 'static'] # WhiteNoise configuration STORAGES = { "default": { "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": { "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", }, } # Media files MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # Django REST Framework REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', 'rest_framework.authentication.SessionAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], 'DEFAULT_FILTER_BACKENDS': [ 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.SearchFilter', 'rest_framework.filters.OrderingFilter', ], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 50, 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', } # JWT Settings from datetime import timedelta SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(hours=1), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'ROTATE_REFRESH_TOKENS': True, 'BLACKLIST_AFTER_ROTATION': True, 'UPDATE_LAST_LOGIN': True, } # DRF Spectacular (OpenAPI/Swagger) SPECTACULAR_SETTINGS = { 'TITLE': 'PX360 API', 'DESCRIPTION': 'Patient Experience 360 Management System API', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, } # Celery Configuration CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='redis://localhost:6379/0') CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND', default='redis://localhost:6379/0') CELERY_ACCEPT_CONTENT = ['json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = TIME_ZONE CELERY_TASK_TRACK_STARTED = True CELERY_TASK_TIME_LIMIT = 30 * 60 # 30 minutes # Celery Beat Schedule CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' # Logging Configuration LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 'style': '{', }, 'simple': { 'format': '{levelname} {message}', 'style': '{', }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { 'level': 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'verbose' }, 'file': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': BASE_DIR / 'logs' / 'px360.log', 'maxBytes': 1024 * 1024 * 15, # 15MB 'backupCount': 10, 'formatter': 'verbose', }, 'integration_file': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': BASE_DIR / 'logs' / 'integrations.log', 'maxBytes': 1024 * 1024 * 15, # 15MB 'backupCount': 10, 'formatter': 'verbose', }, }, 'loggers': { 'django': { 'handlers': ['console', 'file'], 'level': 'INFO', 'propagate': False, }, 'apps': { 'handlers': ['console', 'file'], 'level': 'INFO', 'propagate': False, }, 'apps.integrations': { 'handlers': ['console', 'integration_file'], 'level': 'INFO', 'propagate': False, }, }, } # Create logs directory if it doesn't exist LOGS_DIR = BASE_DIR / 'logs' LOGS_DIR.mkdir(exist_ok=True) # PX360 Business Configuration # These will be overridden by database configurations but provide defaults # Survey Configuration SURVEY_NEGATIVE_THRESHOLD = 3 # Scores below this trigger actions (out of 5) SURVEY_TOKEN_EXPIRY_DAYS = 30 # SLA Configuration (in hours) SLA_DEFAULTS = { 'complaint': { 'low': 72, 'medium': 48, 'high': 24, 'critical': 12, }, 'action': { 'low': 120, 'medium': 72, 'high': 48, 'critical': 24, }, } # Notification Configuration NOTIFICATION_CHANNELS = { 'sms': { 'enabled': env.bool('SMS_ENABLED', default=False), 'provider': env('SMS_PROVIDER', default='console'), }, 'whatsapp': { 'enabled': env.bool('WHATSAPP_ENABLED', default=False), 'provider': env('WHATSAPP_PROVIDER', default='console'), }, 'email': { 'enabled': env.bool('EMAIL_ENABLED', default=True), 'provider': env('EMAIL_PROVIDER', default='console'), }, } # Email Configuration EMAIL_BACKEND = env('EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend') EMAIL_HOST = env('EMAIL_HOST', default='localhost') EMAIL_PORT = env.int('EMAIL_PORT', default=587) EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', default=True) EMAIL_HOST_USER = env('EMAIL_HOST_USER', default='') EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD', default='') DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='noreply@px360.sa') # Security Settings SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = 'DENY'