Skip to content

Instantly share code, notes, and snippets.

@mjumbewu
Created December 30, 2013 16:26
Show Gist options
  • Save mjumbewu/8184292 to your computer and use it in GitHub Desktop.
Save mjumbewu/8184292 to your computer and use it in GitHub Desktop.
import re
from django.conf import settings
from django.core import cache as django_cache
from mock import patch
from rest_framework.permissions import SAFE_METHODS
from rest_framework.response import Response
class CachedResourceMixin (object):
@property
def cache_prefix(self):
return self.request.path
def get_cache_prefix(self):
return self.cache_prefix
def get_cache_metakey(self):
prefix = self.cache_prefix
return prefix + '_keys'
@csrf_exempt
def dispatch(self, request, *args, **kwargs):
# Only do the cache for GET, OPTIONS, or HEAD method.
if request.method.upper() not in SAFE_METHODS:
return super(CachedResourceMixin, self).dispatch(request, *args, **kwargs)
self.request = request
# Check whether the response data is in the cache.
key = self.get_cache_key(request, *args, **kwargs)
response_data = django_cache.cache.get(key) or None
# Also check whether the request cache key is managed in the cache.
# This is important, because if it's not managed, then we'll never
# know when to invalidate it. If it's not managed we should just
# assume that it's invalid.
metakey = self.get_cache_metakey()
keyset = django_cache.cache.get(metakey) or set()
if (response_data is not None) and (key in keyset):
cached_response = self.respond_from_cache(response_data)
handler_name = request.method.lower()
def cached_handler(*args, **kwargs):
return cached_response
# Patch the HTTP method
with patch.object(self, handler_name, new=cached_handler):
response = super(CachedResourceMixin, self).dispatch(request, *args, **kwargs)
else:
response = super(CachedResourceMixin, self).dispatch(request, *args, **kwargs)
# Only cache on OK resposne
if response.status_code == 200:
self.cache_response(key, response)
# Disable client-side caching. Cause IE wrongly assumes when it should
# cache.
response['Cache-Control'] = 'no-cache'
return response
def get_cache_key(self, request, *args, **kwargs):
querystring = request.META.get('QUERY_STRING', '')
contenttype = request.META.get('HTTP_ACCEPT', '')
# TODO: Eliminate the jQuery cache busting parameter for now. Get
# rid of this after the old API has been deprecated.
cache_buster_pattern = re.compile(r'&?_=\d+')
querystring = re.sub(cache_buster_pattern, '', querystring)
return ':'.join([self.cache_prefix, contenttype, querystring])
def respond_from_cache(self, cached_data):
# Given some cached data, construct a response.
content, status, headers = cached_data
response = Response(content, status=status, headers=dict(headers))
return response
def cache_response(self, key, response):
data = response.data
status = response.status_code
headers = response.items()
# Cache enough info to recreate the response.
django_cache.cache.set(key, (data, status, headers), settings.API_CACHE_TIMEOUT)
# Also, add the key to the set of pages cached from this view.
meta_key = self.cache_prefix + '_keys'
keys = django_cache.cache.get(meta_key) or set()
keys.add(key)
django_cache.cache.set(meta_key, keys, settings.API_CACHE_TIMEOUT)
return response
class MyAPIView (CachedResourceMixin, generics.RetrieveUpdateDestroyAPIView):
...
@chhantyal
Copy link

Hi mjumbewu, nice mixin.

Could you elaborate a bit here: # Disable client-side caching. Cause IE wrongly assumes when it should cache.

Giving a cache-control header on response would allow client side caching too, which is I think good when response doesn't change that often. IE doesn't work as expected here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment