python 121 lines · 4 tabs

Per-Tenant Data Isolation in Django with a Thread-Local Current Tenant Manager

Shared by codesnips Jul 2026
4 tabs
import threading
from contextlib import contextmanager

_state = threading.local()


def set_current_tenant(organization):
    _state.tenant = organization


def get_current_tenant():
    return getattr(_state, "tenant", None)


def clear_current_tenant():
    if hasattr(_state, "tenant"):
        del _state.tenant


@contextmanager
def tenant_scope(organization):
    previous = get_current_tenant()
    set_current_tenant(organization)
    try:
        yield
    finally:
        if previous is None:
            clear_current_tenant()
        else:
            set_current_tenant(previous)
4 files · python Explain with highlit

Multi-tenant SaaS applications share one database across many customers, so the single most dangerous bug class is a query that leaks one tenant's rows to another. This snippet enforces isolation at the ORM layer so that every model query is automatically scoped to the current request's organization, rather than trusting each view to remember a .filter(org=...) call.

In tenant_context.py, a threading.local() object holds the active Organization for the duration of a request. The helper functions set_current_tenant, get_current_tenant, and clear_current_tenant are the only sanctioned way to touch that state. Thread-local storage is used because Django's synchronous request handling gives each worker thread one request at a time, so the tenant set at the start of a request stays correct until it is cleared. The tenant_scope context manager exists for background jobs and management commands, where there is no request to read from but code still needs an explicit tenant boundary.

In TenantMiddleware, the organization is resolved from the authenticated user once per request and stored via set_current_tenant. The try/finally around get_response guarantees clear_current_tenant runs even if the view raises, which prevents a leftover tenant from one request bleeding into the next when the worker thread is reused.

In models.py, TenantManager.get_queryset reads the current tenant and appends the organization filter transparently, so Document.objects.all() returns only the caller's rows. Keeping the unscoped Manager available as all_objects is important: admin tooling, cross-tenant reports, and the middleware's own lookups need an escape hatch, and hiding it entirely would push developers toward unsafe raw queries. TenantModel.save auto-stamps organization from the current tenant so callers cannot accidentally create orphaned or misattributed records.

The main trade-off is implicitness — a reader of a view cannot see the tenant filter, so the rule must be well documented and the all_objects manager audited. The pattern also relies on the middleware always running before any query; queries in middleware ordered before TenantMiddleware will see no tenant. When those constraints hold, isolation becomes a property of the framework instead of a checklist every engineer must remember.


Related snips

ruby
payload = {
  sub: user.id,
  iss: 'https://auth.example.com',
  aud: 'codesnips-api',
  exp: 15.minutes.from_now.to_i,
  iat: Time.now.to_i,

JWT issuance and verification without common footguns

jwt authentication api
by Kai Nakamura 2 tabs
python
class Product(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    cost = models.DecimalField(max_digits=10, decimal_places=2)
    margin = models.DecimalField(max_digits=5, decimal_places=2, blank=True)

Django model signals vs overriding save

django python models
by Priya Sharma 2 tabs
python
from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [

Django URL namespacing and reverse lookups

django python urls
by Priya Sharma 3 tabs
bash
#!/usr/bin/env bash
set -euo pipefail

export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"

Secrets management with environment isolation and Vault

secrets-management vault environment-variables
by Kai Nakamura 1 tab
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs
python
import graphene
from graphene_django import DjangoObjectType
from blog.models import Post, Comment


class PostType(DjangoObjectType):

Django GraphQL with Graphene

django python graphql
by Priya Sharma 2 tabs

Share this code

Here's the card — post it anywhere.

Per-Tenant Data Isolation in Django with a Thread-Local Current Tenant Manager — share card
Link copied