""" Common settings shared between development and production environments. Environment-specific settings (DEBUG, SECRET_KEY, DATABASES, etc.) live in development.py and production.py respectively. """ from datetime import timedelta from pathlib import Path import environ env_path = Path(__file__).resolve().parent.parent.parent / ".env" env = environ.Env() environ.Env.read_env(env_path) # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent # ── Installed Apps ──────────────────────────────────────────────────────────── THIRD_PARTY_APPS = [ "rest_framework", "rest_framework_simplejwt", "corsheaders", "django_tailwind_cli", "django_htmx", "django_filters", "import_export", "drf_spectacular", "django_q", ] USER_DEFINED_APPS = [ "src.epics.apps.EpicsConfig", "src.notes.apps.NotesConfig", "src.projects.apps.ProjectsConfig", "src.accounts.apps.AccountsConfig", "src.external.apps.ExternalConfig", "src.notifications.apps.NotificationsConfig", "src.tickets.apps.TicketsConfig", ] BUILT_IN_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ] INSTALLED_APPS = BUILT_IN_APPS + THIRD_PARTY_APPS + USER_DEFINED_APPS INSTALLED_APPS.insert(0, "jazzmin") # ── Middleware ──────────────────────────────────────────────────────────────── MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "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", "django_htmx.middleware.HtmxMiddleware", ] ROOT_URLCONF = "config.urls" # ── Templates ───────────────────────────────────────────────────────────────── 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", "src.dashboard.context_processors.site_stats", ], }, }, ] WSGI_APPLICATION = "config.wsgi.application" # ── Authentication ──────────────────────────────────────────────────────────── AUTH_USER_MODEL = "accounts.User" 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", }, ] LOGIN_URL = "login" LOGIN_REDIRECT_URL = "dashboard" SESSION_SAVE_EVERY_REQUEST = True SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" # ── Internationalisation ────────────────────────────────────────────────────── LANGUAGE_CODE = "en-us" TIME_ZONE = env("TIME_ZONE", default="Asia/Kathmandu") USE_I18N = True USE_TZ = True # ── Static & Media Files ────────────────────────────────────────────────────── STATIC_URL = "/static/" STATIC_ROOT = BASE_DIR / "staticfiles" STATICFILES_DIRS = [BASE_DIR / "static/"] MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media" # ── Cache ────────────────────────────────────────────────────────────────── CACHES = { "default": { "BACKEND": "django.core.cache.backends.db.DatabaseCache", "LOCATION": "django_cache", } } # ── Database ──────────────────────────────────────────────────────────────── CONN_MAX_AGE = env.int("CONN_MAX_AGE", default=600) DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # ── Tailwind ────────────────────────────────────────────────────────────────── TAILWIND_CLI_USE_DAISY_UI = False TAILWIND_CLI_SRC_CSS = "static/src/tailwind.css" # ── REST Framework ──────────────────────────────────────────────────────────── REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework_simplejwt.authentication.JWTAuthentication", "rest_framework.authentication.SessionAuthentication", ), "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": 50, "DEFAULT_FILTER_BACKENDS": ( "django_filters.rest_framework.DjangoFilterBackend", "rest_framework.filters.SearchFilter", "rest_framework.filters.OrderingFilter", ), "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "anon": "100/hour", "user": "1000/hour", }, } SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(hours=1), "REFRESH_TOKEN_LIFETIME": timedelta(days=30), "ROTATE_REFRESH_TOKENS": True, "AUTH_HEADER_TYPES": ("Bearer",), } # ── CORS ────────────────────────────────────────────────────────────────────── CORS_ALLOW_METHODS = [ "DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT", ] CORS_ALLOW_HEADERS = [ "accept", "accept-encoding", "authorization", "content-type", "dnt", "origin", "user-agent", "x-csrftoken", "x-requested-with", ] CORS_PREFLIGHT_MAX_AGE = 86400 # 24 hours # ── Security (shared baseline) ──────────────────────────────────────────────── SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = "DENY" # ── Site ────────────────────────────────────────────────────────────────────── BASE_URL = env("BASE_URL", default="http://localhost:8000") SITE_NAME = env("SITE_NAME", default="Project Management") # ── API Docs ────────────────────────────────────────────────────────────────── SPECTACULAR_SETTINGS = { "TITLE": "Project Management API", "DESCRIPTION": "Django port of Project Management", "VERSION": "1.0.0", } # ── Django Q2 ───────────────────────────────────────────────────────────────── # timeout must be set and less than retry to avoid tasks being re-triggered early Q_CLUSTER = { "name": "project_management", "workers": env.int("Q_WORKERS", default=2), "timeout": 60, "retry": 120, "queue_limit": 50, "bulk": 10, "orm": "default", "poll": 2000, "recycle": 500, }