If a Django view runs many queries or heavy computation but its data only changes a few times a day, wrap it with @cache_page(60 * 15). Django stores the whole response in the cache and serves it unchanged for 15 minutes, instead of running the view again on every request. This fits list pages, stats pages, and rarely changing detail pages — but never apply it blindly to pages that hold per-user data.

The problem

Imagine your stats page runs a batch of aggregate queries every time it opens, then renders the same table of numbers for every visitor. The more traffic you get, the more load the database takes for results that are nearly identical:

# views.py — without cache: every request reruns everything
from django.shortcuts import render
from django.db.models import Sum
from .models import Order
def thong_ke(request):
don_hang = Order.objects.filter(trang_thai="hoan_thanh")
context = {
"tong_doanh_thu": don_hang.aggregate(tong=Sum("tong_tien"))["tong"],
"so_don": don_hang.count(),
}
return render(request, "thong_ke.html", context)

The problem is not the query itself, but that it repeats pointlessly: day-based stats are still correct a few minutes later, yet the server recomputes them from scratch for every viewer.

The solution

One decorator is all it takes — Django handles the rest:

# views.py — computed only once every 15 minutes
from django.views.decorators.cache import cache_page
from django.shortcuts import render
from django.db.models import Sum
from .models import Order
@cache_page(60 * 15)
def thong_ke(request):
don_hang = Order.objects.filter(trang_thai="hoan_thanh")
context = {
"tong_doanh_thu": don_hang.aggregate(tong=Sum("tong_tien"))["tong"],
"so_don": don_hang.count(),
}
return render(request, "thong_ke.html", context)

First, declare a cache backend in settings.py. In-memory caching is enough while developing; switch to Redis in production:

# settings.py — dev environment
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
}
}

If you do not want to touch the view, wrap it in urls.py instead — handy when the view lives in a third-party app:

# urls.py
from django.urls import path
from django.views.decorators.cache import cache_page
from . import views
urlpatterns = [
path("thong-ke/", cache_page(60 * 15)(views.thong_ke), name="thong_ke"),
]

Three things to remember. First, cache_page distinguishes by the full URL, including the query string, so ?page=2 is a separate cached copy — pagination still works correctly. Second, the cache does not know the data changed: within 15 minutes, a new order still will not show up on the page. So pick the timeout to match how often the data changes, or change key_prefix to “flush” the old cache. Third, a page personalized by cookie must also add @vary_on_cookie, and the decorator order is mandatory: @cache_page on top, @vary_on_cookie below. Reverse the order and the next user may receive the previous user’s cached page — a bug I verified directly before writing this post.

# CORRECT order: cache_page on top, vary_on_cookie below
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie
@cache_page(60 * 15)
@vary_on_cookie
def trang_ca_nhan(request):
return render(request, "trang_ca_nhan.html", {"user": request.user})

Apply it to your project

Start with the slowest page whose data rarely changes: a product list or a stats table. Measure with django-debug-toolbar before and after adding the decorator to see the query count drop to 0 on cache hits. Try a 5–15 minute timeout first, watch it for a day, then extend it. And never cache the cart or a personal dashboard — where each person must see their own data, a wrong cache is worse than no cache.

Related posts

Frequently asked questions

How is cache_page different from cache.get/cache.set?

cache_page caches the entire response by URL, with no manual read/write cache logic. cache.get/cache.set are more flexible: they cache individual pieces of data or querysets and can be used anywhere.

How long should the timeout be?

Match it to how often the data changes. Data that changes daily works well with 15–60 minutes; data that changes every few minutes should only be cached for 1–5 minutes, or not cached at all — optimize the query first instead.

Why does the page still show old content after I save new data?

Because the cache has not expired yet. Fix it by waiting out the timeout, setting a shorter timeout, or changing key_prefix so the old cache is ignored immediately.

Can I cache a page that requires login?

Yes, but you must attach @vary_on_cookie in the correct order as shown in the solution section, so each user gets their own cached copy. If you are not sure, the safest choice is not to cache personal pages.

💬 Have you tried this in your own project? If you hit an error or know a cleaner way, leave a comment below — I read them all and will fold the best ideas into the post.

Sources