Chapter 9: User Authentication System
9.3 Permissions and Decorators
login_required Decorator
The login_required decorator is the most commonly used authentication decorator. It ensures that only logged-in users can access specific views:
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
# Basic usage
@login_required
def profile_view(request):
return render(request, 'profile.html', {'user': request.user})
# Specify login URL
@login_required(login_url='/accounts/login/')
def dashboard_view(request):
return render(request, 'dashboard.html')
# Specify login URL and redirect field name
@login_required(login_url='/accounts/login/', redirect_field_name='redirect_to')
def protected_view(request):
return render(request, 'protected.html')
# Use in class-based views
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
class ProfileView(LoginRequiredMixin, TemplateView):
template_name = 'profile.html'
login_url = '/accounts/login/'
redirect_field_name = 'redirect_to'Custom login_required decorator:
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import resolve_url
from django.conf import settings
def custom_login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
"""
Custom login_required decorator with additional logic
"""
def check_user(user):
# Check if user is logged in and email is verified
return user.is_authenticated and hasattr(user, 'emailverification') and user.emailverification.is_verified
if not login_url:
login_url = settings.LOGIN_URL
actual_decorator = user_passes_test(
check_user,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
# Use custom decorator
@custom_login_required
def verified_user_view(request):
return render(request, 'verified_content.html')Permission Checking
Django provides multiple ways to check user permissions:
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import PermissionDenied
from django.shortcuts import render
# Basic permission check decorator
@permission_required('articles.add_article')
def create_article(request):
return render(request, 'create_article.html')
# Multiple permission checks
@permission_required(['articles.add_article', 'articles.change_article'])
def edit_article(request):
return render(request, 'edit_article.html')
# Throw exception instead of redirecting when permission check fails
@permission_required('articles.delete_article', raise_exception=True)
def delete_article(request, article_id):
# Logic to delete article
pass
# Use permission check in class-based views
class ArticleCreateView(PermissionRequiredMixin, CreateView):
permission_required = 'articles.add_article'
model = Article
template_name = 'article_form.html'
fields = ['title', 'content']
# Manual permission check
def manual_permission_check(request):
if not request.user.has_perm('articles.add_article'):
raise PermissionDenied("You don't have permission to create articles")
return render(request, 'create_article.html')
# Check multiple permissions
def multiple_permissions_check(request):
required_permissions = ['articles.add_article', 'articles.change_article']
if not request.user.has_perms(required_permissions):
raise PermissionDenied("You don't have sufficient permissions")
return render(request, 'edit_article.html')
# Check permissions in templates
"""
<!-- Check permissions in templates -->
{% if perms.articles.add_article %}
<a href="{% url 'article_create' %}">Create Article</a>
{% endif %}
{% if perms.articles.change_article %}
<a href="{% url 'article_edit' article.pk %}">Edit Article</a>
{% endif %}
"""User Group Permissions
Managing permissions through user groups is Django's recommended approach:
from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
# Create and manage user groups
def setup_groups():
# Create groups
editors_group, created = Group.objects.get_or_create(name='Editors')
authors_group, created = Group.objects.get_or_create(name='Authors')
reviewers_group, created = Group.objects.get_or_create(name='Reviewers')
# Get content type. ContentType tracks metadata information of models (database tables).
content_type = ContentType.objects.get_for_model(Article)
# Create permissions
add_article = Permission.objects.get(
content_type=content_type,
codename='add_article'
)
change_article = Permission.objects.get(
content_type=content_type,
codename='change_article'
)
delete_article = Permission.objects.get(
content_type=content_type,
codename='delete_article'
)
publish_article = Permission.objects.get(
content_type=content_type,
codename='publish_article'
)
# Assign permissions to groups
# Authors group: can create and edit their own articles
authors_group.permissions.add(add_article, change_article)
# Editors group: can create, edit and delete all articles
editors_group.permissions.add(add_article, change_article, delete_article)
# Reviewers group: can publish articles
reviewers_group.permissions.add(publish_article)
# Add user to group
def add_user_to_group(username, group_name):
try:
user = User.objects.get(username=username)
group = Group.objects.get(name=group_name)
user.groups.add(group)
return True
except (User.DoesNotExist, Group.DoesNotExist):
return False
# Check if user belongs to specific group
def user_in_group(user, group_name):
return user.groups.filter(name=group_name).exists()
# Group-based decorator
def group_required(group_names, login_url=None):
"""
Decorator that requires user to belong to specified groups
"""
if not isinstance(group_names, (list, tuple)):
group_names = [group_names]
def check_group(user):
if user.is_authenticated:
return user.groups.filter(name__in=group_names).exists()
return False
return user_passes_test(check_group, login_url=login_url)
# Use group decorator
@group_required(['Editors', 'Authors'])
def article_management(request):
return render(request, 'article_management.html')
@group_required('Editors')
def delete_article_view(request, article_id):
# Logic to delete article
pass
# Use group check in class-based views
from django.contrib.auth.mixins import UserPassesTestMixin
class EditorRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.groups.filter(name='Editors').exists()
class ArticleDeleteView(EditorRequiredMixin, DeleteView):
model = Article
success_url = '/articles/'
# Group check in templates
"""
<!-- Check user groups in templates -->
{% if user.groups.all %}
<p>Your groups:
{% for group in user.groups.all %}
<span class="badge bg-primary">{{ group.name }}</span>
{% endfor %}
</p>
{% endif %}
{% if user.groups.filter(name='Editors').exists %}
<a href="{% url 'article_delete' article.pk %}" class="btn btn-danger">Delete Article</a>
{% endif %}
"""Custom Permissions
Besides Django's built-in CRUD permissions, you can create custom permissions:
# models.py
from django.db import models
from django.contrib.auth.models import User
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
permissions = [
("publish_article", "Can publish article"),
("moderate_article", "Can moderate article"),
("feature_article", "Can feature article"),
]
# Use custom permissions in views.py
from django.contrib.auth.decorators import permission_required
@permission_required('articles.publish_article')
def publish_article(request, article_id):
article = get_object_or_404(Article, id=article_id)
article.is_published = True
article.save()
return redirect('article_list')
# Create more complex permission checks
def can_edit_article(user, article):
"""
Check if user can edit article
"""
# Superusers can edit all articles
if user.is_superuser:
return True
# Article authors can edit their own articles
if user == article.author:
return True
# Editors group users can edit all articles
if user.groups.filter(name='Editors').exists():
return True
# User has edit permission
if user.has_perm('articles.change_article'):
return True
return False
# Use complex permission checks in views
def edit_article(request, article_id):
article = get_object_or_404(Article, id=article_id)
if not can_edit_article(request.user, article):
raise PermissionDenied("You don't have permission to edit this article")
# Edit article logic
if request.method == 'POST':
# Handle form submission
pass
else:
# Display edit form
pass
return render(request, 'edit_article.html', {'article': article})
# Object-level permission decorator
def object_permission_required(permission_checker):
"""
Object-level permission check decorator
"""
def decorator(view_func):
def wrapper(request, *args, **kwargs):
if not permission_checker(request.user, *args, **kwargs):
raise PermissionDenied("You don't have permission to perform this action")
return view_func(request, *args, **kwargs)
return wrapper
return decorator
# Use object-level permission decorator
@object_permission_required(can_edit_article)
def edit_article_decorated(request, article_id):
# View logic
pass
# Use permission checks in templates
"""
<!-- Check custom permissions in templates -->
{% if perms.articles.publish_article %}
<form method="post" action="{% url 'publish_article' article.pk %}">
{% csrf_token %}
<button type="submit" class="btn btn-success">Publish Article</button>
</form>
{% endif %}
<!-- Check if user has permission to edit specific article -->
{% if article.author == user or user.is_superuser or perms.articles.change_article %}
<a href="{% url 'edit_article' article.pk %}" class="btn btn-primary">Edit</a>
{% endif %}
"""
# View for managing permissions
from django.contrib.admin.views.decorators import staff_member_required
@staff_member_required
def manage_permissions(request):
"""
View for managing user permissions (admin only)
"""
users = User.objects.all()
groups = Group.objects.all()
if request.method == 'POST':
# Handle permission management form
user_id = request.POST.get('user_id')
group_id = request.POST.get('group_id')
if user_id and group_id:
user = User.objects.get(id=user_id)
group = Group.objects.get(id=group_id)
user.groups.add(group)
return render(request, 'manage_permissions.html', {
'users': users,
'groups': groups
})With these permissions and decorators, you can build a flexible and secure user permission management system that ensures users can only access content and functions they have permission to access.
Summary
Django permissions and decorators provide powerful access control mechanisms:
- ✅ login_required decorator protects view access
- ✅ permission_required decorator implements fine-grained permission control
- ✅ User group mechanism simplifies permission management
- ✅ Custom permissions meet special business requirements
- ✅ Object-level permission checks provide more precise control
Proper use of the permission system can build secure and reliable web applications.
Next
We will learn about using Django admin backend.