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 wholehttp.HttpResponse
instance which can't be serialized if you use themsgpack
serializer.cache_page
also sendsCache-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.
Comments