Django's permission system provides fine-grained access control for your application's resources. Understanding how to create, assign, and check permissions enables you to build secure applications with precise authorization controls.
# Django's Permission model
from django.contrib.auth.models import Permission, Group
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
User = get_user_model()
class PermissionSystemOverview:
"""Comprehensive overview of Django's permission system"""
@staticmethod
def permission_model_structure():
"""Understand the Permission model structure"""
# Permission model fields
permission_fields = {
'name': 'Human-readable permission name',
'codename': 'Machine-readable permission identifier',
'content_type': 'Links permission to a specific model',
}
# Example: Get all permissions for the User model
user_content_type = ContentType.objects.get_for_model(User)
user_permissions = Permission.objects.filter(content_type=user_content_type)
permission_examples = []
for perm in user_permissions:
permission_examples.append({
'name': perm.name,
'codename': perm.codename,
'full_name': f"{perm.content_type.app_label}.{perm.codename}"
})
return {
'fields': permission_fields,
'examples': permission_examples
}
@staticmethod
def default_permissions():
"""Django automatically creates default permissions for each model"""
# For any model, Django creates these default permissions:
default_perms = {
'add': 'Can add {model_name}',
'change': 'Can change {model_name}',
'delete': 'Can delete {model_name}',
'view': 'Can view {model_name}', # Added in Django 2.1
}
# Example with a Post model
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
# Django automatically creates:
# - blog.add_post
# - blog.change_post
# - blog.delete_post
# - blog.view_post
return default_perms
@staticmethod
def custom_permissions():
"""Create custom permissions for specific business logic"""
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.CharField(max_length=20, default='draft')
class Meta:
permissions = [
('can_publish', 'Can publish posts'),
('can_feature', 'Can feature posts on homepage'),
('can_moderate', 'Can moderate post comments'),
('can_archive', 'Can archive old posts'),
('can_view_analytics', 'Can view post analytics'),
]
# These create permissions like:
# - blog.can_publish
# - blog.can_feature
# - blog.can_moderate
# etc.
return Post
@staticmethod
def programmatic_permission_creation():
"""Create permissions programmatically"""
# Get content type for model
content_type = ContentType.objects.get_for_model(Post)
# Create custom permission
permission, created = Permission.objects.get_or_create(
codename='can_bulk_edit',
name='Can bulk edit posts',
content_type=content_type,
)
# Create multiple permissions
custom_permissions = [
('can_export_data', 'Can export post data'),
('can_import_data', 'Can import post data'),
('can_manage_categories', 'Can manage post categories'),
]
created_permissions = []
for codename, name in custom_permissions:
perm, created = Permission.objects.get_or_create(
codename=codename,
name=name,
content_type=content_type,
)
created_permissions.append((perm, created))
return created_permissions
# Permission assignment and checking
class PermissionManagement:
"""Manage permission assignment and checking"""
@staticmethod
def assign_permissions_to_users():
"""Assign permissions directly to users"""
user = User.objects.get(username='editor')
# Get specific permissions
add_post_perm = Permission.objects.get(codename='add_post')
change_post_perm = Permission.objects.get(codename='change_post')
publish_perm = Permission.objects.get(codename='can_publish')
# Assign single permission
user.user_permissions.add(add_post_perm)
# Assign multiple permissions
user.user_permissions.add(change_post_perm, publish_perm)
# Assign permissions by codename (more convenient)
user.user_permissions.add(
Permission.objects.get(codename='can_feature')
)
# Remove permissions
user.user_permissions.remove(publish_perm)
# Set permissions (replaces all existing)
permissions = Permission.objects.filter(
codename__in=['add_post', 'change_post', 'can_publish']
)
user.user_permissions.set(permissions)
return user.user_permissions.all()
@staticmethod
def assign_permissions_to_groups():
"""Assign permissions to groups"""
# Create or get group
editors_group, created = Group.objects.get_or_create(name='Editors')
# Get permissions for blog app
blog_permissions = Permission.objects.filter(
content_type__app_label='blog'
)
# Assign all blog permissions to editors
editors_group.permissions.set(blog_permissions)
# Create role-specific groups with specific permissions
role_permissions = {
'Authors': ['add_post', 'change_post', 'view_post'],
'Moderators': ['change_post', 'delete_post', 'can_moderate'],
'Publishers': ['can_publish', 'can_feature', 'can_archive'],
'Analysts': ['view_post', 'can_view_analytics'],
}
created_groups = {}
for role, perm_codenames in role_permissions.items():
group, created = Group.objects.get_or_create(name=role)
permissions = Permission.objects.filter(
codename__in=perm_codenames
)
group.permissions.set(permissions)
created_groups[role] = {
'group': group,
'permissions': list(permissions.values_list('codename', flat=True))
}
return created_groups
@staticmethod
def check_permissions():
"""Check user permissions in various ways"""
user = User.objects.get(username='editor')
# Check single permission
can_add_post = user.has_perm('blog.add_post')
# Check multiple permissions (all must be True)
can_edit_and_publish = user.has_perms([
'blog.change_post',
'blog.can_publish'
])
# Check module permissions (any permission in the app)
has_blog_perms = user.has_module_perms('blog')
# Get all user permissions
all_permissions = user.get_all_permissions()
# Get only group permissions
group_permissions = user.get_group_permissions()
# Get only direct user permissions
user_permissions = user.user_permissions.values_list('codename', flat=True)
permission_check_results = {
'can_add_post': can_add_post,
'can_edit_and_publish': can_edit_and_publish,
'has_blog_perms': has_blog_perms,
'all_permissions': list(all_permissions),
'group_permissions': list(group_permissions),
'direct_permissions': list(user_permissions),
}
return permission_check_results
# Object-level permissions for fine-grained access control
class ObjectLevelPermissions:
"""Implement object-level permission checking"""
@staticmethod
def basic_object_permission_checking():
"""Basic object-level permission patterns"""
def can_edit_post(user, post):
"""Check if user can edit a specific post"""
# Superusers can edit anything
if user.is_superuser:
return True
# Authors can edit their own posts
if post.author == user:
return True
# Editors can edit any post
if user.groups.filter(name='Editors').exists():
return True
# Users with specific permission can edit
if user.has_perm('blog.change_post'):
return True
return False
def can_delete_post(user, post):
"""Check if user can delete a specific post"""
# Only superusers and post authors can delete
if user.is_superuser:
return True
if post.author == user and user.has_perm('blog.delete_post'):
return True
# Editors can delete any post if they have permission
if (user.groups.filter(name='Editors').exists() and
user.has_perm('blog.delete_post')):
return True
return False
def can_publish_post(user, post):
"""Check if user can publish a specific post"""
# Must have publish permission
if not user.has_perm('blog.can_publish'):
return False
# Authors can publish their own posts
if post.author == user:
return True
# Publishers and editors can publish any post
if user.groups.filter(name__in=['Publishers', 'Editors']).exists():
return True
return False
return can_edit_post, can_delete_post, can_publish_post
@staticmethod
def django_guardian_integration():
"""Using django-guardian for advanced object permissions"""
# Note: Requires 'pip install django-guardian'
try:
from guardian.shortcuts import assign_perm, remove_perm, get_perms
from guardian.decorators import permission_required_or_403
from guardian.mixins import PermissionRequiredMixin
class GuardianPermissionExamples:
"""Examples using django-guardian"""
@staticmethod
def assign_object_permissions():
"""Assign permissions to specific objects"""
user = User.objects.get(username='collaborator')
post = Post.objects.get(pk=1)
# Assign object-specific permission
assign_perm('change_post', user, post)
assign_perm('view_post', user, post)
# Assign to group
group = Group.objects.get(name='Collaborators')
assign_perm('view_post', group, post)
return "Permissions assigned"
@staticmethod
def check_object_permissions():
"""Check object-specific permissions"""
user = User.objects.get(username='collaborator')
post = Post.objects.get(pk=1)
# Check specific permission for object
can_change = user.has_perm('blog.change_post', post)
# Get all permissions for object
perms = get_perms(user, post)
return {
'can_change': can_change,
'all_perms': perms
}
@staticmethod
def remove_object_permissions():
"""Remove object-specific permissions"""
user = User.objects.get(username='collaborator')
post = Post.objects.get(pk=1)
# Remove specific permission
remove_perm('change_post', user, post)
return "Permission removed"
# Decorator for object-level permissions
@permission_required_or_403('blog.change_post', (Post, 'pk', 'post_id'))
def edit_post_view(request, post_id):
"""View that requires object-level permission"""
post = get_object_or_404(Post, pk=post_id)
# User has permission for this specific post
return render(request, 'edit_post.html', {'post': post})
# Class-based view with object permissions
class PostEditView(PermissionRequiredMixin, UpdateView):
model = Post
permission_required = 'blog.change_post'
return_403 = True # Return 403 instead of redirect
def get_object(self):
"""Get object and check permissions"""
obj = super().get_object()
# Additional object-level checks can be added here
return obj
return GuardianPermissionExamples
except ImportError:
return "django-guardian not installed"
@staticmethod
def custom_object_permission_backend():
"""Custom backend for object-level permissions"""
from django.contrib.auth.backends import BaseBackend
class ObjectPermissionBackend(BaseBackend):
"""Custom backend for object-level permissions"""
def has_perm(self, user_obj, perm, obj=None):
"""Check if user has permission for specific object"""
if not user_obj.is_active:
return False
if obj is None:
return False
# Parse permission
app_label, codename = perm.split('.', 1)
# Custom object permission logic
if isinstance(obj, Post):
return self.has_post_permission(user_obj, codename, obj)
return False
def has_post_permission(self, user, codename, post):
"""Check post-specific permissions"""
# Authors can always edit their own posts
if codename == 'change_post' and post.author == user:
return True
# Only authors can delete their own posts
if codename == 'delete_post' and post.author == user:
return True
# Published posts can be viewed by anyone
if codename == 'view_post' and post.status == 'published':
return True
# Draft posts can only be viewed by author and editors
if codename == 'view_post' and post.status == 'draft':
if post.author == user:
return True
if user.groups.filter(name='Editors').exists():
return True
# Editors can publish any post
if codename == 'can_publish':
if user.groups.filter(name__in=['Editors', 'Publishers']).exists():
return True
return False
def get_all_permissions(self, user_obj, obj=None):
"""Get all permissions for user on object"""
if not user_obj.is_active or obj is None:
return set()
permissions = set()
# Check each possible permission
possible_perms = [
'blog.view_post',
'blog.change_post',
'blog.delete_post',
'blog.can_publish'
]
for perm in possible_perms:
if self.has_perm(user_obj, perm, obj):
permissions.add(perm)
return permissions
return ObjectPermissionBackend
# Advanced permission patterns
class AdvancedPermissionPatterns:
"""Advanced permission management patterns"""
@staticmethod
def conditional_permissions():
"""Implement conditional permissions based on context"""
def get_conditional_permissions(user, obj, context=None):
"""Get permissions based on current context"""
permissions = set()
# Time-based permissions
current_hour = timezone.now().hour
if 9 <= current_hour <= 17: # Business hours
permissions.add('blog.can_publish')
# Location-based permissions (if available)
if context and context.get('ip_address'):
# Allow certain actions only from office IP
office_ips = ['192.168.1.0/24', '10.0.0.0/8']
# Implementation would check if IP is in allowed ranges
permissions.add('blog.can_moderate')
# Status-based permissions
if isinstance(obj, Post):
if obj.status == 'draft' and obj.author == user:
permissions.update([
'blog.change_post',
'blog.delete_post'
])
elif obj.status == 'published':
permissions.add('blog.view_post')
# Role-based with conditions
user_groups = user.groups.values_list('name', flat=True)
if 'Editors' in user_groups:
permissions.update([
'blog.change_post',
'blog.can_publish',
'blog.can_feature'
])
# Senior editors get additional permissions
if user.date_joined < timezone.now() - timedelta(days=365):
permissions.add('blog.can_archive')
return permissions
return get_conditional_permissions
@staticmethod
def permission_caching():
"""Implement permission caching for performance"""
from django.core.cache import cache
def get_cached_permissions(user, obj=None, cache_timeout=300):
"""Get permissions with caching"""
# Create cache key
if obj:
cache_key = f"perms_{user.id}_{obj._meta.label}_{obj.pk}"
else:
cache_key = f"perms_{user.id}_global"
# Try to get from cache
permissions = cache.get(cache_key)
if permissions is None:
# Calculate permissions
if obj:
permissions = list(user.get_all_permissions(obj))
else:
permissions = list(user.get_all_permissions())
# Cache the result
cache.set(cache_key, permissions, cache_timeout)
return set(permissions)
def invalidate_permission_cache(user, obj=None):
"""Invalidate cached permissions"""
if obj:
cache_key = f"perms_{user.id}_{obj._meta.label}_{obj.pk}"
else:
cache_key = f"perms_{user.id}_global"
cache.delete(cache_key)
# Also invalidate global permissions
global_key = f"perms_{user.id}_global"
cache.delete(global_key)
return get_cached_permissions, invalidate_permission_cache
@staticmethod
def permission_inheritance():
"""Implement permission inheritance hierarchies"""
def get_inherited_permissions(user, obj):
"""Get permissions including inherited ones"""
permissions = set()
# Direct permissions
permissions.update(user.get_all_permissions())
# Inherit from object hierarchy
if hasattr(obj, 'parent') and obj.parent:
parent_perms = get_inherited_permissions(user, obj.parent)
permissions.update(parent_perms)
# Inherit from related objects
if isinstance(obj, Post) and obj.category:
# If user has permissions on category, inherit for posts
category_perms = user.get_all_permissions(obj.category)
# Map category permissions to post permissions
perm_mapping = {
'blog.change_category': 'blog.change_post',
'blog.view_category': 'blog.view_post',
}
for cat_perm, post_perm in perm_mapping.items():
if cat_perm in category_perms:
permissions.add(post_perm)
return permissions
return get_inherited_permissions
@staticmethod
def dynamic_permission_assignment():
"""Dynamically assign permissions based on rules"""
def auto_assign_permissions():
"""Automatically assign permissions based on user activity"""
from django.db.models import Count
# Users who create many posts get author permissions
prolific_users = User.objects.annotate(
post_count=Count('posts')
).filter(post_count__gte=5)
author_permissions = Permission.objects.filter(
codename__in=['add_post', 'change_post']
)
for user in prolific_users:
user.user_permissions.add(*author_permissions)
# Users with high-quality posts get editor permissions
# (This would require a quality scoring system)
quality_users = User.objects.filter(
posts__average_rating__gte=4.0
).distinct()
editor_group, created = Group.objects.get_or_create(name='Quality Editors')
for user in quality_users:
user.groups.add(editor_group)
return {
'prolific_users': prolific_users.count(),
'quality_users': quality_users.count()
}
def revoke_inactive_permissions():
"""Revoke permissions from inactive users"""
# Users inactive for 90 days lose special permissions
inactive_threshold = timezone.now() - timedelta(days=90)
inactive_users = User.objects.filter(
last_login__lt=inactive_threshold
)
# Remove from special groups
special_groups = Group.objects.filter(
name__in=['Editors', 'Moderators', 'Publishers']
)
for user in inactive_users:
user.groups.remove(*special_groups)
# Remove direct permissions except basic ones
basic_perms = ['view_post']
user.user_permissions.exclude(
codename__in=basic_perms
).delete()
return inactive_users.count()
return auto_assign_permissions, revoke_inactive_permissions
# Protecting views with permission decorators and mixins
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.core.exceptions import PermissionDenied
from functools import wraps
class ViewProtection:
"""Protect views with various permission patterns"""
@staticmethod
def basic_permission_decorators():
"""Basic permission decorators for function-based views"""
# Require login
@login_required
def profile_view(request):
return render(request, 'profile.html')
# Require specific permission
@permission_required('blog.add_post')
def create_post_view(request):
return render(request, 'create_post.html')
# Require multiple permissions
@permission_required(['blog.add_post', 'blog.can_publish'])
def create_and_publish_view(request):
return render(request, 'create_publish.html')
# Raise exception instead of redirect
@permission_required('blog.change_post', raise_exception=True)
def edit_post_view(request, post_id):
post = get_object_or_404(Post, pk=post_id)
return render(request, 'edit_post.html', {'post': post})
return [profile_view, create_post_view, create_and_publish_view, edit_post_view]
@staticmethod
def custom_permission_decorators():
"""Custom permission decorators"""
def group_required(*group_names):
"""Decorator to require group membership"""
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect('login')
user_groups = request.user.groups.values_list('name', flat=True)
if not any(group in user_groups for group in group_names):
raise PermissionDenied("Insufficient group membership")
return view_func(request, *args, **kwargs)
return wrapper
return decorator
def object_permission_required(perm, model, pk_field='pk'):
"""Decorator for object-level permissions"""
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect('login')
# Get object
obj_pk = kwargs.get(pk_field)
obj = get_object_or_404(model, pk=obj_pk)
# Check permission
if not request.user.has_perm(perm, obj):
raise PermissionDenied("Insufficient object permissions")
# Add object to kwargs for convenience
kwargs['object'] = obj
return view_func(request, *args, **kwargs)
return wrapper
return decorator
def conditional_permission_required(condition_func):
"""Decorator for conditional permissions"""
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect('login')
# Check condition
if not condition_func(request.user, request, *args, **kwargs):
raise PermissionDenied("Condition not met")
return view_func(request, *args, **kwargs)
return wrapper
return decorator
# Usage examples
@group_required('Editors', 'Administrators')
def admin_panel_view(request):
return render(request, 'admin_panel.html')
@object_permission_required('blog.change_post', Post, 'post_id')
def edit_post_view(request, post_id, object=None):
# object is automatically added by decorator
return render(request, 'edit_post.html', {'post': object})
def can_edit_during_business_hours(user, request, *args, **kwargs):
current_hour = timezone.now().hour
return 9 <= current_hour <= 17 or user.is_superuser
@conditional_permission_required(can_edit_during_business_hours)
def time_restricted_edit_view(request, post_id):
post = get_object_or_404(Post, pk=post_id)
return render(request, 'edit_post.html', {'post': post})
return {
'group_required': group_required,
'object_permission_required': object_permission_required,
'conditional_permission_required': conditional_permission_required
}
@staticmethod
def class_based_view_mixins():
"""Permission mixins for class-based views"""
# Basic permission mixins
class PostCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
model = Post
permission_required = 'blog.add_post'
template_name = 'create_post.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostEditView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = Post
permission_required = 'blog.change_post'
template_name = 'edit_post.html'
# Custom permission mixins
class GroupRequiredMixin:
"""Mixin to require group membership"""
group_required = None
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if self.group_required:
user_groups = request.user.groups.values_list('name', flat=True)
if isinstance(self.group_required, str):
required_groups = [self.group_required]
else:
required_groups = self.group_required
if not any(group in user_groups for group in required_groups):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class ObjectOwnerMixin:
"""Mixin to restrict access to object owners"""
owner_field = 'author'
def get_object(self, queryset=None):
obj = super().get_object(queryset)
# Check if user owns the object
owner = getattr(obj, self.owner_field, None)
if owner != self.request.user and not self.request.user.is_superuser:
raise PermissionDenied("You don't own this object")
return obj
class ConditionalPermissionMixin:
"""Mixin for conditional permissions"""
def check_permissions(self, request):
"""Override this method to implement custom permission logic"""
return True
def dispatch(self, request, *args, **kwargs):
if not self.check_permissions(request):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
# Usage examples
class EditorOnlyView(GroupRequiredMixin, ListView):
model = Post
group_required = ['Editors', 'Administrators']
template_name = 'editor_posts.html'
class MyPostsView(ObjectOwnerMixin, ListView):
model = Post
owner_field = 'author'
template_name = 'my_posts.html'
def get_queryset(self):
return Post.objects.filter(author=self.request.user)
class BusinessHoursEditView(ConditionalPermissionMixin, UpdateView):
model = Post
template_name = 'edit_post.html'
def check_permissions(self, request):
current_hour = timezone.now().hour
return 9 <= current_hour <= 17 or request.user.is_superuser
return {
'PostCreateView': PostCreateView,
'PostEditView': PostEditView,
'GroupRequiredMixin': GroupRequiredMixin,
'ObjectOwnerMixin': ObjectOwnerMixin,
'ConditionalPermissionMixin': ConditionalPermissionMixin
}
# Permission utilities
class PermissionUtilities:
"""Utility functions for permission management"""
@staticmethod
def get_user_permission_summary(user):
"""Get comprehensive permission summary for user"""
summary = {
'user_info': {
'username': user.username,
'is_active': user.is_active,
'is_staff': user.is_staff,
'is_superuser': user.is_superuser,
},
'groups': list(user.groups.values_list('name', flat=True)),
'direct_permissions': list(
user.user_permissions.values_list('codename', flat=True)
),
'group_permissions': list(user.get_group_permissions()),
'all_permissions': list(user.get_all_permissions()),
}
# Organize permissions by app
permissions_by_app = {}
for perm in user.get_all_permissions():
app_label, codename = perm.split('.', 1)
if app_label not in permissions_by_app:
permissions_by_app[app_label] = []
permissions_by_app[app_label].append(codename)
summary['permissions_by_app'] = permissions_by_app
return summary
@staticmethod
def bulk_permission_operations():
"""Perform bulk permission operations"""
def assign_permissions_to_multiple_users(users, permissions):
"""Assign permissions to multiple users"""
for user in users:
user.user_permissions.add(*permissions)
def create_role_based_groups():
"""Create standard role-based groups"""
roles = {
'Content Creators': [
'add_post', 'change_post', 'view_post'
],
'Content Editors': [
'add_post', 'change_post', 'delete_post', 'view_post',
'can_publish', 'can_feature'
],
'Content Moderators': [
'view_post', 'change_post', 'can_moderate'
],
'Content Administrators': [
'add_post', 'change_post', 'delete_post', 'view_post',
'can_publish', 'can_feature', 'can_moderate', 'can_archive'
]
}
created_groups = {}
for role_name, perm_codenames in roles.items():
group, created = Group.objects.get_or_create(name=role_name)
permissions = Permission.objects.filter(
codename__in=perm_codenames
)
group.permissions.set(permissions)
created_groups[role_name] = {
'group': group,
'created': created,
'permissions': list(permissions.values_list('codename', flat=True))
}
return created_groups
def cleanup_unused_permissions():
"""Clean up unused permissions"""
# Find permissions not assigned to any user or group
unused_permissions = Permission.objects.filter(
user__isnull=True,
group__isnull=True
)
# Find empty groups
empty_groups = Group.objects.annotate(
user_count=Count('user'),
permission_count=Count('permissions')
).filter(user_count=0, permission_count=0)
return {
'unused_permissions': unused_permissions.count(),
'empty_groups': empty_groups.count()
}
return {
'assign_permissions_to_multiple_users': assign_permissions_to_multiple_users,
'create_role_based_groups': create_role_based_groups,
'cleanup_unused_permissions': cleanup_unused_permissions
}
Django's permission system provides the foundation for building sophisticated authorization schemes. By understanding how to create, assign, and check permissions at both the model and object level, you can implement fine-grained access control that scales with your application's complexity.
Users and Groups
Django's User and Group models form the foundation of the authentication and authorization system. Understanding how to work with users, organize them into groups, and manage their relationships is crucial for implementing effective access control in your Django applications.
Password Management
Secure password management is critical for protecting user accounts and maintaining application security. Django provides robust password handling capabilities including hashing, validation, and secure storage. Understanding these features enables you to implement strong password policies and protect user credentials.