Skip to content

Django - Caching

Caching means storing the result of an expensive operation so you don’t have to perform it again the next time you need the same result. Think of it like remembering the answer to a complex math problem instead of recalculating it every time someone asks.

Here’s a simple concept of how caching works:

Given a request (e.g., for a specific web page URL):
1. Check if a valid, pre-calculated result for this request exists in the cache.
2. If yes:
- Return the cached result directly.
3. If no:
- Perform the expensive operation (e.g., generate the web page by querying the database, rendering a template).
- Save this newly generated result in the cache for future requests.
- Return the generated result.

Django provides a robust caching framework that allows you to cache various parts of your application to improve performance:

  • The output of specific views.
  • Parts of a template (template fragments).
  • The entire website.
  • Results of arbitrary expensive calculations using a low-level API.

To start using caching in Django, you first need to configure where the cached data will be stored (the cache backend). Django supports several backends:

  • Database Caching: Stores cache data in your Django project’s database.
  • Filesystem Caching: Stores cache data as separate files on your server.
  • In-Memory Caching (LocMem): Stores cache data in the memory of the web server process. Very fast but not shared between processes.
  • Memcached: Uses a fast, external in-memory cache server (Memcached). Requires installing the Memcached server and a Python library (e.g., pylibmc or pymemcache).
  • Redis: Uses a fast, external in-memory data structure store (Redis). Requires installing the Redis server and a Python library (e.g., redis or django-redis).

You configure the cache backend in your project’s settings.py file using the CACHES setting.

Add the following to your settings.py:

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table', # Choose a name for your cache table
}
}

After configuring this, you need to create the cache table in your database. Run the following command:

python manage.py createcachetable

Add the following to your settings.py:

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache', # Ensure this directory exists and is writable by the web server
}
}

This is often the most performant option for production environments. First, install a Memcached client library (e.g., pymemcache): pip install pymemcache. Then, configure settings.py:

# Using pymemcache client
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211', # Assumes Memcached is running on localhost, port 11211
}
}
# Or using a Unix socket
# CACHES = {
# 'default': {
# 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
# 'LOCATION': 'unix:/tmp/memcached.sock',
# }
# }

Redis is another excellent choice for high-performance caching. First, install a Redis client library and a Django cache backend for it (e.g., django-redis): pip install django-redis redis. Then, configure settings.py:

CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1", # Assumes Redis is running on localhost, port 6379, using DB 1
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}

Refer to the django-redis documentation for more configuration options.

The simplest way to use caching is to cache your entire site. This is done by adding Django’s cache middleware to the MIDDLEWARE setting in settings.py. The order is crucial:

MIDDLEWARE = [
# ... other middleware ...
'django.middleware.cache.UpdateCacheMiddleware', # Must be first or near the start
'django.middleware.common.CommonMiddleware',
# ... other middleware ...
'django.middleware.cache.FetchFromCacheMiddleware', # Must be last or near the end
# ... other middleware ...
]

Explanation: UpdateCacheMiddleware needs to run after the response is generated to cache it. FetchFromCacheMiddleware needs to run before other middleware to see if it can serve the request directly from the cache.

Then, configure these settings in settings.py:

# The cache alias defined in CACHES to use for storage.
CACHE_MIDDLEWARE_ALIAS = 'default'
# The default number of seconds each page should be cached.
CACHE_MIDDLEWARE_SECONDS = 600 # Cache for 10 minutes
# Optional: A prefix for cache keys generated by the middleware.
CACHE_MIDDLEWARE_KEY_PREFIX = ''

Site-wide caching is effective for largely static sites but less useful for highly dynamic ones.

For more granular control, you can cache the output of individual views using the cache_page decorator. Apply it directly to your view function:

from django.views.decorators.cache import cache_page
from django.http import HttpResponse
import datetime
# Cache this view for 15 minutes (900 seconds)
@cache_page(60 * 15)
def expensive_calculation_view(request):
# Simulate an expensive operation
now = datetime.datetime.now()
html = f"<html><body>It is now {now}.</body></html>"
return HttpResponse(html)
# Example view with parameters
@cache_page(60 * 15)
def view_articles_by_month(request, year, month):
text = f"Displaying articles for month: {month}/{year}"
# In a real app, you'd query the database here
return HttpResponse(text)

The cache_page decorator takes the cache timeout in seconds as its argument. Each unique URL will be cached separately. For example, a request to /articles/02/2023/ will be cached independently from a request to /articles/03/2024/.

You can also apply cache_page within your URL configuration (urls.py), which can be useful for applying caching without modifying the view code:

from django.urls import path, re_path
from django.views.decorators.cache import cache_page
from . import views # Assuming views are in the same app's views.py
urlpatterns = [
# Cache the view for 15 minutes directly in the URLconf
re_path(
r'^articles/(?P<month>\d{2})/(?P<year>\d{4})/$',
cache_page(60 * 15)(views.view_articles_by_month),
name='articles_by_month'
),
path('expensive/', views.expensive_calculation_view, name='expensive_view'),
]

If applied in urls.py, you don’t need the @cache_page decorator in views.py for that specific view.

Sometimes, only parts of a page are computationally expensive or change infrequently. You can cache these specific sections within your templates using the {% cache %} template tag.

Consider this template snippet:

{% extends "base.html" %}
{% load cache %}
{% block title %}My Page Title{% endblock %}
{% block content %}
<h1>Welcome, {{ user.username }}!</h1>
{# This part is expensive to generate #}
{% cache 3600 sidebar request.user.username %} {# Cache for 1 hour, vary by username #}
<div class="sidebar">
<h2>Recommendations for You</h2>
<ul>
{% for item in recommended_items %}
<li>{{ item.name }}</li>
{% empty %}
<li>No recommendations available.</li>
{% endfor %}
</ul>
</div>
{% endcache %}
<div class="main-content">
<p>This content changes frequently.</p>
<p>Current time: {% now "H:i:s" %}</p>
</div>
{% endblock %}

Explanation:

  • First, load the cache library: {% load cache %}.
  • The {% cache %} tag takes at least two arguments: the cache timeout in seconds (e.g., 3600) and a unique name for the cache fragment (e.g., sidebar).
  • You can optionally add more arguments (like request.user.username above) to create different cache versions based on those values. This is called ‘varying’ the cache.
  • Everything between {% cache %} and {% endcache %} will be cached.

For caching arbitrary Python objects or results of functions outside the view/template context, use the low-level cache API:

from django.core.cache import cache
import time
def get_complex_data(user_id):
cache_key = f'complex_data_{user_id}'
result = cache.get(cache_key)
if result is None:
# Data not in cache, calculate it
print(f'Calculating complex data for user {user_id}...')
time.sleep(2) # Simulate expensive operation
result = {'data': f'Some complex data for {user_id}', 'timestamp': time.time()}
# Store in cache for 5 minutes (300 seconds)
cache.set(cache_key, result, 300)
print('Data stored in cache.')
else:
print(f'Retrieved complex data for user {user_id} from cache.')
return result
# Example usage
data1 = get_complex_data(123)
data2 = get_complex_data(123) # This call should hit the cache

Key methods include cache.set(key, value, timeout), cache.get(key, default=None), cache.delete(key), cache.clear(), and more. Refer to the Django documentation for the full API.

Further Reading: