Difficulty: Advanced
Django's caching framework provides a way to store computed data so expensive operations do not need to be repeated. Caching is one of the most effective ways to improve Django application performance. Django supports several cache backends: in-memory (LocMemCache), file-based, database, Memcached, and Redis.
The cache framework operates at multiple levels. Per-site caching caches every page for anonymous users. Per-view caching uses the @cache_page decorator to cache specific views. Template fragment caching uses the {% cache %} tag to cache parts of templates. Low-level caching uses cache.get() and cache.set() for manual control.
Redis and Memcached are the recommended backends for production. Redis (via django-redis) is the most popular choice because it supports data structures, persistence, and pub/sub in addition to key-value caching. Memcached is simpler but faster for pure key-value operations.
The low-level cache API provides full control: cache.set(key, value, timeout) stores a value, cache.get(key, default) retrieves it, cache.delete(key) removes it, and cache.get_or_set(key, callable, timeout) returns the cached value or computes and stores it. The timeout is in seconds (None means cache forever).
Django sessions store data between requests for individual users. By default, sessions are stored in the database. For better performance, you can use cache-based sessions (SESSION_ENGINE = 'django.contrib.sessions.backends.cache') or cached database sessions (which cache sessions but fall back to the database). Sessions are accessed through request.session, which behaves like a dictionary.
Session data is tied to a session ID stored in a cookie (sessionid by default). Django handles session creation, serialization, and expiration automatically. You can set SESSION_COOKIE_AGE (in seconds) to control how long sessions last, SESSION_EXPIRE_AT_BROWSER_CLOSE to expire on browser close, and SESSION_COOKIE_SECURE to require HTTPS.
# settings.py
# Development: in-memory cache
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
# Production: Redis cache (requires django-redis)
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
},
'TIMEOUT': 300, # Default timeout: 5 minutes
}
}
# Production: Memcached
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
}
}
LocMemCache is fine for development and single-process deployments. Redis is the most popular production choice, supporting data structures and persistence. Memcached is simpler but lacks Redis features.
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie
# Cache entire view for 15 minutes
@cache_page(60 * 15)
def article_list(request):
articles = Article.objects.filter(is_published=True)
return render(request, 'blog/article_list.html', {'articles': articles})
# Cache per user (vary by cookie)
@cache_page(60 * 15)
@vary_on_cookie
def dashboard(request):
return render(request, 'dashboard.html')
# Template fragment caching
# {% load cache %}
# {% cache 600 sidebar request.user.id %}
# <div class="sidebar">
# {% for item in expensive_query %}
# <p>{{ item.name }}</p>
# {% endfor %}
# </div>
# {% endcache %}
@cache_page caches the entire response. @vary_on_cookie creates separate caches per user. Template {% cache %} caches specific fragments. The cache tag arguments are: timeout (seconds), fragment name, and vary-on keys.
from django.core.cache import cache
# Basic operations
cache.set('my_key', 'my_value', timeout=300) # 5 minutes
value = cache.get('my_key') # 'my_value'
value = cache.get('missing_key', 'default') # 'default'
cache.delete('my_key')
# get_or_set: compute only if not cached
def get_expensive_data():
return Article.objects.aggregate(
total=Count('id'),
avg_views=Avg('views'),
)
stats = cache.get_or_set('article_stats', get_expensive_data, 600)
# Practical example in a view
def article_list(request):
cache_key = 'published_articles'
articles = cache.get(cache_key)
if articles is None:
articles = list(Article.objects.filter(
is_published=True
).select_related('author', 'category'))
cache.set(cache_key, articles, 300)
return render(request, 'articles.html', {'articles': articles})
# Invalidate cache when data changes
@receiver(post_save, sender=Article)
def invalidate_article_cache(sender, kwargs):
cache.delete('published_articles')
The low-level cache API gives full control. cache.get_or_set is the most convenient pattern - it returns cached data or computes and stores it. Invalidate the cache when underlying data changes using signals.
# settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
SESSION_CACHE_ALIAS = 'default'
SESSION_COOKIE_AGE = 1209600 # 2 weeks in seconds
SESSION_COOKIE_SECURE = True # HTTPS only in production
# Using sessions in views
def set_preferences(request):
request.session['theme'] = 'dark'
request.session['language'] = 'en'
request.session.set_expiry(3600) # 1 hour
return redirect('home')
def get_preferences(request):
theme = request.session.get('theme', 'light')
language = request.session.get('language', 'en')
return render(request, 'settings.html', {
'theme': theme,
'language': language,
})
def clear_session(request):
request.session.flush() # Delete session data and cookie
return redirect('home')
# Check if session has data
if 'cart' in request.session:
cart = request.session['cart']
# Modify mutable session data
cart = request.session.get('cart', [])
cart.append({'product_id': 1, 'quantity': 2})
request.session['cart'] = cart # Re-assign to trigger save
request.session.modified = True # Or set this flag
Sessions store per-user data between requests. cached_db backend caches sessions in Redis/Memcached with database fallback. Use request.session like a dictionary. Re-assign or set modified=True when changing mutable objects.
Cache Framework, Memcached, Redis, Session, cache_page, Template Cache