Basic Usage
Let's build a simple task management application with RLS to understand the basics.
The Problem
In a typical Django application, you need to remember to filter querysets:
# Without RLS - Easy to forget filtering!
def task_list(request):
    # WRONG: Shows all tasks to everyone
    tasks = Task.objects.all()
    
    # RIGHT: Must remember to filter
    tasks = Task.objects.filter(owner=request.user)
    return render(request, 'tasks.html', {'tasks': tasks})
The RLS Solution
With Django RLS, filtering happens automatically at the database level:
1. Define Your Model
from django.db import models
from django.contrib.auth.models import User
from django_rls.models import RLSModel
from django_rls.policies import UserPolicy
class Task(RLSModel):
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    completed = models.BooleanField(default=False)
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        ordering = ['-created_at']
        rls_policies = [
            UserPolicy('owner_policy', user_field='owner'),
        ]
    
    def __str__(self):
        return self.title
2. Create Views
Your views become simpler and more secure:
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .models import Task
from .forms import TaskForm
@login_required
def task_list(request):
    # Automatically filtered to current user's tasks!
    tasks = Task.objects.all()
    return render(request, 'tasks/list.html', {'tasks': tasks})
@login_required
def task_create(request):
    if request.method == 'POST':
        form = TaskForm(request.POST)
        if form.is_valid():
            task = form.save(commit=False)
            task.owner = request.user  # Set owner
            task.save()
            return redirect('task_list')
    else:
        form = TaskForm()
    return render(request, 'tasks/form.html', {'form': form})
@login_required
def task_update(request, pk):
    # Will only find tasks owned by current user
    task = get_object_or_404(Task, pk=pk)
    # ... rest of view
3. Enable RLS
After creating your models, enable RLS:
python manage.py makemigrations
python manage.py migrate
python manage.py enable_rls
What's Happening Behind the Scenes
When RLS is enabled, PostgreSQL automatically adds WHERE clauses:
-- What you write:
SELECT * FROM myapp_task;
-- What PostgreSQL executes for user_id=1:
SELECT * FROM myapp_task 
WHERE owner_id = current_setting('rls.user_id')::integer;
Complete Example Application
Models (models.py)
from django.db import models
from django.contrib.auth.models import User
from django_rls.models import RLSModel
from django_rls.policies import UserPolicy
class Project(RLSModel):
    name = models.CharField(max_length=100)
    description = models.TextField()
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        rls_policies = [
            UserPolicy('owner_policy', user_field='owner'),
        ]
class Task(RLSModel):
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    assigned_to = models.ForeignKey(User, on_delete=models.CASCADE)
    completed = models.BooleanField(default=False)
    due_date = models.DateField(null=True, blank=True)
    
    class Meta:
        rls_policies = [
            UserPolicy('assigned_policy', user_field='assigned_to'),
        ]
Views (views.py)
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.db.models import Count
from .models import Project, Task
@login_required
def dashboard(request):
    # All queries automatically filtered!
    context = {
        'projects': Project.objects.all(),
        'tasks': Task.objects.filter(completed=False),
        'completed_tasks': Task.objects.filter(completed=True).count(),
        'project_stats': Project.objects.annotate(
            task_count=Count('task')
        ),
    }
    return render(request, 'dashboard.html', context)
API Views (api.py)
from rest_framework import viewsets
from .models import Task, Project
from .serializers import TaskSerializer, ProjectSerializer
class ProjectViewSet(viewsets.ModelViewSet):
    # No need for get_queryset() filtering!
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    
    def perform_create(self, serializer):
        serializer.save(assigned_to=self.request.user)
Common Patterns
Shared Access
Allow multiple users to access the same records:
class SharedDocument(RLSModel):
    title = models.CharField(max_length=200)
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    shared_with = models.ManyToManyField(User, related_name='shared_docs')
    
    class Meta:
        rls_policies = [
            # Owner can access
            UserPolicy('owner_policy', user_field='owner'),
            # Shared users can access
            CustomPolicy(
                'shared_policy',
                expression="""
                    id IN (
                        SELECT shareddocument_id 
                        FROM myapp_shareddocument_shared_with 
                        WHERE user_id = current_setting('rls.user_id')::integer
                    )
                """
            ),
        ]
Hierarchical Access
Managers can see their team's data:
class TeamTask(RLSModel):
    title = models.CharField(max_length=200)
    assigned_to = models.ForeignKey(User, on_delete=models.CASCADE)
    
    class Meta:
        rls_policies = [
            CustomPolicy(
                'team_policy',
                expression="""
                    assigned_to_id = current_setting('rls.user_id')::integer
                    OR assigned_to_id IN (
                        SELECT id FROM auth_user 
                        WHERE manager_id = current_setting('rls.user_id')::integer
                    )
                """
            ),
        ]
Time-Based Access
Records accessible only during certain periods:
class TimedContent(RLSModel):
    title = models.CharField(max_length=200)
    available_from = models.DateTimeField()
    available_until = models.DateTimeField()
    
    class Meta:
        rls_policies = [
            CustomPolicy(
                'time_policy',
                expression="""
                    CURRENT_TIMESTAMP BETWEEN available_from AND available_until
                """
            ),
        ]
Benefits
- Security by Default: Can't accidentally expose data
- Simpler Code: No need to filter in every view
- Consistency: Same rules everywhere (views, API, admin)
- Performance: Database-optimized filtering
- Auditability: Security rules in one place