Rules
Rules are the core building blocks of Django Shield. A rule is a function that checks if a user has permission.
What is a Rule?
A rule is a Python function that:
- Takes
useras the first parameter - Optionally takes an object as the second parameter
- Returns
True(allowed) orFalse(denied)
The @rule Decorator
Use @rule to register a permission function:
from django_shield import rule
@rule
def is_authenticated(user):
return user.is_authenticated
The function name becomes the rule name. You can use this name with @guard:
@guard('is_authenticated')
def my_view(request):
...
Rules Without Object
Some rules only need the user:
@rule
def is_staff(user):
return user.is_staff
@rule
def is_superuser(user):
return user.is_superuser
@rule
def is_verified(user):
return user.email_verified
Use these for views that don't operate on a specific object:
@guard('is_staff')
def admin_dashboard(request):
...
Rules With Object
Most rules check if a user can access a specific object:
@rule
def is_author(user, post):
return post.author == user
@rule
def can_view_document(user, document):
return document.is_public or document.owner == user
@rule
def is_team_member(user, project):
return user in project.team.members.all()
Use with the model parameter:
@guard('is_author', model=Post)
def edit_post(request, pk):
...
Custom Rule Names
By default, the function name is the rule name. Use name to set a custom name:
@rule(name='can_edit')
def check_edit_permission(user, obj):
return obj.author == user or user.is_staff
Now use 'can_edit' instead of 'check_edit_permission':
@guard('can_edit', model=Post)
def edit_view(request, pk):
...
Accessing Rules from Registry
You can access registered rules programmatically:
from django_shield import RuleRegistry
# Check if a rule exists
if RuleRegistry.exists('is_author'):
rule = RuleRegistry.get('is_author')
result = rule.check(user, post)
Combining Rules
Using guard.all()
All rules must pass:
@guard.all('is_authenticated', 'is_verified', 'is_active')
def secure_view(request):
...
Using guard.any()
At least one rule must pass:
@guard.any('is_author', 'is_staff', 'is_superuser')
def edit_post(request, pk):
...
Combining in Expression Syntax
Use and, or, not:
@guard('is_author or is_staff')
def edit_post(request, pk):
...
@guard('is_authenticated and not is_banned')
def post_comment(request):
...
Best Practices
1. Keep Rules Simple
Each rule should check one thing:
# Good - single responsibility
@rule
def is_author(user, post):
return post.author == user
@rule
def is_staff(user):
return user.is_staff
# Use together
@guard.any('is_author', 'is_staff', model=Post)
# Avoid - too many responsibilities
@rule
def can_edit(user, post):
if user.is_superuser:
return True
if user.is_staff:
return True
if post.author == user:
return True
if user in post.editors.all():
return True
return False
2. Name Rules Clearly
Use descriptive names that explain the permission:
# Good
@rule
def is_project_owner(user, project):
...
@rule
def can_view_draft(user, post):
...
# Avoid
@rule
def check(user, obj):
...
@rule
def perm1(user):
...
3. Handle None Objects
When a rule might receive None:
@rule
def can_edit_post(user, post):
if post is None:
return False
return post.author == user
4. Organize Rules by Domain
Group related rules in dedicated files:
myapp/
permissions/
__init__.py
posts.py # Post-related rules
users.py # User-related rules
teams.py # Team-related rules
Import all rules in __init__.py:
# permissions/__init__.py
from .posts import *
from .users import *
from .teams import *
5. Import Rules Early
Rules must be imported before views use them. Import in your app's apps.py:
# apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
import myapp.permissions # Register all rules