Per-View cache with Django & Wagtail CMS

Tags: wagtail, cache

Using django's site-wide cache middleware is pretty straight forward and acts as expected on a wagtail site.  But what about per-view caching?

As it turns out, using django's per-view caching is quite easy to implement on wagtail Page sub-classes.  Out of the box you can just do:



from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
from wagtail.wagtailcore.models import Page

@method_decorator(cache_page(300), name="serve")
class MyPage(Page):
    # my class code here
    

You can also add optional keyword arguments for 'key_prefix' and 'cache' to the 'cache_page' decorator.  This gives quite a lot of flexibility and control over where a view is cached and what it's cache_key will be based on.

But what if you wanted to have a dynamic 'key_prefix', say based on the MyPage primary key?  Well then you would need access to the instance of MyPage (self) and you can't do this with 'method_decorator'.

I wrote a decorator (based on 'method_decorator') that works for wagtail's Page and its sub-classes:


from django.views.decorators.cache import cache_page

def wagtail_cache_page(timeout, cache="", key_prefix="", append_key_prefix_with_pk=False):
    def inner(obj):
        """
        obj can be a class or method.
        Must be either a wagtail Page subclass or
         the 'serve' method of a wagtail Page subclass
        """
        is_class = isinstance(obj, type)
        if is_class:
            if hasattr(obj, 'serve'):
                serve = getattr(obj, 'serve')
                if not callable(serve):
                    raise TypeError("Cannot decorate 'serve' as it isn't a callable ")
            else:
                raise ValueError("The Class must have a 'serve' method. ")
        else:
            serve = obj

        def wrapper(self, request, *args, **kwargs):
            suffix = str(self.pk) if append_key_prefix_with_pk else ''

            @cache_page(timeout, cache=cache, key_prefix=key_prefix + suffix)
            def _cache_page_hack(request, self, *args, **kwargs):
                return serve(self, request, *args, **kwargs)
            return _cache_page_hack(request, self, *args, **kwargs)
        if is_class:
            setattr(obj, 'serve', wrapper)
            return obj
        return wrapper
    return inner
    

This will append the instance primary key to the 'key_prefix', which can be quite handy when trying to retrieve a cache_key for a specific page instance. You could tweak it to add any other info from the instance, if you wanted to.

You use it like this:


from my_app.decorators import wagtail_cache_page
from wagtail.wagtailcore.models import Page

@wagtail_cache_page(60*10, cache="my_cache", key_prefix="MyPage", append_key_prefix_with_pk=True)
class MyPage(Page):
    # class stuff here
    
Loading comments...
';