RLS Context
RLS context (rls.user_id, rls.tenant_id, and custom keys) are PostgreSQL session variables read by your policies. They are not authentication — they tell PostgreSQL which identity to filter for on this connection.
In 1.0.0, context management lives in django_rls.context. Imports from django_rls.db.functions still work via re-exports.
Middleware (HTTP requests)
RLSContextMiddleware sets identity from trusted request attributes only:
request.user.id(authenticated users)request.tenant.idrequest.user.profile.tenant_id(if present)request.session["tenant_id"]— only whenALLOW_SESSION_TENANT=True
Headers, query strings, and JSON bodies cannot inject context.
Setting context manually
from django_rls.context import set_rls_context, get_rls_context
set_rls_context("user_id", 42, system=True)
set_rls_context("tenant_id", 7, system=True)
assert get_rls_context("user_id") == "42"
Use system=True when establishing identity from a trusted source (middleware, workers, tests).
Identity immutability (1.0.0)
Once user_id or tenant_id is set, they cannot be overwritten unless you use a privileged scope:
from django_rls.context import set_rls_context, system_rls_context
from django_rls.exceptions import RLSContextImmutableError
set_rls_context("user_id", 1, system=True)
set_rls_context("user_id", 2) # raises RLSContextImmutableError
with system_rls_context(user_id=99):
# privileged switch for workers, admin tasks, tests
...
Context managers
system_rls_context() — workers, tests, migrations
from django_rls.context import system_rls_context
def process_invoice(invoice_id, actor_user_id, tenant_id):
with system_rls_context(user_id=actor_user_id, tenant_id=tenant_id):
return Invoice.objects.get(pk=invoice_id)
rls_context() — restore previous values on exit
from django_rls.context import rls_context
with rls_context(system=True, user_id=10, department_id="sales"):
Document.objects.filter(...)
# previous session values restored
Background jobs (Celery, async workers)
HTTP middleware does not run in workers. See
Background Tasks to propagate the enqueuing request's
context to Celery, django.tasks, and other task queues.
Jobs without a user (periodic tasks, maintenance scripts) should derive
user_id / tenant_id from the database — never from raw task arguments — and
wrap their work in system_rls_context():
from django_rls.context import system_rls_context
@shared_task
def export_tenant_data(tenant_id, requested_by_id):
with system_rls_context(user_id=requested_by_id, tenant_id=tenant_id):
return list(Report.objects.values())
Clearing context
from django_rls.context import clear_rls_context
clear_rls_context() # clears all registered keys in DB + in-process state
Middleware clears context in a finally block after every request, including when views raise exceptions.
Requiring identity
When REQUIRE_CONTEXT=True:
DJANGO_RLS = {"REQUIRE_CONTEXT": True}
from django_rls.exceptions import RLSContextRequiredError
Document.objects.count() # raises RLSContextRequiredError without identity
with system_rls_context(user_id=1):
Document.objects.count() # OK