Custom Decoraators

December 14, 2025
30 min read
Lalit Mahato
Custom Decoraators

1. What is a Decorator (Python)

A decorator is a function that modifies the behavior of another function or method without changing its code.
It uses the @decorator_name syntax.

2. Why Decorators are Important in Django

In Django, decorators are heavily used for:

  • Authentication and authorization

  • Request method validation

  • Caching

  • Security

  • Custom logic before/after views

They are mostly applied to views.

3. Common Django Built-in Decorators

3.1 login_required

Restricts access to authenticated users only

from django.contrib.auth.decorators import login_required

@login_required(login_url='login')
def task_list(request):
    tasks = Task.objects.all()
    form = TaskForm(request.POST or None)
    if request.method == "POST":
        if form.is_valid:
            form.save()
            return redirect('task_list')
    context = {
        'tasks': tasks,
        'form': form
    }
    return render(request, "index.html", context)

4. Create your own custom decorators.

4.1 Unauthenticated User Decorators

Create decorators.py file inside users app.

Add following line of codes.

from django.shortcuts import redirect

def unauthenticated_user(view_func):
    def wrapper_func(request, *args, **kwargs):
        if request.user.is_authenticated:
            return redirect('task_list')
        else:
            return view_func(request, *args, **kwargs)
    return wrapper_func

We will use this custom decorators to login view. This decorators redirect user to task_list view from the login page if the user is already login and user tries to access the login view. 

For this we will implement it on our login view,

Go to the users/views.py file and import the decorator

from users.decorators import unauthenticated_user

Add the unauthenticated_user decorator above the login_view. The final code looks like following.

@unauthenticated_user

def login_view(request):
    if request.method == 'POST':
        form = AuthenticationForm(request, data=request.POST)
        if form.is_valid():
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            user = authenticate(username=username, password=password)
            if user is not None:
                login(request, user)
                messages.info(request, f"Welcome {username}!")
                return redirect('task_list') # Redirect to dashboard/home in real app
            else:
                messages.error(request, "Invalid username or password.")
        else:
            messages.error(request, "Invalid username or password.")
    else:
        form = AuthenticationForm()
        # Add Bootstrap styling manually for built-in form
        for field in form.fields.values():
            field.widget.attrs['class'] = 'form-control'
    return render(request, 'users/login.html', {'form': form})

4.2 Permission Checker

We can use this to protect our view. The following decorator checks if user has group permission or not. If it has permission then allow user to access the view otherwise it redirect to 401 unauthorized user page.

from django.shortcuts import redirect
def allowed_users(allowed_roles=[]):
    def decorator(view_func):
        def wrapper_func(request, *args, **kwargs):
            group = None
            if request.user.groups.exists():
                group = request.user.groups.all()[0].name
            if group in allowed_roles:
                return view_func(request, *args, **kwargs)
            else:
                return redirect('error_401')
        return wrapper_func
    return decorator

Create view to show 401 error inside users app views.py

def error_401(request):
    return render(request, "401.html")

create 401.html page

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>401 | Unauthorized</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: Arial, Helvetica, sans-serif;
        }

        body {
            height: 100vh;
            background: linear-gradient(135deg, #1e3c72, #2a5298);
            display: flex;
            align-items: center;
            justify-content: center;
            color: #fff;
        }

        .container {
            background: rgba(255, 255, 255, 0.1);
            padding: 50px 40px;
            border-radius: 12px;
            text-align: center;
            max-width: 420px;
            width: 90%;
            box-shadow: 0 20px 40px rgba(0,0,0,0.3);
        }

        h1 {
            font-size: 72px;
            margin-bottom: 10px;
            letter-spacing: 2px;
        }

        h2 {
            font-size: 22px;
            margin-bottom: 15px;
            font-weight: normal;
        }

        p {
            font-size: 15px;
            line-height: 1.6;
            opacity: 0.9;
            margin-bottom: 30px;
        }

        .btn {
            display: inline-block;
            padding: 12px 28px;
            background: #ffffff;
            color: #1e3c72;
            border-radius: 30px;
            text-decoration: none;
            font-weight: bold;
            transition: all 0.3s ease;
        }

        .btn:hover {
            background: #f1f1f1;
            transform: translateY(-2px);
        }

        .icon {
            font-size: 60px;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>

<div class="container">
    <div class="icon">🔒</div>
    <h1>401</h1>
    <h2>Unauthorized Access</h2>
    <p>
        You do not have permission to access this page.<br>
        Please log in with the correct credentials.
    </p>
    <a href="{% url 'login' %}" class="btn">Go to Login</a>
</div>

</body>
</html>

Create url for 401 page view. The final urls.py looks like following.

# users/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('signup/', views.signup_view, name='signup'),
    path('login/', views.login_view, name='login'),
    path('logout/', views.logout_view, name='logout'),
    path('change-password/', views.change_password_view, name='change_password'),
    path('error-401/', views.error_401, name='error_401'),
]

Login to the admin panel. Go to groups and create groups with following groups.

  • admin
  • user

After creating the views go to users on admin panel and assign group to the user.

Protect your views now.

Now we will protect our view and only allowed user with permission can access the view. Following code show 401 page if user is not assigned any group.

from users.decorators import allowed_users

@allowed_users(allowed_roles=['admin', 'user'])
@login_required(login_url='login')
def task_list(request):
    tasks = Task.objects.all()
    form = TaskForm(request.POST or None)
    if request.method == "POST":
        if form.is_valid:
            form.save()
            return redirect('task_list')
    context = {
        'tasks': tasks,
        'form': form
    }
    return render(request, "index.html", context)

 

 

Lalit Mahato

Lalit Mahato

Software Engineer | Machine Learning Enthusiast

Innovative and results-driven software developer with 5+ years of experience in designing, developing, and deploying high-quality software solutions. Proficient in various programming languages and technologies, with a keen interest in …

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment

Search

Categories

Related Posts

Emails in Django
Emails in Django

Dec 18, 2025

Read More
Django Cheatsheet
Django Cheatsheet

Dec 17, 2025

Read More
Django-specific OWASP guidelines
Django-specific OWASP guidelines

Dec 17, 2025

Read More