Django's cache_page decorator is fatally limited

In a recent project, we enabled caching, but we only want to cache certain pages, not the whole site. Django has a good caching framework and only of the interesting parts is the per-view cache, which allows you to cache nothing, except a specific view (or multiple specific views). It's opt-in caching, not opt-out caching.

However after deploying it, we had to disable it, because it was caching pages for logged in users. Some of our unittests were also failing because you could not change language (as part of our i18n effort).

This is because the @cache_page decorator cannot work fully. Since it's a view decorator, it is like a middleware that is at the very bottom (i.e. run last for requests, but run first for response). The response object from your view function doesn't have various HTTP headers set, like session cookie information (which include the logged in user), this is set by the AuthenticationMiddleware, or Content-Language (which says what language the content is in), which is set by LocaleMiddleware. This means that your response can't be cached based on those headers (since they aren't there!)

The Django documentation says to use Vary headers to keep your cache aware of what it should cahe based on. But since the Cookie or Content-Language headers haven't been added to the response by the time the view function is finished (and inside the cache_page decorator) it can't cache based on the.

This means your view will be cached once (perhaps by a logged in user), and given out to people who aren't logged in. This is obviously very bad, and hence @cache_page probably shouldn't be used.

(NB: There is a django bug report on this issue)

This entry is tagged: