Here I'm going to explain a solution I had to make for a site I recently launched. Basically, I wanted to cache the whole page in memcache and set the appropriate Expires and Cache-Control headers so that my view was only rendered once an hour and parts of the page needs to be unique (i.e. "Hi, logged in as xxxx")

The advantages is great: The page loads fast, content is stored in memcache every hour, page still appears to be dynamic.

The disadvantages are not so great: the AJAX loads fast but causes a flicker

Basically, I wrote a custom decorator called custom_cache_page(<delay in seconds>) that works like the normal cache_page(<delay in seconds>) decorator available in stock Django. However, my decorator inserts a piece of HTML into the rendered HTML (before it's stored in memcache) that I later use to update certain elements of the page with AJAX instead of server side. Enough talking, let's look at the code!:


from django.utils.decorators import decorator_from_middleware
from django.middleware.cache import CacheMiddleware
class CustomCacheMiddleware(CacheMiddleware):
   def __init__(self, cache_delay=0, *args, **kwargs):
       super(CustomCacheMiddleware, self).__init__(*args, **kwargs)
       self.cache_delay = cache_delay

   def process_response(self, request, response):
       if self.cache_delay:
           extra_js = '<script type="text/javascript">var '\
                      'CACHE_CONTROL=%s;</script>' %\
                       self.cache_delay
           response.content = response.content.replace(u'</body>',
                                             u'%s\n</body>' % extra_js)

       return super(CustomCacheMiddleware, self
                   ).process_response(request, response)

custom_cache_page = decorator_from_middleware(CustomCacheMiddleware)

if settings.DEBUG:
   def custom_cache_page(delay):
       def rendered(view):
           def inner(request, *args, **kwargs):
               return view(request, *args, **kwargs)
           return inner
       return rendered

@custom_cache_page(60 * 60 * 1) # 1 hours
def my_expensive_but_cacheable_view(request):
   # only run max. once every hour
   complex_and_cpu_intensive_calculation()
   ...

What you now get is that in every page that is server-side cached in memcache (and set Expires and Cache-Control headers) get a little piece of Javascript code inserted into the rendered HTML. Now, all I need to do is to write some jQuery code that loads the navigation menu dynamically but instigate it from a AJAX request. Your mileage here might vary (put this in your base.html or whatever you call it):


$(function() {
  if (typeof CACHE_CONTROL != "undefined" &amp;&amp; CACHE_CONTROL) {
     // the page is cached, need to use AJAX to load what should be dynamic
     $('#nav').load('/_nav.html');
  }
});

I usually prefix all my views with an underscore when they only return a limited chunk of HTML rather than a whole HTML document.

Discussion

98% of my pages are deliberately not cached because they either aren't expensive to render or really can't be cached because of logged in users or whatnot.

This solution has the added benefit of being totally contained in Django, so it doesn't require any work on the nginx/apache/lighttpd front end but if you really want some speed (e.g. getting nginx talking directly to memcache) you can still use the above solution to get what you want.

My little decorator can be improved admittedly. You might perhaps add a dynamic check if the page should not be cache for example. Or perhaps some other available trick for invalidating the cache if you're really need to. Or perhaps some other tricks can be made to automate the Javascript AJAX loading.

Comments

Tiago S.

Thanks for the tip. I liked they way you handled it.

I've seen an approach like this before(ie, loading a small snippet of uncacheable data via js) and your approach is a lot cleaner.

Your email will never ever be published.

Previous:
What a super user-friendly menu! August 22, 2009
Next:
To sub-select or not sub-select in PostgreSQL August 31, 2009 Linux
Related by category:
How to avoid a count query in Django if you can February 14, 2024 Django
How to have default/initial values in a Django form that is bound and rendered January 10, 2020 Django
My site's now NextJS - And I (almost) regret it already December 17, 2021 Django
How to sort case insensitively with empty strings last in Django April 3, 2022 Django
Related by keyword:
Be very careful with your add_header in Nginx! You might make your site insecure February 11, 2018 Linux, Web development, Nginx
ssl_session_cache in Nginx and the ab benchmark December 31, 2010 Linux, DoneCal
How I simulate a CDN with Nginx May 15, 2019 Python, Nginx
fcgi vs. gunicorn vs. uWSGI April 9, 2010 Python, Linux, Django