Skip to main content

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.

python
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.

python
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:

python
@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.

python
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:

python
@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.

python
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:

python
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).

python
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.

python
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).

python
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:

python
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:

python
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:

  1. Keep your code DRY (Don't Repeat Yourself) by extracting common functionality
  2. Apply access control and permissions consistently
  3. Restrict views to specific HTTP methods
  4. Improve performance with caching
  5. 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

  1. Create a custom decorator that restricts a view to staff users only
  2. Implement a decorator that checks if a user has a complete profile before allowing access to certain views
  3. Build a decorator that logs performance metrics for your views
  4. Create a view that requires multiple permissions using the built-in decorators
  5. Implement a decorator that throttles requests to prevent abuse (e.g., only allow 10 requests per minute)

Additional Resources

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! :)