Getting Started with Django RLS: A Complete Guide

Ready to implement Row Level Security in your Django application? This guide will walk you through everything you need to get started with Django RLS, from installation to your first policy.

Prerequisites

Before you begin, ensure you have:

  • Django 3.2+ installed
  • PostgreSQL 9.5+ as your database (RLS requires PostgreSQL)
  • Python 3.7+
  • Basic knowledge of Django models and migrations

Step 1: Install Django RLS

Install the package using pip:

pip install django-rls

Add it to your INSTALLED_APPS:

# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'django_rls',  # Add this
    'your_app',
]

Step 2: Add the RLS Middleware

Add Django RLS middleware to automatically manage context:

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    
    'django_rls.middleware.RLSMiddleware',  # Add this
]

Step 3: Convert Your Models to RLS Models

Instead of inheriting from models.Model, inherit from RLSModel:

# models.py
from django.db import models
from django_rls.models import RLSModel
from django_rls.policies import UserPolicy

class Document(RLSModel):  # Changed from models.Model
    title = models.CharField(max_length=200)
    content = models.TextField()
    owner = models.ForeignKey(
        'auth.User',
        on_delete=models.CASCADE,
        related_name='documents'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        # Define your RLS policies here
        rls_policies = [
            UserPolicy('owner_policy', user_field='owner')
        ]
    
    def __str__(self):
        return self.title

Step 4: Create and Run Migrations

Django RLS automatically creates the necessary PostgreSQL RLS policies through migrations:

python manage.py makemigrations
python manage.py migrate

Django RLS will create:

  • The database table (like normal)
  • PostgreSQL RLS policies based on your configuration
  • Migration files for tracking changes

Step 5: Use Your Model

Now you can use your model normally, but with automatic RLS protection:

# views.py
from django.shortcuts import render
from .models import Document

def document_list(request):
    # This query automatically respects RLS policies
    # User only sees their own documents
    documents = Document.objects.all()
    return render(request, 'documents/list.html', {'documents': documents})

def document_detail(request, pk):
    # Even accessing a specific document is protected by RLS
    document = Document.objects.get(pk=pk)
    return render(request, 'documents/detail.html', {'document': document})

Step 6: Create in Django Admin

Django admin works seamlessly with RLS:

# admin.py
from django.contrib import admin
from .models import Document

@admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
    list_display = ['title', 'owner', 'created_at']
    readonly_fields = ['created_at']

Admin users will automatically see only documents they own (or all if they have appropriate permissions).

Common Use Cases

1. User-Owned Documents

class Document(RLSModel):
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    
    class Meta:
        rls_policies = [
            UserPolicy('owner_policy', user_field='owner')
        ]

Each user sees only their own documents.

2. Multi-Tenant SaaS

class Document(RLSModel):
    tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE)
    
    class Meta:
        rls_policies = [
            TenantPolicy('tenant_isolation', tenant_field='tenant')
        ]

Each tenant sees only their own data.

3. Team-Based Access

class Document(RLSModel):
    team = models.ForeignKey(Team, on_delete=models.CASCADE)
    
    class Meta:
        rls_policies = [
            # Define a team policy that allows all team members
        ]

4. Shared Documents with Ownership

class Document(RLSModel):
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    shared_with = models.ManyToManyField(User, related_name='shared_documents')
    
    class Meta:
        rls_policies = [
            # Policy that shows owned documents + shared documents
        ]

Testing Your Implementation

Use Django RLS’s test utilities:

# tests.py
from django.contrib.auth.models import User
from django_rls.test_utils import RLSTestCase
from .models import Document

class DocumentTestCase(RLSTestCase):
    def setUp(self):
        super().setUp()
        self.user1 = User.objects.create_user('user1', '[email protected]', 'pass')
        self.user2 = User.objects.create_user('user2', '[email protected]', 'pass')
    
    def test_user_isolation(self):
        # Create document as user1
        doc1 = Document.objects.create(
            title='User 1 Doc',
            owner=self.user1
        )
        
        # Set context to user1
        self.set_user_context(self.user1)
        
        # User1 can see their document
        self.assertEqual(Document.objects.count(), 1)
        self.assertEqual(Document.objects.first(), doc1)
        
        # Switch to user2
        self.set_user_context(self.user2)
        
        # User2 can't see user1's document
        self.assertEqual(Document.objects.count(), 0)

Troubleshooting

Policy Not Showing Effect

  1. Ensure middleware is installed - RLS requires context to be set
  2. Check database user permissions - The PostgreSQL user needs RLS enabled
  3. Verify migrations ran - Run python manage.py migrate

Performance Issues

  1. Add database indexes - RLS conditions work with indexes:

    class Document(RLSModel):
        owner = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
    
  2. Use select_related for foreign keys:

    documents = Document.objects.select_related('owner').all()
    

Next Steps

Now that you’ve set up Django RLS:

  1. Explore the documentation - Read about advanced policies and custom rules
  2. Check out examples - Look at the Django RLS GitHub repository
  3. Join the community - Participate in discussions and ask questions on the forum
  4. Test thoroughly - Write comprehensive tests for your RLS policies

Quick Reference

Import statements:

from django_rls.models import RLSModel
from django_rls.policies import UserPolicy, TenantPolicy
from django_rls.test_utils import RLSTestCase

Settings required:

INSTALLED_APPS = [..., 'django_rls', ...]
MIDDLEWARE = [..., 'django_rls.middleware.RLSMiddleware', ...]

Model structure:

class MyModel(RLSModel):
    # Your fields
    
    class Meta:
        rls_policies = [...]  # Define policies here

You’re now ready to implement database-level Row Level Security in your Django applications!

Running makemigrations and migrate doesn’t enable RLS nor create the policies in the migration files or in the database


"django>=6.0.3"
"django-rls>=0.4.1"
"psycopg[binary]>=3.3.3"

Checking, thanks for bringing this up!

it worked when i manually ran manage.py enable_rls, i kind of expected that the generated migrations will; include the enable rls and create policy code
also it is not clear in the ERP tutorial that i need to run this manually every time i has a new RLSModel or updated policy

1 Like

Hi @a-atalla — sorry for the delay, and thanks for the clear follow-up.

You’re right about two separate things:

1. Generated migrations don’t currently write ENABLE ROW LEVEL SECURITY / CREATE POLICY into the migration file.
makemigrations only tracks Django model state. RLS is applied afterward. We do ship migration operations (EnableRLS, CreatePolicy, etc. in django_rls.migration_operations) if you want them in a migration by hand, but they are not auto-generated today.

2. What should run after migrate
In the package code, a post_migrate signal (enable_rls_on_migrate in django_rls.models) calls RLSModel.enable_rls() for models in the app that just migrated — but only when DJANGO_RLS["AUTO_ENABLE_RLS"] is true (default True in django_rls.conf). So you usually should not need a manual step, provided:

  • DATABASES["default"]["ENGINE"] is django_rls.backends.postgresql
  • you haven’t set AUTO_ENABLE_RLS to False
  • the models subclass RLSModel and define Meta.rls_policies

If any of those aren’t true (or post_migrate failed silently — failures are logged unless STRICT_MIGRATE_RLS is on), migrate alone won’t create policies, and python manage.py enable_rls is the correct fix (exactly what you did). That command lives in django_rls.management.commands.enable_rls and calls model.enable_rls() for each matching RLSModel.

When you add a new RLSModel or change policies
Re-run:

python manage.py enable_rls
# or for one app:
python manage.py enable_rls --app your_app_label

enable_rls() also FORCEs RLS and creates/updates each policy (if a policy already exists it alters the definition). Useful checks: python manage.py rls_status and (on 1.0+) python manage.py audit_rls.

For deploys, the reliable pattern is: migrate, then enable_rls (or rely on AUTO_ENABLE_RLS and verify with audit_rls in CI).

You’re also right that the ERP tutorial should spell this out — that’s a docs gap we’ll track on the repo.

If AUTO_ENABLE_RLS was on and the backend was correct but policies still didn’t appear after migrate, reply with your DJANGO_RLS settings and DB ENGINE and we can dig in.

— Kuldeep

1 Like

Tracked the docs gap here: [DOCS] Document enable_rls / AUTO_ENABLE_RLS explicitly in Getting Started and ERP tutorial · Issue #78 · kdpisda/django-rls · GitHub — Getting Started / ERP tutorial will call out enable_rls / AUTO_ENABLE_RLS and the migrate-then-enable_rls deploy pattern.

Hi @a-atalla — sorry for the delay, and thanks for the clear follow-up.

You’re right about two separate things:

1. Generated migrations don’t currently write ENABLE ROW LEVEL SECURITY / CREATE POLICY into the migration file.
makemigrations only tracks Django model state. RLS is applied afterward. We do ship migration operations (EnableRLS, CreatePolicy, etc.) if you want them in a migration by hand, but they are not auto-generated today.

2. What should run after migrate
With the default settings, DJANGO_RLS["AUTO_ENABLE_RLS"] is True, and a post_migrate hook calls enable_rls() for RLSModels in the app that just migrated. So you usually should not need a manual step — but only if:

  • DATABASES["default"]["ENGINE"] is django_rls.backends.postgresql
  • you haven’t set AUTO_ENABLE_RLS to False
  • the models actually subclass RLSModel and define Meta.rls_policies

If any of those aren’t true, migrate alone won’t create policies, and python manage.py enable_rls is the correct fix (exactly what you did).

When you add a new RLSModel or change policies
Re-run:

python manage.py enable_rls
# or for one app:
python manage.py enable_rls --app your_app_label

Useful checks: python manage.py rls_status and (on 1.0+) python manage.py audit_rls.

For deploys, the reliable pattern is: migrate, then enable_rls (or rely on AUTO_ENABLE_RLS and verify with audit_rls in CI).

You’re also right that the ERP tutorial should spell this out — I’ve tracked that as a docs gap: [DOCS] Document enable_rls / AUTO_ENABLE_RLS explicitly in Getting Started and ERP tutorial · Issue #78 · kdpisda/django-rls · GitHub

Package source is GitHub - kdpisda/django-rls: Row Level Security for Django · GitHub and docs at https://django-rls.com. If AUTO_ENABLE_RLS was on and the backend was correct but policies still didn’t appear after migrate, reply with your DJANGO_RLS settings and DB ENGINE and we can dig in.

— Kuldeep