Django View Decorators
When building web applications with Django, you'll often find yourself wanting to add common functionality across multiple views. This might include checking if a user is logged in, ensuring a user has specific permissions, or restricting views to certain HTTP methods. Django view decorators provide an elegant solution to these common patterns.
What are Decorators?
Before diving into Django view decorators, let's understand what decorators are in Python. Decorators are a powerful feature in Python that allow you to modify or enhance functions without changing their code. They use the @decorator_name
syntax and are placed above function definitions.
A decorator is essentially a function that takes another function as an argument and returns a modified version of that function.
def my_decorator(func):
def wrapper():
print("Something happens before the function is called.")
func()
print("Something happens after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# When calling say_hello()
say_hello()
Output:
Something happens before the function is called.
Hello!
Something happens after the function is called.
Common Django View Decorators
Django provides several built-in decorators that help you handle common patterns in your views. Let's explore the most useful ones:
1. @login_required
This decorator ensures that a user is logged in before they can access a view. If the user isn't authenticated, they'll be redirected to the login page.
from django.contrib.auth.decorators import login_required
@login_required
def profile_view(request):
# Only authenticated users can see this
return render(request, 'profile.html')
You can also customize the redirect URL:
@login_required(login_url='/custom-login/')
def profile_view(request):
return render(request, 'profile.html')
2. @permission_required
This decorator checks if a user has a specific permission before allowing access to a view.
from django.contrib.auth.decorators import permission_required
@permission_required('app.can_edit_entry')
def edit_entry(request, entry_id):
# Only users with 'app.can_edit_entry' permission can access this
return render(request, 'edit_entry.html')
You can also specify what should happen if the user lacks permission:
@permission_required('app.can_edit_entry', raise_exception=True) # Returns a 403 Forbidden
def edit_entry(request, entry_id):
return render(request, 'edit_entry.html')
3. @require_http_methods
Restricts access to a view based on the HTTP method used in the request.
from django.views.decorators.http import require_http_methods
@require_http_methods(["GET", "POST"])
def my_view(request):
if request.method == "GET":
# Handle GET request
return render(request, 'form.html')
else: # POST
# Process form submission
return redirect('success')
There are also specialized versions for common HTTP methods:
from django.views.decorators.http import require_GET, require_POST, require_safe
@require_GET
def get_view(request):
# Only accepts GET requests
return render(request, 'view.html')
@require_POST
def post_view(request):
# Only accepts POST requests
# Process form submission
return redirect('success')
@require_safe # Equivalent to @require_http_methods(["GET", "HEAD"])
def safe_view(request):
# Only accepts GET and HEAD requests
return render(request, 'view.html')
4. @csrf_exempt
Django's CSRF protection is enabled by default, but sometimes you might need to exempt certain views (like for an API endpoint that's authenticated differently).
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def api_endpoint(request):
# This view doesn't require CSRF verification
return JsonResponse({'status': 'success'})
Note: Use this decorator cautiously, as it can expose your site to CSRF attacks.
5. @cache_page
Caches the result of a view for a specified time, which can greatly improve performance for views that don't change often.
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # Cache for 15 minutes
def my_expensive_view(request):
# This view's result will be cached for 15 minutes
# The expensive calculation only happens once per 15 minutes
result = perform_expensive_calculation()
return render(request, 'result.html', {'result': result})
Chaining Multiple Decorators
You can apply multiple decorators to a single view. They are executed from bottom to top (the decorator closest to the function is applied first).
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
@login_required
@require_POST
def submit_comment(request):
# Only authenticated users can access this view
# AND the request must use POST method
# Process the comment submission
return redirect('comments')
Creating Your Own Decorators
Sometimes, you might need custom functionality that isn't provided by Django's built-in decorators. Let's create a simple decorator that logs information about view access:
import logging
from functools import wraps
logger = logging.getLogger(__name__)
def log_view_access(view_func):
@wraps(view_func) # Preserves the original function's metadata
def wrapper(request, *args, **kwargs):
logger.info(f"View {view_func.__name__} accessed by {request.user} from {request.META.get('REMOTE_ADDR')}")
return view_func(request, *args, **kwargs)
return wrapper
# Using our custom decorator
@log_view_access
def dashboard(request):
return render(request, 'dashboard.html')
Practical Example: Building a Blog with Decorators
Let's see how we might use decorators in a real-world blog application:
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required, permission_required
from django.views.decorators.http import require_POST, require_GET
from .models import Post, Comment
# Public view - accessible to everyone
@require_GET
def post_list(request):
posts = Post.objects.filter(published=True)
return render(request, 'blog/post_list.html', {'posts': posts})
# Public view with specific post
@require_GET
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id, published=True)
return render(request, 'blog/post_detail.html', {'post': post})
# Only logged-in users can comment
@login_required
@require_POST
def add_comment(request, post_id):
post = get_object_or_404(Post, id=post_id)
comment = Comment(
post=post,
author=request.user,
content=request.POST.get('content', '')
)
comment.save()
return redirect('post_detail', post_id=post_id)
# Only authorized users with specific permissions can create posts
@login_required
@permission_required('blog.add_post')
def create_post(request):
if request.method == 'POST':
# Process form submission
title = request.POST.get('title')
content = request.POST.get('content')
published = request.POST.get('published', False) == 'on'
post = Post(
title=title,
content=content,
author=request.user,
published=published
)
post.save()
return redirect('post_detail', post_id=post.id)
else:
return render(request, 'blog/create_post.html')
In this example:
- Public views use
@require_GET
to ensure they only accept GET requests - The comment form submission uses both
@login_required
to ensure the user is authenticated and@require_POST
to ensure it's called with a POST request - The post creation view checks both authentication (
@login_required
) and specific permissions (@permission_required
)
Summary
Django view decorators are powerful tools that help you:
- Keep your code DRY (Don't Repeat Yourself) by extracting common functionality
- Apply access control and permissions consistently
- Restrict views to specific HTTP methods
- Improve performance with caching
- Add custom behavior to multiple views
By mastering view decorators, you can write cleaner, more maintainable Django applications with consistent behavior across your views.
Exercise Ideas
- Create a custom decorator that restricts a view to staff users only
- Implement a decorator that checks if a user has a complete profile before allowing access to certain views
- Build a decorator that logs performance metrics for your views
- Create a view that requires multiple permissions using the built-in decorators
- Implement a decorator that throttles requests to prevent abuse (e.g., only allow 10 requests per minute)
Additional Resources
- Django Documentation on Decorators
- Python Decorators in Depth
- Django Authentication System
- Understanding Django Middleware (an alternative approach for some cross-cutting concerns)
Happy coding with Django decorators!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)