Conditional view processing allows Django to handle HTTP conditional requests efficiently, reducing bandwidth and improving performance by serving cached content when appropriate. This includes ETags, Last-Modified headers, and conditional GET/HEAD requests.
HTTP conditional processing uses headers to determine if content has changed since the last request, allowing browsers and proxies to use cached versions when appropriate.
ETags are unique identifiers for specific versions of resources:
from django.views.decorators.http import condition, etag
from django.utils.http import http_date
import hashlib
# Using etag decorator
def generate_etag(request, article_id):
article = Article.objects.get(pk=article_id)
content = f"{article.title}{article.updated_at}"
return hashlib.md5(content.encode()).hexdigest()
@etag(generate_etag)
def article_detail(request, article_id):
article = get_object_or_404(Article, pk=article_id)
return render(request, 'article_detail.html', {'article': article})
# Django automatically handles:
# - If-None-Match header from client
# - Returns 304 Not Modified if ETag matches
# - Returns full response with ETag header if changed
from django.views.decorators.http import last_modified
def article_last_modified(request, article_id):
article = Article.objects.get(pk=article_id)
return article.updated_at
@last_modified(article_last_modified)
def article_detail(request, article_id):
article = get_object_or_404(Article, pk=article_id)
return render(request, 'article_detail.html', {'article': article})
# Django automatically handles:
# - If-Modified-Since header from client
# - Returns 304 Not Modified if not modified
# - Returns full response with Last-Modified header if changed
from django.views.decorators.http import condition
def article_etag(request, article_id):
article = Article.objects.get(pk=article_id)
return hashlib.md5(f"{article.id}{article.updated_at}".encode()).hexdigest()
def article_last_modified(request, article_id):
article = Article.objects.get(pk=article_id)
return article.updated_at
@condition(etag_func=article_etag, last_modified_func=article_last_modified)
def article_detail(request, article_id):
article = get_object_or_404(Article, pk=article_id)
return render(request, 'article_detail.html', {'article': article})
from django.views.generic import DetailView
from django.utils.decorators import method_decorator
from django.views.decorators.http import condition
import hashlib
class ArticleDetailView(DetailView):
model = Article
template_name = 'article_detail.html'
def get_etag(self):
article = self.get_object()
content = f"{article.id}{article.updated_at}"
return hashlib.md5(content.encode()).hexdigest()
def get_last_modified(self):
article = self.get_object()
return article.updated_at
@method_decorator(condition(
etag_func=lambda request, pk: ArticleDetailView().get_etag(),
last_modified_func=lambda request, pk: ArticleDetailView().get_last_modified()
))
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
from django.db.models import Max
def article_list_etag(request):
# Generate ETag based on latest update
latest_update = Article.objects.aggregate(Max('updated_at'))['updated_at__max']
count = Article.objects.count()
content = f"{count}{latest_update}"
return hashlib.md5(content.encode()).hexdigest()
def article_list_last_modified(request):
# Return latest modification time
return Article.objects.aggregate(Max('updated_at'))['updated_at__max']
@condition(etag_func=article_list_etag, last_modified_func=article_list_last_modified)
def article_list(request):
articles = Article.objects.all()
return render(request, 'article_list.html', {'articles': articles})
def user_specific_etag(request):
# Generate ETag including user information
user_id = request.user.id if request.user.is_authenticated else 'anonymous'
articles = Article.objects.filter(author=request.user)
latest = articles.aggregate(Max('updated_at'))['updated_at__max']
content = f"{user_id}{latest}"
return hashlib.md5(content.encode()).hexdigest()
@condition(etag_func=user_specific_etag)
def my_articles(request):
articles = Article.objects.filter(author=request.user)
return render(request, 'my_articles.html', {'articles': articles})
Control caching based on request headers:
from django.views.decorators.vary import vary_on_headers, vary_on_cookie
@vary_on_headers('Accept-Language')
@condition(etag_func=article_etag)
def article_detail(request, article_id):
# Response varies by Accept-Language header
article = get_object_or_404(Article, pk=article_id)
return render(request, 'article_detail.html', {'article': article})
@vary_on_cookie
def user_dashboard(request):
# Response varies by cookies (user-specific)
return render(request, 'dashboard.html')
from django.views.decorators.cache import cache_control, never_cache
@cache_control(max_age=3600, public=True)
@condition(etag_func=article_etag)
def article_detail(request, article_id):
# Cacheable for 1 hour
article = get_object_or_404(Article, pk=article_id)
return render(request, 'article_detail.html', {'article': article})
@never_cache
def sensitive_data(request):
# Never cache this response
return render(request, 'sensitive.html')
@cache_control(private=True, max_age=600)
def user_profile(request):
# Private cache for 10 minutes
return render(request, 'profile.html')
from django.utils.cache import get_conditional_response
from django.utils.http import http_date
class ConditionalProcessingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# Add conditional processing headers
if hasattr(response, 'etag'):
response['ETag'] = response.etag
if hasattr(response, 'last_modified'):
response['Last-Modified'] = http_date(response.last_modified.timestamp())
# Check for conditional request
conditional_response = get_conditional_response(
request,
etag=getattr(response, 'etag', None),
last_modified=getattr(response, 'last_modified', None)
)
return conditional_response or response
from rest_framework.views import APIView
from rest_framework.response import Response
from django.utils.decorators import method_decorator
from django.views.decorators.http import condition
class ArticleAPIView(APIView):
def get_etag(self, request, pk):
article = Article.objects.get(pk=pk)
return hashlib.md5(f"{article.id}{article.updated_at}".encode()).hexdigest()
def get_last_modified(self, request, pk):
article = Article.objects.get(pk=pk)
return article.updated_at
@method_decorator(condition(
etag_func=lambda request, pk: ArticleAPIView().get_etag(request, pk),
last_modified_func=lambda request, pk: ArticleAPIView().get_last_modified(request, pk)
))
def get(self, request, pk):
article = get_object_or_404(Article, pk=pk)
serializer = ArticleSerializer(article)
return Response(serializer.data)
from django.test import TestCase, Client
from django.utils import timezone
class ConditionalViewTests(TestCase):
def setUp(self):
self.client = Client()
self.article = Article.objects.create(
title='Test Article',
content='Test content',
updated_at=timezone.now()
)
def test_etag_match_returns_304(self):
# First request to get ETag
response = self.client.get(f'/articles/{self.article.id}/')
etag = response['ETag']
# Second request with If-None-Match
response = self.client.get(
f'/articles/{self.article.id}/',
HTTP_IF_NONE_MATCH=etag
)
self.assertEqual(response.status_code, 304)
def test_modified_content_returns_200(self):
# First request
response = self.client.get(f'/articles/{self.article.id}/')
etag = response['ETag']
# Modify article
self.article.title = 'Updated Title'
self.article.save()
# Second request with old ETag
response = self.client.get(
f'/articles/{self.article.id}/',
HTTP_IF_NONE_MATCH=etag
)
self.assertEqual(response.status_code, 200)
def test_last_modified_header(self):
response = self.client.get(f'/articles/{self.article.id}/')
self.assertIn('Last-Modified', response)
# Request with If-Modified-Since
last_modified = response['Last-Modified']
response = self.client.get(
f'/articles/{self.article.id}/',
HTTP_IF_MODIFIED_SINCE=last_modified
)
self.assertEqual(response.status_code, 304)
Conditional view processing provides significant benefits:
Conditional view processing is a powerful tool for optimizing Django applications, reducing server load and improving response times by leveraging HTTP's built-in caching mechanisms.
Handling HTTP Methods
HTTP methods define the type of action being performed on a resource. Django provides comprehensive support for handling different HTTP methods, enabling you to build RESTful APIs and implement proper request handling patterns.
File Uploads
File uploads are a common requirement in web applications. Django provides robust support for handling file uploads securely and efficiently, with built-in validation, processing, and storage capabilities.