Skip to content

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:

python
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:

python
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:

python
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:

python
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:

python
# 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:

  1. ✅ login_required decorator protects view access
  2. ✅ permission_required decorator implements fine-grained permission control
  3. ✅ User group mechanism simplifies permission management
  4. ✅ Custom permissions meet special business requirements
  5. ✅ 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.

10.1 Admin Basics →

Table of Contents

Return to Course Outline

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).