Wednesday, 20 June, 2018 UTC


Summary

I use this a lot. It has served me very well. The code:
import hashlib import functools import markus # optional from django.core.cache import cache from django import http from django.utils.encoding import force_bytes, iri_to_uri metrics = markus.get_metrics(__name__) # optional def json_response_cache_page_decorator(seconds): """Cache only when there's a healthy http.JsonResponse response.""" def decorator(func): @functools.wraps(func) def inner(request, *args, **kwargs): cache_key = 'json_response_cache:{}:{}'.format( func.__name__, hashlib.md5(force_bytes(iri_to_uri( request.build_absolute_uri() ))).hexdigest() ) content = cache.get(cache_key) if content is not None: # metrics is optional metrics.incr( 'json_response_cache_hit', tags=['view:{}'.format(func.__name__)] ) return http.HttpResponse( content, content_type='application/json' ) response = func(request, *args, **kwargs) if ( isinstance(response, http.JsonResponse) and response.status_code in (200, 304) ): cache.set(cache_key, response.content, seconds) return response return inner return decorator 
To use it simply add to Django view functions that might return a http.JsonResponse. For example, something like this:
@json_response_cache_page_decorator(60) def search(request): q = request.GET.get('q') if not q: return http.HttpResponseBadRequest('no q') results = search_database(q) return http.JsonResponse({ 'results': results, }) 
The reasons I use this instead of django.views.decorators.cache.cache_page() is because of a couple of reasons.
  • cache_page generates cache keys that don't contain the view function name.
  • cache_page tries to cache the whole http.HttpResponse instance which can't be serialized if you use the msgpack serializer.
  • cache_page also sends Cache-Control headers which is not always what you want.
  • Not possible to inject your own custom code such as my usage of metrics.
Disclaimer: This snippet of code comes from a side-project that has a very specific set of requirements. They're rather unique to that project and I have a full picture of the needs. E.g. I know what specific headers matter and don't matter. Your project might be different. For example, perhaps you don't have markus to handle your metrics. Or perhaps you need to re-write the query string for something to normalize the cache key differently. Point being, take the snippet of code as inspiration when you too find that django.views.decorators.cache.cache_page() isn't good enough for your Django view functions.