4

I am trying to use the @cache_page decorator, and invalidate the cache properly. I want to:

  • Cache the home ('index') page.
  • Go to a new page ('non_refreshing_page'), which doesn't touch the page.
  • Return to the home page and see the cached version of the page.
  • Go to a new page ('refreshing_page'), which invalidates the cache for the home page.
  • Go back to the home page, and see a refreshed version of the page.

This almost works. I can see that the cache is being cleared, by using memcdump. But when I go back to the home page and expect to see a refreshed page, I see a cached version. The only way I actually see a refreshed version is by going home -> refreshing_page -> home, and then typing ctrl-R or ctrl-F5.

Am I fighting a browser cache somehow? If so, how do I invalidate the browser cache? I do not face this issue when I comment out the caching decorator, so even if this isn't entirely a django issue, the caching within django seems to be causing the browser to cache the page.

I put my test project on github. The home page loads 10 random numbers, so you can easily see if you have a fresh version of the page or a cached version. I think the most relevant page of the project is views.py.

I adapted my main approach to invalidation from Expire a view-cache in Django?.

Here is my views.py:

from django.shortcuts import render_to_response
from django.template import RequestContext
from django.views.decorators.cache import cache_page

from django.utils.cache import get_cache_key
from django.core.cache import cache
from django.http import HttpRequest
from django.core.urlresolvers import reverse

from random import randint


@cache_page(60 * 10)
def index(request):
    numbers = [randint(1,9) for x in range(0,10)]

    return render_to_response('random_app/index.html',
                              {'numbers': numbers,
                               },
                              context_instance=RequestContext(request))


def refreshing_page(request):
    # Invalidated the index page.
    invalidate_cache('index')
    return render_to_response('random_app/refreshing_page.html',
                              {},
                              context_instance=RequestContext(request))


def non_refreshing_page(request):
    return render_to_response('random_app/non_refreshing_page.html',
                              {},
                              context_instance=RequestContext(request))


def invalidate_cache(view_path, args=[], namespace=None, key_prefix=None):
    """Function to allow invalidating a view-level cache.
    Adapted from: https://stackoverflow.com/questions/2268417/expire-a-view-cache-in-django
    """
    # Usage: invalidate_cache('index', namespace='ed_news', key_prefix=':1:')

    # Create a fake request.
    request = HttpRequest()
    # Get the request path.
    if namespace:
        view_path = namespace + ":" + view_path

    request.path = reverse(view_path, args=args)
    #print 'request:', request

    # Get cache key, expire if the cached item exists.
    # Using the key_prefix did not work on first testing.
    #key = get_cache_key(request, key_prefix=key_prefix)
    page_key = get_cache_key(request)
    header_key = ''

    if page_key:
        # Need to clear page and header cache. Get the header key
        #  from the page key.
        # Typical page key: :1:views.decorators.cache.cache_page..GET.6666cd76f96956469e7be39d750cc7d9.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC
        # Typical header key: :1:views.decorators.cache.cache_header..6666cd76f96956469e7be39d750cc7d9.en-us.UTC
        #  Change _page..GET. to _header..
        #  then lose the second hash.
        import re
        p = re.compile("(.*)_page\.\.GET\.([a-z0-9]*)\.[a-z0-9]*(.*en-us.UTC)")
        m = p.search(page_key)
        header_key = m.groups()[0] + '_header..' + m.groups()[1] + m.groups()[2]

        print '\n\nviews.invalidate_cache'
        print 'page_key:', page_key
        print 'header_key:', header_key

        # If the page/ header have been cached, destroy them.
        if cache.get(page_key):
            # Delete the page and header caches.
            cache.delete(page_key)
            cache.delete(header_key)

            print 'invalidated cache'
            return True

    print "couldn't invalidate cache"
    return False
Community
  • 1
  • 1
japhyr
  • 1,710
  • 2
  • 18
  • 24
  • I think I figured this out. I added some cache-control parameters to the response object. I believe the browser will cache appropriately, but check if the server has a new copy first. I will test further and post an update if my solution really works. The changes are included in the updated repository, but not in the original copy of the view that I posted here. – japhyr Feb 20 '14 at 17:14
  • 1
    Are you confusing the server-side cache (which is what @cache_page is for) with the browser cache? Django responses are generally not cached by the browser. If you want to be sure, open Chrome's dev tools and in its settings you can "disable cache". – Nils Feb 20 '14 at 17:42
  • 1
    Also, the page cache invalidation looks fragile to me - what if they change the key generation? There's another solution http://stackoverflow.com/questions/2268417/expire-a-view-cache-in-django More generally, are you sure you need to do all this? Caching the page is only necessary if it's very expensive to generate or very high-traffic. You could cache the expensive sub-parts instead so the logic about using the cache is in the view itself. – Nils Feb 20 '14 at 17:46
  • I don't think I'm confusing server-side and browser caching. I want Django to cache the results of a few expensive pages. I just opened Chrome dev tools, disabled cache, and my code as written above works. So I think I am seeing browser-caching, which seems to be caused by the caching decorator. – japhyr Feb 21 '14 at 00:27
  • I assume you are calling the regex work to get the header key fragile. I agree; I would have liked to get that key directly from get_cache_key. But now that I've figured out a way to disable the browser caching issue, I find that deleting the page cache is enough. The header being cached does not seem to cause a problem. I recognize that caching is an optimization. I want to be able to serve reasonable traffic on a small server, so I want to understand basic caching. I'm going to launch a small project soon, and I'm looking forward to seeing whether caching is significant or not. – japhyr Feb 21 '14 at 00:32

0 Answers0