import os from datetime import timedelta from pathlib import Path from dotenv import load_dotenv load_dotenv() BASE_DIR = Path(__file__).resolve().parent.parent.parent SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "1", "yes") DJANGO_APPS = [ "unfold", "unfold.contrib.filters", "unfold.contrib.forms", "unfold.contrib.import_export", "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", "rest_framework.authtoken", "rest_framework_simplejwt.token_blacklist", "drf_spectacular", "drf_spectacular_sidecar", "django_filters", "import_export", "corsheaders", "auditlog", ] LOCAL_APPS = [ "apps.users", "apps.workspaces", "apps.clients", "apps.projects", "apps.tags", "apps.time_entries", "apps.notifications", ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "corsheaders.middleware.CorsMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "core.middlewares.current_user.CurrentUserMiddleware", "core.middlewares.exception_logging.ExceptionLoggingMiddleware", "config.services.logging.RequestLoggingMiddleware", "auditlog.middleware.AuditlogMiddleware", ] ROOT_URLCONF = "config.urls" AUTH_USER_MODEL = "users.User" 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", ], }, }, ] ASGI_APPLICATION = "config.asgi.application" DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": os.getenv("POSTGRES_DB"), "USER": os.getenv("POSTGRES_USER"), "PASSWORD": os.getenv("POSTGRES_PASSWORD"), "HOST": os.getenv("POSTGRES_HOST"), "PORT": os.getenv("POSTGRES_PORT"), "CONN_MAX_AGE": 0, "DISABLE_SERVER_SIDE_CURSORS": True, } } 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", }, ] REST_FRAMEWORK = { "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework_simplejwt.authentication.JWTAuthentication", ], "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.AllowAny"], "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], "DATETIME_FORMAT": "%Y-%m-%d %H:%M", "DATE_FORMAT": "%Y-%m-%d", "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "EXCEPTION_HANDLER": "core.exceptions.handlers.exception_handler", } LANGUAGE_CODE = os.getenv("LANGUAGE_CODE", "en-us") TIME_ZONE = os.getenv("TIME_ZONE", "Asia/Tehran") USE_I18N = True USE_TZ = True STATIC_URL = "/static/" STATIC_ROOT = BASE_DIR / "static" MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" SPECTACULAR_SETTINGS = { "TITLE": "Qlockify.ir API Documentation", "DESCRIPTION": "API documentation for Qlockify.ir", "VERSION": "1.0.0", "SERVE_INCLUDE_SCHEMA": True, "SWAGGER_UI_SETTINGS": { "deepLinking": True, }, "COMPONENT_SPLIT_REQUEST": True, "ENUM_NAME_OVERRIDES": {}, "TAG_SORTING": "alpha", "SWAGGER_UI_DIST": "SIDECAR", "SWAGGER_UI_FAVICON_HREF": "SIDECAR", "REDOC_DIST": "SIDECAR", } JWT_ACCESS_TOKEN_LIFETIME = int(os.getenv("JWT_ACCESS_TOKEN_LIFETIME_MINUTES", 60)) JWT_REFRESH_TOKEN_LIFETIME = int(os.getenv("JWT_REFRESH_TOKEN_LIFETIME_DAYS", 7)) JWT_ROTATE_REFRESH_TOKENS = os.getenv("JWT_ROTATE_REFRESH_TOKENS", "True") == "True" JWT_BLACKLIST_AFTER_ROTATION = os.getenv("JWT_BLACKLIST_AFTER_ROTATION", "True") == "True" JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256") SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(minutes=JWT_ACCESS_TOKEN_LIFETIME), "REFRESH_TOKEN_LIFETIME": timedelta(days=JWT_REFRESH_TOKEN_LIFETIME), "ROTATE_REFRESH_TOKENS": JWT_ROTATE_REFRESH_TOKENS, "BLACKLIST_AFTER_ROTATION": JWT_BLACKLIST_AFTER_ROTATION, "ALGORITHM": JWT_ALGORITHM, "SIGNING_KEY": SECRET_KEY, "AUTH_HEADER_TYPES": ("Bearer",), "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", "USER_ID_FIELD": "id", "USER_ID_CLAIM": "user_id", } REDIS_HOST = os.getenv("REDIS_HOST", "localhost") REDIS_PORT = os.getenv("REDIS_PORT", "6379") REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", "") if REDIS_PASSWORD: REDIS_URL = f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/0" else: REDIS_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/0" CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": REDIS_URL, "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", }, } } CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/1") CELERY_RESULT_BACKEND = os.getenv("CELERY_RESULT_BACKEND", "redis://127.0.0.1:6379/1") CELERY_ACCEPT_CONTENT = ["json"] CELERY_TASK_SERIALIZER = "json" CELERY_RESULT_SERIALIZER = "json" CELERY_TASK_ALWAYS_EAGER = False CELERY_TIMEZONE = os.getenv("TIME_ZONE") CELERY_TASK_TRACK_STARTED = True NOTIFICATIONS_ENABLED = os.getenv("NOTIFICATIONS_ENABLED", "True") == "True" NOTIFICATION_STREAM_TOKEN_LIFETIME_SECONDS = int( os.getenv("NOTIFICATION_STREAM_TOKEN_LIFETIME_SECONDS", "90") ) NOTIFICATION_SSE_HEARTBEAT_SECONDS = int( os.getenv("NOTIFICATION_SSE_HEARTBEAT_SECONDS", "20") ) NOTIFICATION_SSE_RETRY_MS = int(os.getenv("NOTIFICATION_SSE_RETRY_MS", "5000")) NOTIFICATION_DEFAULT_PAGE_SIZE = int( os.getenv("NOTIFICATION_DEFAULT_PAGE_SIZE", "20") ) NOTIFICATION_MAX_PAGE_SIZE = int(os.getenv("NOTIFICATION_MAX_PAGE_SIZE", "50")) NOTIFICATION_REDIS_CHANNEL_PREFIX = os.getenv( "NOTIFICATION_REDIS_CHANNEL_PREFIX", "notif:user" ) NOTIFICATION_RETENTION_DAYS = int(os.getenv("NOTIFICATION_RETENTION_DAYS", "30")) NOTIFICATION_TOAST_LEVELS = tuple( level.strip() for level in os.getenv( "NOTIFICATION_TOAST_LEVELS", "info,success,warning,error" ).split(",") if level.strip() ) CELERY_IMPORTS = ("apps.users.tasks", "apps.notifications.tasks") STORAGES = { "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, "images": {"BACKEND": "config.services.storage.UploadStorage"}, "staticfiles": { "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", }, } SMS_APIKEY = os.getenv("SMS_APIKEY", "") BASE_URL = os.getenv("BASE_URL", "")