Introduction
Django provides a powerful set of decorators that let you modify view behavior without changing the view function itself. These decorators handle common patterns like restricting HTTP methods, requiring authentication, and controlling caching.
Key Concepts
- Decorator: A function that wraps another function to add behavior before or after execution.
- HTTP Method Restriction: Limiting which HTTP methods (GET, POST, etc.) a view accepts.
- View Decoration: Applying decorators to views in
views.pyor directly inurls.py.
Real World Context
In production applications, you almost never want a view to accept all HTTP methods. A page that displays data should only respond to GET, while a form submission handler should only respond to POST. Decorators enforce these constraints cleanly.
Deep Dive
Django offers several built-in view decorators in django.views.decorators.
HTTP Method Decorators
Restrict which HTTP methods a view accepts:
pythonfrom django.views.decorators.http import require_http_methods, require_GET, require_POST, require_safe @require_GET def article_list(request): """Only accepts GET requests.""" articles = Article.objects.all() return render(request, 'articles/list.html', {'articles': articles}) @require_POST def delete_article(request, pk): """Only accepts POST requests.""" article = get_object_or_404(Article, pk=pk) article.delete() return redirect('article_list') @require_http_methods(["GET", "POST"]) def edit_article(request, pk): """Accepts GET and POST only.""" article = get_object_or_404(Article, pk=pk) if request.method == 'POST': form = ArticleForm(request.POST, instance=article) if form.is_valid(): form.save() return redirect('article_detail', pk=pk) else: form = ArticleForm(instance=article) return render(request, 'articles/edit.html', {'form': form}) @require_safe def public_data(request): """Only accepts GET and HEAD (safe methods).""" return JsonResponse({'status': 'ok'})
If a disallowed method is used, Django returns 405 Method Not Allowed.
CSRF Decorators
Control CSRF protection on specific views:
pythonfrom django.views.decorators.csrf import csrf_exempt, csrf_protect @csrf_exempt def webhook_handler(request): """External webhooks can't provide CSRF tokens.""" payload = json.loads(request.body) process_webhook(payload) return JsonResponse({'received': True}) @csrf_protect def special_form(request): """Ensure CSRF protection even if middleware is disabled.""" pass
Caching Decorators
Control response caching:
pythonfrom django.views.decorators.cache import cache_page, never_cache @cache_page(60 * 15) # Cache for 15 minutes def article_list(request): articles = Article.objects.all() return render(request, 'articles/list.html', {'articles': articles}) @never_cache def dashboard(request): """Always fetch fresh data.""" return render(request, 'dashboard.html')
Stacking Multiple Decorators
Decorators are applied bottom-up (closest to the function runs first):
pythonfrom django.contrib.auth.decorators import login_required from django.views.decorators.http import require_POST @login_required @require_POST def vote(request, question_id): """Must be logged in AND use POST.""" question = get_object_or_404(Question, pk=question_id) # Process vote... return redirect('polls:results', pk=question_id)
Decorating Class-Based Views
Use method_decorator for CBVs:
pythonfrom django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from django.views.generic import ListView @method_decorator(cache_page(60 * 15), name='dispatch') class ArticleListView(ListView): model = Article
Or apply directly in URLs:
pythonfrom django.contrib.auth.decorators import login_required urlpatterns = [ path('vote/', login_required(VoteView.as_view()), name='vote'), ]
Common Pitfalls
- Using
@csrf_exemptcarelessly: Only use it for views that genuinely cannot provide CSRF tokens, like external API webhooks. Never apply it to forms used by your own frontend. - Stacking decorators in wrong order: Decorators apply bottom-up.
@login_requiredshould be the outermost (top) decorator so authentication is checked first. - Forgetting
method_decoratorfor CBVs: Regular decorators do not work directly on class methods. Usemethod_decoratoror apply decorators in URLconf.
Best Practices
- Always restrict HTTP methods: Use
@require_GET,@require_POST, or@require_http_methodson every view to enforce the expected method. - Prefer
@require_safeover@require_GET:require_safeallows both GET and HEAD, which is more correct for read-only views. - Apply
@login_requiredat the outermost level: Check authentication before checking methods or other conditions.
Summary
@require_GET,@require_POST, and@require_http_methodsrestrict allowed HTTP methods, returning 405 for others@csrf_exemptdisables CSRF protection for specific views like external webhooks@cache_page(seconds)caches view responses for improved performance- Decorators are stacked bottom-up, with the outermost running first
- Use
method_decoratorto apply function decorators to class-based views
Code Examples
from django.views.decorators.http import require_http_methods
from django.contrib.auth.decorators import login_required
@login_required
@require_http_methods(["GET", "POST"])
def edit_article(request, pk):
article = get_object_or_404(Article, pk=pk)
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
form.save()
return redirect('article_detail', pk=pk)
else:
form = ArticleForm(instance=article)
return render(request, 'articles/edit.html', {'form': form})