-
-
Save rod-dot-codes/dd8f68609ed79bb8d460 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
from rest_framework.response import Response | |
import logging | |
import json | |
import re | |
import dill | |
from django.conf import settings | |
from django.core import cache as django_cache | |
from mock import patch | |
from rest_framework.permissions import SAFE_METHODS | |
logger = logging.getLogger(__name__) | |
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 | |
key = self.get_cache_key(request, *args, **kwargs) | |
response_data = None | |
if 'invalidate' not in self.request.GET: | |
# Check whether the response data is in the cache. | |
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() | |
else: | |
#INVALIDATE | |
keyset = [] | |
#We need to be a bit more through and delete all the keys that include the id. | |
logger.info('Cache key is {0}'.format(key)) | |
#The keys we need to get rid of are | |
logger.info('Cache keys are {}'.format(django_cache.cache.get(self.cache_prefix + '_keys'))) | |
try: | |
if django_cache.cache.get(self.cache_prefix + '_keys') is not None: | |
for key_value in django_cache.cache.get(self.cache_prefix + '_keys'): | |
logger.info('{}'.format(key_value)) | |
django_cache.cache.delete(key_value) | |
django_cache.cache.delete(self.cache_prefix + '_keys') | |
except: | |
pass | |
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' | |
response['cached'] = 'Cached' | |
return response | |
def get_cache_key(self, request, *args, **kwargs): | |
querystring = request.META.get('QUERY_STRING', '') | |
# 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, querystring]) | |
def respond_from_cache(self, cached_data): | |
# Given some cached data, construct a response. | |
content, status, headers = dill.loads(cached_data) | |
content = dill.loads(content) | |
status = dill.loads(status) | |
headers = dill.loads(headers) | |
response = Response(content, status=status, headers=dict(headers)) | |
return response | |
def cache_response(self, key, response): | |
try: | |
data = dill.dumps(response.data) | |
status = dill.dumps(response.status_code) | |
headers = dill.dumps(dict(response.items())) | |
# Cache enough info to recreate the response. | |
django_cache.cache.set(key, dill.dumps((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 | |
except Exception as e: | |
print('Exception occured at Cache:{0}'.format(e)) | |
logger.error('There was a cache error: {0}'.format(e), exc_info=True, extra={ | |
# Optionally pass a request and we'll grab any information we can | |
'request': request, | |
}) | |
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