🎉 Heading to EuroPython 2026? Check out my poster on Django TDD Patterns & read the full visual field guide here.

django-rls

Row-level security for Django on PostgreSQL — policies as model metadata, enforced in the database.

Stack
Django · PostgreSQL
Status
active
Source
https://github.com/kdpisda/django-rls

Started as a scratch-your-own-itch weekend library, now the subject of a hands-on workshop at DjangoCon Europe. PostgreSQL row-level security expressed as Django model metadata: policies live next to the models they protect and are enforced by the database, not the ORM.

How it works

Models inherit from RLSModel and declare policies in Meta.rls_policies. Tenant- and user-scoped policies come ready-made (TenantPolicy, UserPolicy), and ModelPolicy accepts plain Django Q objects — which compile down to real PostgreSQL CREATE POLICY statements:

from django.db.models import Q
from django_rls.models import RLSModel
from django_rls.policies import ModelPolicy, RLS

class Project(RLSModel):
    name = models.CharField(max_length=100)
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    is_public = models.BooleanField(default=False)

    class Meta:
        rls_policies = [
            ModelPolicy("access_policy",
                        filters=Q(owner=RLS.user_id()) | Q(is_public=True)),
        ]

RLSContextMiddleware sets the session context per request, context processors can inject anything dynamic (user IP, session data), and custom migration operations plus enable_rls/disable_rls management commands handle the DDL side.

Under the hood

  • Context reads are cached per statement: current_setting() is wrapped in a scalar subquery so Postgres evaluates it once as an InitPlan, not per row.
  • Joined lookups like company__name compile to subqueries inside the policy.
  • Field names are validated before DDL generation, keeping injection out of generated policies.
  • Hierarchical tenancy (nested organizations) works via recursive CTEs, and custom user models — UUID primary keys included — are supported.

Runs on Python 3.10–3.13 against PostgreSQL 12+. Published on PyPI as django-rls (BSD-3-Clause), with docs at django-rls.com.