Authentication & Permissions
Implement user authentication, authorization, and permission systems in Django applications. This is a foundational concept in Python web development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world Python/Django experience. Take your time with each section and practice the examples
Django Authentication System
Django provides a comprehensive authentication system that handles user accounts, groups, permissions, and sessions. It's highly customizable and secure by default.. This is an essential concept that every Python/Django developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Authentication Implementation
# views.py
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import UserRegistrationForm, UserLoginForm
def register(request):
if request.method == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
messages.success(request, 'Registration successful! Welcome to our site.')
return redirect('home')
else:
form = UserRegistrationForm()
return render(request, 'registration/register.html', {'form': form})
def user_login(request):
if request.method == 'POST':
form = UserLoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
messages.success(request, f'Welcome back, {user.username}!')
return redirect('home')
else:
messages.error(request, 'Invalid username or password.')
else:
form = UserLoginForm()
return render(request, 'registration/login.html', {'form': form})
@login_required
def user_logout(request):
logout(request)
messages.info(request, 'You have been logged out.')
return redirect('home')
@login_required
def profile(request):
return render(request, 'registration/profile.html')
# Permission-based views
@permission_required('blog.add_post')
def create_post(request):
# Only users with 'add_post' permission can access
pass
@permission_required('blog.change_post')
def edit_post(request, pk):
# Only users with 'change_post' permission can access
pass
# Class-based views with mixins
from django.views.generic import CreateView, UpdateView, DeleteView
class PostCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
permission_required = 'blog.add_post'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
permission_required = 'blog.change_post'
def get_queryset(self):
# Users can only edit their own posts
return Post.objects.filter(author=self.request.user)
# Custom permissions
class Post(models.Model):
# ... fields ...
class Meta:
permissions = [
("can_publish", "Can publish post"),
("can_feature", "Can feature post"),
("can_moderate", "Can moderate comments"),
]
# Permission checking in templates
"""
{% if perms.blog.add_post %}
<a href="{% url 'post_create' %}" class="btn btn-primary">Create Post</a>
{% endif %}
{% if perms.blog.change_post %}
<a href="{% url 'post_edit' post.pk %}" class="btn btn-secondary">Edit</a>
{% endif %}
{% if perms.blog.delete_post %}
<a href="{% url 'post_delete' post.pk %}" class="btn btn-danger">Delete</a>
{% endif %}
"""Practice Exercise: Role-Based Access Control
# Role-Based Access Control (RBAC) System
from django.contrib.auth.models import AbstractUser, Group, Permission
from django.db import models
from django.contrib.contenttypes.models import ContentType
class Role(models.Model):
"""Custom role model for RBAC"""
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True)
permissions = models.ManyToManyField(Permission, blank=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
class Meta:
ordering = ['name']
class UserProfile(models.Model):
"""Extended user profile with roles"""
user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
roles = models.ManyToManyField(Role, blank=True)
department = models.CharField(max_length=100, blank=True)
employee_id = models.CharField(max_length=50, unique=True, blank=True)
def __str__(self):
return f"{self.user.username}'s profile"
def has_role(self, role_name):
"""Check if user has a specific role"""
return self.roles.filter(name=role_name, is_active=True).exists()
def has_permission(self, permission_name):
"""Check if user has a specific permission through roles"""
for role in self.roles.filter(is_active=True):
if role.permissions.filter(codename=permission_name).exists():
return True
return False
def get_all_permissions(self):
"""Get all permissions from user's roles"""
permissions = set()
for role in self.roles.filter(is_active=True):
permissions.update(role.permissions.all())
return permissions
class RBACMiddleware:
"""Middleware to add RBAC functionality to request"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
# Add user profile to request
try:
request.user_profile = request.user.userprofile
except UserProfile.DoesNotExist:
request.user_profile = None
response = self.get_response(request)
return response
# Custom decorators for RBAC
from functools import wraps
from django.core.exceptions import PermissionDenied
def role_required(role_name):
"""Decorator to check if user has a specific role"""
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if hasattr(request, 'user_profile') and request.user_profile:
if request.user_profile.has_role(role_name):
return view_func(request, *args, **kwargs)
raise PermissionDenied("You don't have the required role.")
return _wrapped_view
return decorator
def permission_required_rbac(permission_name):
"""Decorator to check if user has a specific permission through RBAC"""
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if hasattr(request, 'user_profile') and request.user_profile:
if request.user_profile.has_permission(permission_name):
return view_func(request, *args, **kwargs)
raise PermissionDenied("You don't have the required permission.")
return _wrapped_view
return decorator
# Usage examples
@role_required('editor')
def edit_post(request, pk):
# Only users with 'editor' role can access
pass
@permission_required_rbac('can_publish')
def publish_post(request, pk):
# Only users with 'can_publish' permission can access
pass
# Role management views
@login_required
@role_required('admin')
def manage_roles(request):
"""Admin view to manage user roles"""
if request.method == 'POST':
user_id = request.POST.get('user_id')
role_id = request.POST.get('role_id')
action = request.POST.get('action')
try:
user = User.objects.get(id=user_id)
role = Role.objects.get(id=role_id)
if action == 'add':
user.userprofile.roles.add(role)
messages.success(request, f'Role {role.name} added to {user.username}')
elif action == 'remove':
user.userprofile.roles.remove(role)
messages.success(request, f'Role {role.name} removed from {user.username}')
except (User.DoesNotExist, Role.DoesNotExist):
messages.error(request, 'User or role not found.')
users = User.objects.all()
roles = Role.objects.all()
return render(request, 'admin/manage_roles.html', {
'users': users,
'roles': roles
})