"""Default settings for django-multitenant, overridable in the consumer project's settings.py."""
from django.conf import settings
MULTITENANT_DEFAULTS: dict[str, object] = {
# Isolation
"ISOLATION_BACKEND": "schema", # 'schema', 'rls', 'shared_fk'
# Schema (schema backend only)
"SHARED_SCHEMA_NAME": "public",
"TENANT_SCHEMA_PREFIX": "tenant_",
"SCHEMA_NAME_REGEX": r"^[a-z][a-z0-9_]{2,62}$",
"RESERVED_SCHEMA_NAMES": [
"public",
"pg_catalog",
"information_schema",
"pg_toast",
"pg_temp",
],
# Tenant resolution
"TENANT_HEADER": "X-Tenant-ID",
"TENANT_JWT_CLAIM": "tenant_id",
"TENANT_RESOLUTION_ORDER": ["jwt", "header"],
# ABAC
"ABAC_DEFAULT_EFFECT": "deny",
"ABAC_CONDITION_ENGINE": "json", # 'json', 'cedar', 'casbin'
"ABAC_STATIC_POLICIES_MODULE": "src.module.authorization.domain.static_policies",
"ABAC_CACHE_POLICIES": True,
"ABAC_ENABLED": True,
"ABAC_POLICY_CACHE_TTL": 300,
"ABAC_MAX_CONDITION_DEPTH": 10,
"ABAC_MAX_CONDITIONS_PER_POLICY": 50,
"ABAC_REGEX_TIMEOUT_MS": 100,
# Approval
"APPROVAL_DEFAULT_EXPIRATION_HOURS": 72,
"APPROVAL_DEFAULT_REQUIRED_APPROVALS": 1,
"APPROVAL_AUTO_EXECUTE_DEFAULT": True,
"APPROVAL_ENABLED": True,
# JWT (claims only — token lifetime is managed via SIMPLE_JWT)
"JWT_INCLUDE_TENANT_CLAIMS": True,
# Async backend
"ASYNC_BACKEND": "auto", # 'celery', 'sync', 'auto'
# Cache isolation
"CACHE_KEY_FUNCTION": "src.share_kernel.infrastructure.cache.tenant_cache_key_func",
"CACHE_ISOLATION_ENABLED": True,
# Noisy neighbor
"TENANT_STATEMENT_TIMEOUT_MS": 30000,
"TENANT_RATE_THROTTLE": "100/minute",
# Security
"TENANT_SCHEMA_SAFE_IDENTIFIER": True,
# OTP / Two-Factor Authentication
"OTP_ENABLED": False,
"OTP_EMAIL_ENABLED": True,
"OTP_TOTP_ENABLED": True,
"OTP_STATIC_ENABLED": True,
"OTP_EMAIL_TOKEN_VALIDITY_SECONDS": 300, # 5 minutes
"OTP_TOTP_ISSUER_NAME": "django-multitenant",
"OTP_STATIC_TOKEN_COUNT": 10,
"OTP_REQUIRED_FOR_LOGIN": False, # When True, login requires OTP verification
# Email verification
"EMAIL_VERIFICATION_ENABLED": False,
"EMAIL_VERIFICATION_TOKEN_VALIDITY_HOURS": 48,
# Tenant invitations
"INVITATION_ENABLED": True,
"INVITATION_EXPIRATION_HOURS": 168, # 7 days
"INVITATION_FROM_EMAIL": None, # Falls back to DEFAULT_FROM_EMAIL
}
[docs]
def get_setting(name: str) -> object:
"""Retrieve a multitenant setting, falling back to the default."""
user_settings = getattr(settings, "MULTITENANT", {})
if name in user_settings:
return user_settings[name]
return MULTITENANT_DEFAULTS[name]