from functools import wraps
from django.http import JsonResponse
from django.core.cache import cache
def ajax_required(view_func):
"""Decorator to ensure request is AJAX."""
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return JsonResponse({'error': 'AJAX required'}, status=400)
return view_func(request, *args, **kwargs)
return wrapper
def rate_limit(key_prefix, limit=10, period=60):
"""Simple rate limiting decorator."""
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
# Create cache key from IP or user
if request.user.is_authenticated:
cache_key = f'{key_prefix}:{request.user.id}'
else:
cache_key = f'{key_prefix}:{request.META.get("REMOTE_ADDR")}'
# Check rate limit
count = cache.get(cache_key, 0)
if count >= limit:
return JsonResponse({'error': 'Rate limit exceeded'}, status=429)
# Increment counter
cache.set(cache_key, count + 1, period)
return view_func(request, *args, **kwargs)
return wrapper
return decorator
# Usage
@ajax_required
@rate_limit('api_endpoint', limit=100, period=3600)
def my_api_view(request):
return JsonResponse({'status': 'ok'})
Custom decorators encapsulate reusable view logic. I use functools.wraps to preserve function metadata. For class-based views, I use method_decorator. Common patterns include permission checks, rate limiting, or request validation. Decorators can modify request/response or short-circuit with early returns. I stack multiple decorators carefully—order matters. For complex logic, middleware might be better than decorators. This keeps views clean and promotes code reuse across endpoints.