Skip to main content

Django View Decorators

Introduction

View decorators are a powerful feature in Django that allow you to wrap your view functions with additional functionality. They follow Python's decorator pattern, which enables you to modify or enhance the behavior of a function without changing its code.

In Django, decorators are commonly used to:

  • Control access to views (authentication/authorization)
  • Restrict allowed HTTP methods
  • Cache view responses
  • Add CSRF protection
  • Transform data before or after the view processes it

This tutorial will guide you through Django's built-in decorators and show you how to create your own custom decorators.

Understanding Decorators in Python

Before diving into Django-specific decorators, let's briefly review how decorators work in Python:

python
def my_decorator(view_func):
def wrapper_function(*args, **kwargs):
# Code executed before the view
print("Something is happening before the view function is called.")

result = view_func(*args, **kwargs)

# Code executed after the view
print("Something is happening after the view function is called.")

return result
return wrapper_function

@my_decorator
def my_view(request):
return HttpResponse("Hello, world!")

Using the @decorator_name syntax is equivalent to:

python
my_view = my_decorator(my_view)

Common Django View Decorators

Django provides several useful built-in decorators to enhance your views:

1. @require_http_methods

This decorator restricts a view to respond only to specific HTTP methods.

python
from django.views.decorators.http import require_http_methods

@require_http_methods(["GET", "POST"])
def my_view(request):
if request.method == "GET":
return HttpResponse("This is a GET request")
elif request.method == "POST":
return HttpResponse("This is a POST request")

Django also provides convenience decorators for common HTTP methods:

python
from django.views.decorators.http import require_GET, require_POST, require_safe

@require_GET
def get_only_view(request):
return HttpResponse("This view only accepts GET requests")

@require_POST
def post_only_view(request):
return HttpResponse("This view only accepts POST requests")

@require_safe # Allows only GET and HEAD requests
def safe_view(request):
return HttpResponse("This view only accepts safe methods")

2. @login_required

This decorator ensures that a user is logged in before accessing a view. If not, it redirects to the login page.

python
from django.contrib.auth.decorators import login_required

@login_required
def profile_view(request):
return HttpResponse(f"Hello, {request.user.username}!")

You can specify a custom login URL:

python
@login_required(login_url='/custom-login/')
def profile_view(request):
return HttpResponse(f"Hello, {request.user.username}!")

3. @permission_required

This decorator checks if a user has specific permissions:

python
from django.contrib.auth.decorators import permission_required

@permission_required('polls.add_choice')
def add_choice_view(request):
# View code here
return HttpResponse("You can add choices!")

# Multiple permissions (must have both)
@permission_required(['polls.add_choice', 'polls.change_choice'])
def manage_choices_view(request):
return HttpResponse("You can manage choices!")

4. @user_passes_test

This decorator allows you to define custom test functions for user access:

python
from django.contrib.auth.decorators import user_passes_test

def is_staff_or_superuser(user):
return user.is_staff or user.is_superuser

@user_passes_test(is_staff_or_superuser)
def admin_dashboard(request):
return HttpResponse("Welcome to the admin dashboard!")

5. @cache_page

This decorator caches the response of a view for a specified time period:

python
from django.views.decorators.cache import cache_page

@cache_page(60 * 15) # Cache for 15 minutes
def expensive_calculation_view(request):
# Some expensive database queries or calculations
result = perform_expensive_calculation()
return HttpResponse(f"Result: {result}")

6. @csrf_exempt and @csrf_protect

These decorators handle CSRF protection:

python
from django.views.decorators.csrf import csrf_exempt, csrf_protect

@csrf_exempt
def api_endpoint(request):
# This view doesn't require CSRF token
return HttpResponse("API endpoint")

@csrf_protect
def protected_form_view(request):
# This view is explicitly protected (useful when CSRF middleware is disabled)
return render(request, 'form.html')

Combining Multiple Decorators

You can apply multiple decorators to a view. They are processed from bottom to top:

python
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.views.decorators.cache import never_cache

@login_required
@require_POST
@never_cache
def submit_article(request):
# This view:
# 1. Requires authentication
# 2. Only accepts POST requests
# 3. Is never cached
# ...
return HttpResponse("Article submitted!")

Creating Custom Decorators

Let's create a custom decorator to check if a user is a premium member:

python
from functools import wraps
from django.http import HttpResponseForbidden

def premium_required(view_func):
@wraps(view_func) # This preserves the original function's metadata
def wrapper(request, *args, **kwargs):
if hasattr(request.user, 'profile') and request.user.profile.is_premium:
return view_func(request, *args, **kwargs)
else:
return HttpResponseForbidden("Premium membership required")
return wrapper

@premium_required
def exclusive_content(request):
return render(request, 'exclusive.html')

Decorators with Arguments

Sometimes you need decorators that accept arguments. This requires an additional wrapper function:

python
from functools import wraps
from django.http import HttpResponseForbidden

def minimum_role_required(role_level):
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect('login')

if request.user.profile.role_level >= role_level:
return view_func(request, *args, **kwargs)
else:
return HttpResponseForbidden(f"Role level {role_level} required")
return wrapper
return decorator

@minimum_role_required(5)
def moderator_view(request):
return HttpResponse("Welcome, moderator!")

Real-world Example: Rate Limiting

Here's a practical example of a rate-limiting decorator using Django's cache framework:

python
from functools import wraps
from django.core.cache import cache
from django.http import HttpResponseTooManyRequests

def rate_limit(requests_per_minute=60):
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
ip_address = request.META.get('REMOTE_ADDR')
cache_key = f"rate_limit_{ip_address}"

# Get the current request count for this IP
request_count = cache.get(cache_key, 0)

if request_count >= requests_per_minute:
return HttpResponseTooManyRequests("Rate limit exceeded")

# Increment the request count
cache.set(cache_key, request_count + 1, 60) # 60 seconds (1 minute) timeout

return view_func(request, *args, **kwargs)
return wrapper
return decorator

@rate_limit(5) # Allow only 5 requests per minute
def api_view(request):
return JsonResponse({"message": "API response"})

Using Decorators with Class-Based Views

To apply decorators to class-based views, you can:

  1. Use Django's method_decorator to apply a decorator to a specific method:
python
from django.utils.decorators import method_decorator
from django.views import View
from django.contrib.auth.decorators import login_required

class ProfileView(View):
@method_decorator(login_required)
def get(self, request):
return HttpResponse("Profile page")

@method_decorator(login_required)
def post(self, request):
# Update profile
return HttpResponse("Profile updated")
  1. Apply a decorator to the entire class:
python
@method_decorator(login_required, name='dispatch')
class ProfileView(View):
def get(self, request):
return HttpResponse("Profile page")

def post(self, request):
return HttpResponse("Profile updated")
  1. Apply multiple decorators at the class level:
python
@method_decorator(csrf_exempt, name='dispatch')
@method_decorator(login_required, name='dispatch')
class SettingsView(View):
# View methods here

Summary

In this tutorial, we've explored Django's view decorators, which provide a powerful way to enhance your views with additional functionality without cluttering your core view logic.

Key takeaways:

  • Decorators follow Python's decorator pattern
  • Django provides many useful built-in decorators for HTTP methods, authentication, permissions, and caching
  • You can create custom decorators for your specific requirements
  • Multiple decorators can be combined
  • Decorators can be applied to function-based views and class-based views (using method_decorator)

Using decorators effectively helps keep your code DRY (Don't Repeat Yourself) and improves the maintainability of your Django application.

Additional Resources

Exercises

  1. Create a decorator that logs every access to a view, including the timestamp, user (if authenticated), and IP address.
  2. Implement a superuser_required decorator that only allows superusers to access a view.
  3. Create a decorator that adds custom headers to all responses from a view.
  4. Implement a maintenance_mode decorator that can be toggled via a setting to return a maintenance page instead of the normal view.
  5. Create a decorator that measures and logs the execution time of a view function for performance monitoring.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)