Created
December 30, 2013 16:26
-
-
Save mjumbewu/8184292 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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): | |
... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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?