User-Based Access Control
Implement fine-grained user-based access control for personal data, documents, and resources.
User-Owned Resources
The most common pattern is resources owned by individual users:
Simple User Ownership
from django.db import models
from django.contrib.auth.models import User
from django.db.models import Q
from django_rls.models import RLSModel
from django_rls.policies import ModelPolicy, RLS
class PersonalNote(RLSModel):
title = models.CharField(max_length=200)
content = models.TextField()
owner = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
rls_policies = [
# Standard "View Own Data" pattern
ModelPolicy('owner_policy', filters=Q(owner=RLS.user_id()))
]
class UserProfile(RLSModel):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField()
avatar = models.ImageField(upload_to='avatars/')
is_public = models.BooleanField(default=False)
class Meta:
rls_policies = [
# Owner can see profile OR Anyone can see if public
ModelPolicy(
'access_policy',
filters=(
Q(user=RLS.user_id()) |
Q(is_public=True)
)
)
]
Collaborative Features
Shared Documents
Implement Google Docs-style sharing using relations.
class Document(RLSModel):
title = models.CharField(max_length=200)
content = models.TextField()
owner = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
rls_policies = [
# Access if Owner OR if shared via DocumentShare
ModelPolicy(
'access_policy',
filters=(
Q(owner=RLS.user_id()) |
Q(documentshare__shared_with=RLS.user_id()) # Automatic EXISTS subquery!
)
)
]
class DocumentShare(RLSModel):
document = models.ForeignKey(Document, on_delete=models.CASCADE)
shared_with = models.ForeignKey(User, on_delete=models.CASCADE)
permission = models.CharField(max_length=10, choices=[('view', 'View'), ('edit', 'Edit')])
class Meta:
rls_policies = [
# Visibility:
# 1. Document Owner can see shares (to manage them)
# 2. The Shared User can see their own share entry
ModelPolicy(
'share_visibility',
filters=(
Q(document__owner=RLS.user_id()) | # Owner of the parent doc
Q(shared_with=RLS.user_id()) # The user it's shared with
)
)
]
Team Collaboration
Team members access resources based on membership.
class Team(models.Model):
name = models.CharField(max_length=100)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
class TeamMember(models.Model):
team = models.ForeignKey(Team, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class TeamResource(RLSModel):
team = models.ForeignKey(Team, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
class Meta:
rls_policies = [
# Access if current user is a member of the resource's team
ModelPolicy(
'team_access',
filters=Q(team__teammember__user=RLS.user_id())
)
]
Note: django-rls automatically converts the team__teammember__user lookup into an efficient EXISTS subquery.
Hierarchical Access
Manager-Employee Structure
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
manager = models.ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL)
class PerformanceReview(RLSModel):
employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
reviewer = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='reviews_given')
content = models.TextField()
class Meta:
rls_policies = [
ModelPolicy(
'review_visibility',
filters=(
# 1. Employee sees own review
Q(employee__user=RLS.user_id()) |
# 2. Manager sees their direct reports' reviews
Q(employee__manager__user=RLS.user_id()) |
# 3. Reviewer sees reviews they wrote
Q(reviewer__user=RLS.user_id())
)
)
]
Privacy Controls
Granular Privacy Settings
class UserContent(RLSModel):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
visibility = models.CharField(max_length=20, choices=[
('private', 'Private'),
('friends', 'Friends Only'),
('public', 'Public'),
])
class Meta:
rls_policies = [
ModelPolicy(
'privacy_policy',
filters=(
Q(owner=RLS.user_id()) | # Owner
Q(visibility='public') | # Public
(
Q(visibility='friends') & # Friends Only
Q(owner__friendship__friend=RLS.user_id(),
owner__friendship__status='accepted')
)
)
)
]
Admin Override
Sometimes admins need to bypass standard rules.
class SupportTicket(RLSModel):
user = models.ForeignKey(User, on_delete=models.CASCADE)
# ... fields
class Meta:
rls_policies = [
ModelPolicy(
'access_policy',
filters=(
Q(user=RLS.user_id()) | # Ticket Owner
Q(assigned_to=RLS.user_id()) | # Assigned Staff
Q(RLS.context('user_is_staff') == 'true') # Global Admin Override
)
)
]
Implementation Tips
1. User Context Helper
To support custom context like user_is_staff, use a Context Processor:
# settings.py
RLS_CONTEXT_PROCESSORS = [
'myapp.context.user_role_context'
]
# myapp/context.py
def user_role_context(request):
if request.user.is_authenticated:
return {
'user_is_staff': str(request.user.is_staff).lower()
}
return {}
2. Indexes
Always index fields used in lookups for performance:
class OptimizedDocument(RLSModel):
owner = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
class Meta:
indexes = [
models.Index(fields=['owner', '-created_at']),
]