URLs and Views

Conditional View Processing

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.

Conditional View Processing

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

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 (Entity Tags)

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

Last-Modified Headers

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

Combined Conditional Processing

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})

Class-Based Views with Conditional Processing

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)

Advanced Conditional Processing

Conditional Processing for Lists

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})

Per-User Conditional Processing

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})

Vary Headers

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')

Cache Control

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')

Middleware for Conditional Processing

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

API Views with Conditional Processing

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)

Testing Conditional Views

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)

Performance Benefits

Conditional view processing provides significant benefits:

  • Reduced Bandwidth: 304 responses have no body
  • Faster Response Times: No template rendering needed
  • Lower Server Load: Less processing for unchanged content
  • Better User Experience: Faster page loads
  • CDN Efficiency: Better cache hit rates

Best Practices

  1. Use for Expensive Views: Apply to views with heavy database queries or rendering
  2. Combine with Caching: Use alongside Django's caching framework
  3. Consider User Context: Include user-specific data in ETag generation when needed
  4. Test Thoroughly: Ensure conditional logic works correctly
  5. Monitor Performance: Track 304 response rates and cache effectiveness

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.