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)