oscarmlage oscarmlage

Django and memcache: clear cache keys

Written by oscarmlage on

Let's play Django with Memcached. As the great framework Django is, it's so easy to activate any kind of cache in your project. Memcached is one of the options, but you can also work with DatabaseCache, FileBasedCache, LocMemCache, MemcachedCache, DummyCache (a kind of non-cache very useful for devel/test enviroments) or - of course - your own CustomCache if you want.

Activating cache

It's too easy to activate the cache feature, it's enough to set the preferences in settings, install python-memcached in your enviroment (in case you will use MemcachedCache), and not much more to do. A couple of examples:

1. Basic FileBasedCache settings:

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
    }
}

2. MemcachedCache settings:

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

3. Depending on the enviroment you can use MemcachedCache and DummyCache:

# settings.devel.py
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}
# settings.production.py
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

Setting the places where cache will act

Now that we have our project with a configured kind of cache, we must to say where and when to activate it. There are multiple ways to do it (on the whole site, views, templates, urls...). I'm going to use... let's say urls. So, in our urls.py we have to set a time and activate the cache:

# urls.py

from django.views.decorators.cache import cache_page

ctime = (60 * 24) # A day
urlpatterns = patterns('',
    url(r'^$',
        cache_page(ctime)(BlogIndexView.as_view()),
        {},
        'blog-index'
        ),
        ...
)

A simple server reload will be enough to have cache running. We can see it on action with django-debug-toolbar, django-memcache-status or something like that.

Clean a specific key cache

And now the funniest part. For example, talking about a blog tool, when you write a new post (or editing older one) the software should be able to remove some cache keys, i.e. the blog-index one (because you have a new post) and the post-detail other (because you must be able to inmediately see the changes in the post you're editting).

Following this link I've created a cache.py with this content:

# cache.py

# -*- coding: utf-8 -*-

def expire_view_cache(
    view_name,
    args=[],
    namespace=None,
    key_prefix=None,
    method="GET"):

    """
    This function allows you to invalidate any view-level cache.

    view_name: view function you wish to invalidate or it's named url pattern
    args: any arguments passed to the view function
    namepace: optioal, if an application namespace is needed
    key prefix: for the @cache_page decorator for the function (if any)

    http://stackoverflow.com/questions/2268417/expire-a-view-cache-in-django
    added: method to request to get the key generating properly
    """

    from django.core.urlresolvers import reverse
    from django.http import HttpRequest
    from django.utils.cache import get_cache_key
    from django.core.cache import cache
    from django.conf import settings
    # create a fake request object
    request = HttpRequest()
    request.method = method
    if settings.USE_I18N:
        request.LANGUAGE_CODE = settings.LANGUAGE_CODE
    # Loookup the request path:
    if namespace:
        view_name = namespace + ":" + view_name
    request.path = reverse(view_name, args=args)
    # get cache key, expire if the cached item exists:
    key = get_cache_key(request, key_prefix=key_prefix)
    if key:
        if cache.get(key):
            cache.set(key, None, 0)
        return True
    return False

And last step is to call the expire_view_cache function on model form save hook (admin.py in this case):

# admin.py

from cache import expire_view_cache

class PostAdminForm(admin.ModelAdmin):
    ...
    def save_model(self, request, obj, form, change):
        expire_view_cache("blog-index")
        expire_view_cache("post-detail", [obj.slug])

And that's all, we are able to clean/purge/remove the cache when a new post is added or edited. As you can see in the code, cache is fun but you have to be careful to set it on the right way.