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).