Skip to main content

django-tenancy

Shared multi-tenancy infrastructure for the BF Agent platform — per ADR-035.

Core Components

  • Organization: Tenant model with lifecycle management (trial → active → suspended → deleted)
  • Membership: User-to-organization mapping with roles (owner, admin, member, viewer, external)
  • SubdomainTenantMiddleware: Resolves tenant from subdomain or X-Tenant-ID header
  • TenantAwareManager: Explicit .for_tenant(uuid) queryset filtering
  • Context propagation: Async-safe contextvars + PostgreSQL RLS session variable
  • Health endpoints: /livez/ (liveness) + /healthz/ (readiness with DB + Redis checks)
  • Decorators: tenant_context() context manager, @with_tenant_from_arg() for Celery tasks

Installation

pip install -e ".[dev]"

Usage

# settings.py
INSTALLED_APPS = [
    ...
    "django_tenancy",
]

MIDDLEWARE = [
    ...
    "django_tenancy.middleware.SubdomainTenantMiddleware",
]

# urls.py
from django_tenancy.healthz import liveness, readiness

urlpatterns = [
    path("livez/", liveness),
    path("healthz/", readiness),
]

# In views / services:
from django_tenancy.managers import TenantAwareManager

class MyModel(models.Model):
    tenant_id = models.UUIDField(db_index=True)
    objects = TenantAwareManager()

# Query with tenant isolation:
MyModel.objects.for_tenant(request.tenant_id)

ADR-137: TenantManager (auto-filter)

from django_tenancy.managers import TenantManager

class MyModel(models.Model):
    tenant_id = models.UUIDField(db_index=True)
    objects = TenantManager()

# In request context → auto-filtered by middleware tenant:
MyModel.objects.all()

# Explicit (Celery, management commands):
MyModel.objects.for_tenant(tenant_uuid)

# Admin / cross-tenant reports:
MyModel.objects.unscoped()

ADR-137: Row-Level Security (Phase 2)

Setup (once per database)

# 1. Create separate DB roles
python manage.py setup_rls_roles --dry-run   # preview
python manage.py setup_rls_roles \
    --app-user=risk_hub \
    --app-password=<secret>

# 2. Enable RLS on all tenant tables
python manage.py enable_rls --dry-run        # preview
python manage.py enable_rls                  # execute

# Single table:
python manage.py enable_rls --table=risk_assessment

# Remove RLS:
python manage.py enable_rls --disable

DB Role Separation

Role Used by RLS
<db>_admin (table owner) migrate, createsuperuser exempt
<db>_app (non-owner) gunicorn, celery active

After setup_rls_roles, update DATABASE_URL for gunicorn/celery to use the app-user. Keep the admin-user for migrations.

ADR-137: Phase 4 — RLS Rollout Checklist

Pre-requisites

  1. All tenant-scoped models use TenantManager (Phase 4.1 ✅)
  2. SubdomainTenantMiddleware sets app.tenant_id session var
  3. DB roles created via setup_rls_roles

Rollout Steps (per environment)

# 1. Dry-run — verify SQL, no changes
python manage.py enable_rls --dry-run

# 2. Apply RLS policies
python manage.py enable_rls

# 3. Verify no query breakage (run test suite)
python -m pytest tests/ -x

# 4. Switch app to non-owner DB user
#    Update DATABASE_URL in .env.prod:
#    OLD: postgresql://risk_hub_admin:xxx@db/risk_hub
#    NEW: postgresql://risk_hub_app:xxx@db/risk_hub
#    Keep admin user for migrate service only.

# 5. Restart app containers
docker compose restart web celery

# 6. Smoke test — verify tenant isolation
curl -H "X-Tenant-ID: <uuid>" https://app/api/v1/...

Covered Tables (28 models)

App Models
risk Assessment, Hazard
actions ActionItem
documents Document, DocumentVersion
approvals ApprovalWorkflow, ApprovalRequest
notifications Notification, NotificationPreference
permissions Role, Scope, Assignment
identity ApiKey
tenancy Membership, Site
explosionsschutz Area, ExplosionConcept, ZoneDefinition, ProtectionMeasure, Equipment, Inspection, ZoneIgnitionSourceAssessment, VerificationDocument, ZoneCalculationResult, EquipmentATEXCheck
substances Party + all TenantScopedModel subclasses
gbu HazardAssessmentActivity, ActivityMeasure

Excluded (by design)

  • Organization — tenant entity itself, not tenant-scoped
  • User — nullable tenant_id, uses Django's UserManager
  • Permission, RolePermission, ApprovalStep, ApprovalDecision — no tenant_id
  • TenantScopedMasterData subclasses (explosionsschutz) — nullable tenant_id, hybrid isolation

Critical Rule

Organization.id != Organization.tenant_id — always use org.tenant_id for data isolation.

Metadata

Release files for iil-django-tenancy 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for iil-django-tenancy 0.1.0
File Size Uploaded Provenance
iil_django_tenancy-0.1.0.tar.gz 25.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for iil-django-tenancy 0.1.0
File Interpreter ABI Platform
iil_django_tenancy-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 54.5 kB

Release files / iil_django_tenancy-0.1.0.tar.gz

Download URL iil_django_tenancy-0.1.0.tar.gz
Size 25.9 kB
Tags Source
SHA-256 checksum
How to use checksums
6a2358ae9df559fc382b102707f84fe690f69a7ab55c59aa25d6c16703d80366
BLAKE2b-256 checksum
How to use checksums
e1f383c8242eec1ee8f206ee10faa799bc829c0eed8d14ef1653cea7eaf48a6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.12

Release files / iil_django_tenancy-0.1.0-py3-none-any.whl

Download URL iil_django_tenancy-0.1.0-py3-none-any.whl
Size 28.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
63f0745abda8f8eb0a1ae6e7c37064d5bb05773ef1f59d2120957f7ef84c76a7
BLAKE2b-256 checksum
How to use checksums
d104700ca313927e740f90db8f6c600e13f382e962894f2caeb35eaba502a29e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.12

Security for iil-django-tenancy 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page