A proper and simple Django caching middleware that's opt in

Since Django's cache_page is rubbish with middleware, I had to write a new simple middleware that will allow you to opt-in pages to caching (like @cache_page), however will work with various middlewares (e.g. sessions, languages), like the usually Django per-site cache.

Code

This is the code, just put it in a file somewhere

from functools import wraps

class DefaultDontCacheMiddleware():
    """
    A middleware that will mark every request as not to be cached.
    This allows one to use the Django (Update|FetchFrom)CacheMiddleware
    middlewares without having caching turned on for every view. This allows an
    opt-in cache, not opt-out
    """

    def process_request(self, request):
        request._cache_update_cache = False

def mark_for_caching(func):
    """
    Decorator that enabled caching for this page.
    It's similar to ``cache_page`` decorator, but will work with middlewares
    that set headers (e.g. LocaleMiddleWare, SessionMiddleware,
    AuthenticationMiddleware etc.)
    """

    @wraps(func)
    def newfunc(request, *args, **kwargs):
        request._cache_update_cache = True
        return func(request, *args, **kwargs)

    return newfunc

Usage

Middleware

You have to load the DefaultDontCacheMiddleware after the django.middleware.cache.FetchFromCacheMiddleware, this is because the FetchFromCacheMiddleware will, if there is a cache miss, mark that request as something to cache in the UpdateCacheMiddleware. We must override it's behaviour, and tell it not to cache anything by default

View decorator

For each view that you want cached, just put the @mark_for_caching decorator on it, in the same way that you use the @cache_page decorator.

Explaination

I had to look through the Django caching code to try to figure out what was going on, and saw that the cache will only be updated if the request has the _cache_update_cache attritube True. This allows you to prevent caching. It is set to True by the FetchFromCacheMiddleware if there is a cache miss, so that the results of the view function will be cached. After the FetchFromCacheMiddleware is called, our DefaultDontCacheMiddleware is called and it sets that to False, so that by the time it gets back up to the UpdateCacheMiddleware, it's not cached.

However if the view goes through the @mark_for_caching decorator, then that value is set to it's value of True, and the UpdateCacheMiddleware will store the result, along with any of the Vary headers or Cookie/Content-Language headers as apporpriate.

This entry is tagged: