Caché en Django

Intermedio
Django
Django
Actualizado: 18/04/2026

Configuración de caché

Django soporta múltiples backends de caché. Redis es la opción recomendada en producción:

pip install django-redis

Diagrama conceptual de Caché en Django

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'COMPRESSOR': 'django_redis.compressors.zlib.ZlibCompressor',
        },
        'KEY_PREFIX': 'miapp',
        'TIMEOUT': 300,  # 5 minutos por defecto
    },
    'sesiones': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/2',
        'OPTIONS': {'CLIENT_CLASS': 'django_redis.client.DefaultClient'},
    }
}

# Para desarrollo (caché en memoria, sin Redis)
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    }
}

cache_page: caché de vista completa

from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie, vary_on_headers

@cache_page(60 * 15)  # 15 minutos
def catalogo_publico(request):
    productos = Producto.objects.filter(activo=True).select_related('categoria')
    return render(request, 'catalogo.html', {'productos': productos})

# Con vary: diferente caché según el idioma
@cache_page(60 * 60)
@vary_on_headers('Accept-Language')
def pagina_inicio(request):
    return render(request, 'inicio.html')

# En CBV
from django.utils.decorators import method_decorator

@method_decorator(cache_page(60 * 15), name='dispatch')
class CatalogoView(ListView):
    model = Producto
    template_name = 'catalogo.html'

API de bajo nivel

from django.core.cache import cache

# Almacenar en caché
cache.set('productos_destacados', lista_productos, timeout=60 * 30)  # 30 min
cache.set('config_sitio', config_dict)  # Usa el timeout por defecto

# Recuperar de caché
productos = cache.get('productos_destacados')
if productos is None:
    # Cache miss: calcular y almacenar
    productos = Producto.objects.filter(destacado=True).select_related('categoria')
    cache.set('productos_destacados', list(productos), timeout=60 * 30)

# Patrón cache-or-set
from django.core.cache import cache

def obtener_estadisticas():
    clave = 'estadisticas_globales'
    stats = cache.get(clave)
    if stats is None:
        stats = {
            'total_usuarios': User.objects.count(),
            'total_productos': Producto.objects.filter(activo=True).count(),
            'total_pedidos_hoy': Pedido.objects.filter(fecha__date=date.today()).count(),
        }
        cache.set(clave, stats, timeout=60 * 5)  # 5 minutos
    return stats

# Eliminar de caché (invalidación)
cache.delete('productos_destacados')
cache.delete_many(['clave1', 'clave2', 'clave3'])

# Incrementar contador atómicamente
cache.set('visitas_articulo_1', 0, timeout=None)
cache.incr('visitas_articulo_1')

# Comprobar si existe
if cache.has_key('productos_destacados'):
    pass

Fragment caching en plantillas

{% load cache %}

<!-- Cachear un fragment durante 30 minutos -->
{% cache 1800 menu_navegacion %}
    <nav>
        {% for categoria in categorias %}
            <a href="{{ categoria.get_absolute_url }}">{{ categoria.nombre }}</a>
        {% endfor %}
    </nav>
{% endcache %}

<!-- Caché diferente por usuario -->
{% cache 600 carrito_usuario request.user.pk %}
    <div class="carrito">
        {{ carrito|length }} producto(s) — {{ carrito.total }} €
    </div>
{% endcache %}

<!-- Caché diferente por idioma -->
{% cache 3600 pagina_inicio LANGUAGE_CODE %}
    <div class="bienvenida">
        {% trans "Bienvenido a nuestra tienda" %}
    </div>
{% endcache %}

Invalidación con señales

La invalidación de caché es el problema más difícil del caching. Usar señales para invalidar automáticamente:

from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache

@receiver([post_save, post_delete], sender=Producto)
def invalidar_cache_catalogo(sender, **kwargs):
    """Invalida la caché del catálogo cuando cambia algún producto."""
    cache.delete_many([
        'productos_destacados',
        'catalogo_publico',
        'estadisticas_globales',
    ])
    # Invalidar por patrón con django-redis
    cache.delete_pattern('catalogo:*')

@receiver(post_save, sender=Categoria)
def invalidar_cache_categorias(sender, **kwargs):
    cache.delete('menu_navegacion')
    cache.delete('categorias_activas')

Caché por versión

Django soporta versionado de caché para invalidar grupos de claves:

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'VERSION': 1,
    }
}

# Invalidar toda la versión incrementando el número
from django.core.cache import cache
cache.clear()  # Elimina todo el caché de la versión actual
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Django

Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Configurar el backend de caché con Redis en settings.py. Aplicar caché a nivel de vista completa con @cache_page. Usar la API de bajo nivel cache.get(), cache.set() y cache.delete(). Implementar fragment caching en plantillas con {% cache %}. Gestionar la invalidación de caché cuando los datos cambian.