1. Project Setup & Data Modeling
Let's start by defining our data model. We need to represent the organizational structure of MegaCorp and the resources we want to protect.
Installation
First, ensure you have django-rls installed:
pip install django-rls
And add it to your INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
# ...
'django_rls',
'myapp',
]
MIDDLEWARE = [
# ...
'django.contrib.auth.middleware.AuthenticationMiddleware',
# Add RLS middleware AFTER authentication
'django_rls.middleware.RLSContextMiddleware',
]
The Data Model
Copy the following into your myapp/models.py.
We need three main components:
- Department: A hierarchical tree structure.
- ERPDocument: The sensitive resource we want to protect.
- UserPermission: A table to store specific collaborative exceptions.
from django.db import models
from django.contrib.auth.models import User
from django_rls.models import RLSModel
# 1. Department Hierarchy
# Note: This is a standard Django model. We don't necessarily need RLS
# on the structure itself, everyone can likely see the org chart.
class Department(models.Model):
name = models.CharField(max_length=100)
# Self-referencing FK for hierarchy (e.g. Engineering -> Backend)
parent = models.ForeignKey(
'self',
null=True,
blank=True,
related_name='sub_departments',
on_delete=models.CASCADE
)
def __str__(self):
return self.name
# 2. Granular ACL Table
# Used to grant specific exceptions.
class UserPermission(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
document_id = models.IntegerField() # Linking to ERPDocument ID loosely or via FK
can_view = models.BooleanField(default=False)
# 3. The Protected Resource
# Inherits from RLSModel to enable Row Level Security
class ERPDocument(RLSModel):
title = models.CharField(max_length=100)
department = models.ForeignKey(Department, on_delete=models.CASCADE)
content = models.TextField()
is_confidential = models.BooleanField(default=True)
class Meta:
rls_policies = [] # We will populate this next!
Configuring the User
For our hierarchy rules to work, we need to know which Department a User belongs to. In a real app, you might have a UserProfile model.
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True)
Initial Migration
Run the migrations to create these tables in PostgreSQL.
python manage.py makemigrations
python manage.py migrate
At this stage, RLS is not yet active. We have defined the structure, but we haven't defined any policies yet. In the next chapter, we will implement the first layer of security.
A note on migrations and RLS
makemigrations only tracks Django model state — the generated migration
files never contain ENABLE ROW LEVEL SECURITY or CREATE POLICY
statements. Once ERPDocument has policies, RLS is applied separately,
after migrate, via the post_migrate signal (as long as
AUTO_ENABLE_RLS
is True, which it is by default, and DATABASES["default"]["ENGINE"] is
django_rls.backends.postgresql).
If that signal doesn't fire in your environment, or you'd rather trigger it explicitly as part of your deploy pipeline, run:
python manage.py enable_rls
Re-run this command any time you add a new RLSModel or change its
rls_policies — see Management Commands.
If you prefer to embed RLS DDL directly into a migration file instead of
relying on post_migrate, use the EnableRLS and CreatePolicy operations
from django_rls.migration_operations.