API Reference¶
Auto-generated from source code docstrings.
Top-Level Exports¶
The most common symbols are available directly from django_rls_tenants:
from django_rls_tenants import (
RLSConstraint,
RLSManager,
RLSProtectedModel,
TenantQuerySet,
TenantUser,
admin_context,
tenant_context,
with_rls_context,
)
RLS Layer¶
Generic PostgreSQL Row-Level Security primitives. This layer has zero imports
from tenants/.
GUC Helpers¶
django_rls_tenants.rls.guc.set_guc
¶
Set a PostgreSQL session variable (GUC).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Variable name (e.g., |
required |
value
|
str
|
Variable value as string. |
required |
is_local
|
bool
|
If |
False
|
using
|
str
|
Database alias. Default: |
'default'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
RuntimeError
|
If |
Source code in django_rls_tenants/rls/guc.py
django_rls_tenants.rls.guc.get_guc
¶
Get a PostgreSQL session variable value.
Returns:
| Type | Description |
|---|---|
str | None
|
The variable value, or |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in django_rls_tenants/rls/guc.py
django_rls_tenants.rls.guc.clear_guc
¶
Clear a GUC variable by setting it to an empty string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Variable name to clear. |
required |
is_local
|
bool
|
If |
False
|
using
|
str
|
Database alias. Default: |
'default'
|
Source code in django_rls_tenants/rls/guc.py
Constraints¶
django_rls_tenants.rls.constraints.RLSConstraint
¶
RLSConstraint(
*,
field: str,
name: str,
guc_tenant_var: str = "rls.current_tenant",
guc_admin_var: str = "rls.is_admin",
tenant_pk_type: str = "int",
extra_bypass_flags: list[str] | None = None,
)
Bases: BaseConstraint
Django constraint that generates PostgreSQL RLS policies during migrations.
When Django applies a migration containing this constraint, it:
- Enables RLS on the table (
ALTER TABLE ... ENABLE ROW LEVEL SECURITY). - Forces RLS for the table owner (
ALTER TABLE ... FORCE ROW LEVEL SECURITY). - Creates an isolation policy with configurable
USINGandWITH CHECK.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
field
|
str
|
FK field name for tenant identification (e.g., |
required |
name
|
str
|
Constraint name. Supports |
required |
guc_tenant_var
|
str
|
GUC variable holding the current tenant ID.
Default: |
'rls.current_tenant'
|
guc_admin_var
|
str
|
GUC variable for admin bypass.
Default: |
'rls.is_admin'
|
tenant_pk_type
|
str
|
SQL cast type for tenant PK.
Default: |
'int'
|
extra_bypass_flags
|
list[str] | None
|
Additional GUC variables that bypass the |
None
|
Source code in django_rls_tenants/rls/constraints.py
constraint_sql
¶
No inline constraint SQL; defer RLS DDL to after CREATE TABLE.
Django calls constraint_sql during CREATE TABLE for inline
constraints. RLS policies require the table to exist first, so we
defer the actual DDL and return an empty string (filtered out by
Django's if statement guard).
Source code in django_rls_tenants/rls/constraints.py
create_sql
¶
create_sql(
model: type[Model] | None,
schema_editor: BaseDatabaseSchemaEditor | None,
) -> Statement
Generate SQL to enable RLS and create the isolation policy.
Source code in django_rls_tenants/rls/constraints.py
remove_sql
¶
remove_sql(
model: type[Model] | None,
schema_editor: BaseDatabaseSchemaEditor | None,
) -> Statement
Generate SQL to drop the policy and disable RLS.
Source code in django_rls_tenants/rls/constraints.py
validate
¶
validate(
model: type[Model],
instance: Any,
exclude: Any = None,
using: str | None = None,
) -> None
No-op: RLS is enforced at the database level, not in Django validation.
deconstruct
¶
Return a 3-tuple for Django's migration serializer.
Source code in django_rls_tenants/rls/constraints.py
Context Managers¶
django_rls_tenants.rls.context.rls_context
¶
rls_context(
variables: dict[str, str],
*,
is_local: bool = False,
using: str = "default",
) -> Iterator[None]
Set multiple GUC variables for the duration of a block.
Saves and restores previous values on exit (supports nesting).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variables
|
dict[str, str]
|
Dict of GUC variable names to values. |
required |
is_local
|
bool
|
If |
False
|
using
|
str
|
Database alias. Default: |
'default'
|
Source code in django_rls_tenants/rls/context.py
django_rls_tenants.rls.context.bypass_flag
¶
Temporarily set a GUC bypass flag to 'true'.
Saves and restores previous value on exit (supports nesting).
Usage::
with bypass_flag("rls.is_login_request"):
user = User.objects.get(email=email)
Source code in django_rls_tenants/rls/context.py
Tenants Layer¶
Django multitenancy built on top of the rls/ primitives.
Configuration¶
django_rls_tenants.tenants.conf.RLSTenantsConfig
¶
Read library configuration from settings.RLS_TENANTS.
All settings live under a single dict::
RLS_TENANTS = {
"TENANT_MODEL": "myapp.Tenant", # Required
"GUC_PREFIX": "rls", # Default: "rls"
"TENANT_FK_FIELD": "tenant", # Default: "tenant"
"USER_PARAM_NAME": "as_user", # Default: "as_user"
"TENANT_PK_TYPE": "int", # Default: "int"
"USE_LOCAL_SET": False, # Default: False
}
Source code in django_rls_tenants/tenants/conf.py
Models¶
django_rls_tenants.tenants.models.RLSProtectedModel
¶
Bases: Model
Abstract base model for tenant-scoped models.
Provides:
- A
tenantForeignKey added dynamically via theclass_preparedsignal (target read fromRLS_TENANTS["TENANT_MODEL"]). RLSManageras the default manager (withfor_user()).RLSConstraintinMeta.constraints(generates RLS policy).
Usage::
class Order(RLSProtectedModel):
product = models.CharField(max_length=255)
amount = models.DecimalField(...)
class Meta(RLSProtectedModel.Meta):
db_table = "order"
To customize the tenant FK (e.g., nullable for admin users),
declare the field directly on your model -- the class_prepared
handler will not add a duplicate::
class User(AbstractUser, RLSProtectedModel):
tenant = models.ForeignKey(
Tenant, on_delete=models.CASCADE,
null=True, blank=True,
)
Managers¶
django_rls_tenants.tenants.managers.TenantQuerySet
¶
Bases: QuerySet
QuerySet that sets RLS GUC variables at evaluation time.
Stores the user reference from for_user() and defers GUC setup
to _fetch_all(), ensuring lazy querysets work correctly with RLS.
Source code in django_rls_tenants/tenants/managers.py
for_user
¶
Scope this queryset to the given user's tenant.
For admin users: returns all rows (RLS admin bypass at eval time). For tenant users: returns rows matching the user's tenant.
The queryset remains lazy and chainable. GUC variables are set
when the queryset is evaluated, not when for_user() is called.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
as_user
|
TenantUser
|
User object satisfying the |
required |
Source code in django_rls_tenants/tenants/managers.py
django_rls_tenants.tenants.managers.RLSManager
¶
Bases: Manager
Manager for RLS-protected models.
Provides for_user() for scoped queries and
prepare_tenant_in_model_data() for resolving tenant FKs.
get_queryset
¶
for_user
¶
prepare_tenant_in_model_data
¶
Resolve a raw tenant ID for model creation.
If model_data contains a raw tenant ID (int/str) under
the configured FK field name, sets the FK column directly
({field}_id) to avoid a SELECT query. Allows passing
tenant=42 in creation data without N+1 overhead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_data
|
dict[str, Any]
|
Dict of field names to values. |
required |
as_user
|
TenantUser
|
User for context (unused here but part of API). |
required |
Source code in django_rls_tenants/tenants/managers.py
Context Managers¶
django_rls_tenants.tenants.context.tenant_context
¶
Set RLS context to a specific tenant. Supports nesting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tenant_id
|
int | str
|
The tenant PK to scope queries to. |
required |
using
|
str
|
Database alias. Default: |
'default'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in django_rls_tenants/tenants/context.py
django_rls_tenants.tenants.context.admin_context
¶
Set RLS context to admin mode. Supports nesting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
using
|
str
|
Database alias. Default: |
'default'
|
Source code in django_rls_tenants/tenants/context.py
django_rls_tenants.tenants.context.with_rls_context
¶
with_rls_context(
func: Callable[..., Any] | None = None,
*,
user_param: str | None = None,
) -> Callable[..., Any]
Decorator that extracts a user argument and sets RLS context.
Can be used bare or with an explicit user_param::
@with_rls_context
def my_view(request, as_user): ...
@with_rls_context(user_param="current_user")
def my_view(request, current_user): ...
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
Callable[..., Any] | None
|
The function to decorate (when used without parentheses). |
None
|
user_param
|
str | None
|
Override the parameter name to look for. Defaults to
|
None
|
When the user argument is None, logs a warning and proceeds
without context (fail-closed: RLS blocks all access).
Source code in django_rls_tenants/tenants/context.py
Middleware¶
django_rls_tenants.tenants.middleware.RLSTenantMiddleware
¶
Bases: MiddlewareMixin
Set RLS context for each authenticated request.
For authenticated users: sets tenant_context or admin_context
based on the user's TenantUser protocol implementation.
For unauthenticated requests: no context is set. RLS policies block all access to protected tables (fail-closed).
This is API-agnostic -- works identically for REST, GraphQL, Django views, or any other request handler.
Add to MIDDLEWARE::
MIDDLEWARE = [
...
"django_rls_tenants.tenants.middleware.RLSTenantMiddleware",
]
process_request
¶
Set GUC variables based on the authenticated user.
If the first set_guc succeeds but the second fails (e.g.,
broken connection), both GUCs are cleared to prevent partial
state from leaking tenant context.
Source code in django_rls_tenants/tenants/middleware.py
process_response
¶
Clear GUC variables to prevent cross-request leaks.
Source code in django_rls_tenants/tenants/middleware.py
Types¶
django_rls_tenants.tenants.types.TenantUser
¶
Bases: Protocol
Protocol that user objects must satisfy for RLS context resolution.
Implement these two properties on your User model::
class User(AbstractUser, RLSProtectedModel):
@property
def is_tenant_admin(self) -> bool:
return self.role.name == "ADMIN"
@property
def rls_tenant_id(self) -> int | str | None:
return self.tenant_id if self.tenant_id else None
Bypass Helpers¶
django_rls_tenants.tenants.bypass.set_bypass_flag
¶
Set a bypass flag on the current database connection.
The flag name should match one of the extra_bypass_flags
configured on an RLSConstraint::
set_bypass_flag("rls.is_login_request")
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flag_name
|
str
|
GUC variable name (e.g., |
required |
is_local
|
bool
|
If |
False
|
using
|
str
|
Database alias. Default: |
'default'
|
Source code in django_rls_tenants/tenants/bypass.py
django_rls_tenants.tenants.bypass.clear_bypass_flag
¶
Clear a bypass flag on the current database connection.
Testing¶
django_rls_tenants.tenants.testing.rls_bypass
¶
Temporarily enable admin bypass for tests.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
using
|
str
|
Database alias. Default: |
'default'
|
Usage::
with rls_bypass():
all_orders = Order.objects.all() # sees all tenants
Source code in django_rls_tenants/tenants/testing.py
django_rls_tenants.tenants.testing.rls_as_tenant
¶
Scope to a specific tenant for tests.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tenant_id
|
int | str
|
The tenant PK to scope to. |
required |
using
|
str
|
Database alias. Default: |
'default'
|
Usage::
with rls_as_tenant(tenant_id=42):
orders = Order.objects.all() # only tenant 42
Source code in django_rls_tenants/tenants/testing.py
django_rls_tenants.tenants.testing.assert_rls_enabled
¶
Assert that RLS is enabled and forced on the given table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table_name
|
str
|
The database table name to check. |
required |
using
|
str
|
Database alias. Default: |
'default'
|
Raises:
| Type | Description |
|---|---|
AssertionError
|
If RLS is not enabled or not forced. |
Source code in django_rls_tenants/tenants/testing.py
django_rls_tenants.tenants.testing.assert_rls_policy_exists
¶
assert_rls_policy_exists(
table_name: str,
policy_name: str | None = None,
*,
using: str = "default",
) -> None
Assert that an RLS policy exists on the given table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table_name
|
str
|
The database table name to check. |
required |
policy_name
|
str | None
|
Expected policy name. Defaults to
|
None
|
using
|
str
|
Database alias. Default: |
'default'
|
Source code in django_rls_tenants/tenants/testing.py
django_rls_tenants.tenants.testing.assert_rls_blocks_without_context
¶
Assert that querying with no GUC context returns zero rows.
Verifies the fail-closed behavior. Requires at least one row to exist in the table (caller must set up test data first).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_class
|
type[Model]
|
The RLS-protected model class to query. |
required |
using
|
str
|
Database alias. Default: |
'default'
|
Raises:
| Type | Description |
|---|---|
AssertionError
|
If any rows are returned, or if the table is empty (which would make the assertion pass vacuously). |