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:
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:
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.
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:
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.
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:
@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:
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:
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:
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:
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:
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:
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:
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:
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:
- Use Django's
method_decorator
to apply a decorator to a specific method:
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")
- Apply a decorator to the entire class:
@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")
- Apply multiple decorators at the class level:
@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
- Django Documentation: Built-in decorators
- Python Documentation: Decorators
- Django Source Code: View decorators
Exercises
- Create a decorator that logs every access to a view, including the timestamp, user (if authenticated), and IP address.
- Implement a
superuser_required
decorator that only allows superusers to access a view. - Create a decorator that adds custom headers to all responses from a view.
- Implement a
maintenance_mode
decorator that can be toggled via a setting to return a maintenance page instead of the normal view. - 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! :)