complete new cache_page decorator
key is set by user id
from django.core.cache import cache as core_cache
'''
refer:
http://stackoverflow.com/questions/20146741/django-per-user-view-caching
https://djangosnippets.org/snippets/2524/
'''
def cache_key(request):
if request.user.is_anonymous():
user = 'anonymous'
else:
user = request.user.id
q = getattr(request, request.method)
q.lists()
urlencode = q.urlencode(safe='()')
CACHE_KEY = 'view_cache_%s_%s_%s' % (request.path, user, urlencode)
return CACHE_KEY
def cache_per_user_function(ttl=None, prefix=None, cache_post=False):
'''
Decorator which caches the view for each User
* ttl - the cache lifetime, do not send this parameter means that the cache
will last until the restart server or decide to remove it
* prefix - Prefix to use to store the response in the cache. If not informed,
it will be used 'view_cache _' + function.__ name__
* cache_post - Determine whether to make requests cache POST
* The caching for anonymous users is shared with everyone
How to use it:
@cache_per_user_function(ttl=3600, cache_post=False)
def my_view(request):
return HttpResponse("LOL %s" % (request.user))
'''
def decorator(function):
def apply_cache(request, *args, **kwargs):
CACHE_KEY = cache_key(request)
# Gera a chave do cache
if prefix:
CACHE_KEY = '%s_%s' % (prefix, CACHE_KEY)
# Verifica se pode fazer o cache do request
if not cache_post and request.method == 'POST':
can_cache = False
else:
can_cache = True
if can_cache:
response = core_cache.get(CACHE_KEY, None)
else:
response = None
if not response:
response = function(request, *args, **kwargs)
if can_cache:
core_cache.set(CACHE_KEY, response, ttl)
return response
return apply_cache
return decorator
def cache_per_user_method(ttl=None, prefix=None, cache_post=False):
'''
Decorator which caches the view for each User
* ttl - the cache lifetime, do not send this parameter means that the cache
will last until the restart server or decide to remove it
* prefix - Prefix to use to store the response in the cache. If not informed,
it will be used 'view_cache _' + function.__ name__
* cache_post - Determine whether to make requests cache POST
* The caching for anonymous users is shared with everyone
How to use it:
@cache_per_user_method(ttl=3600, cache_post=False)
def get(self, request):
...
'''
def decorator(view_method):
def apply_cache(obj, request, *args, **kwargs):
CACHE_KEY = cache_key(request)
# Gera a chave do cache
if prefix:
CACHE_KEY = '%s_%s' % (prefix, CACHE_KEY)
# Verifica se pode fazer o cache do request
if not cache_post and request.method == 'POST':
can_cache = False
else:
can_cache = True
if can_cache:
response = core_cache.get(CACHE_KEY, None)
else:
response = None
if not response:
response = view_method(obj, request, *args, **kwargs)
if can_cache and hasattr(response, 'render'):
core_cache.set(CACHE_KEY, response.render(), ttl)
return response
return apply_cache
return decorator
keys are different between authenticated and anonymous users
from django.views.decorators.cache import cache_page
def cache_on_auth(timeout):
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
return cache_page(timeout, key_prefix="_auth_%s_" % request.user.is_authenticated())(view_func)(request, *args, **kwargs)
return _wrapped_view
return decorator
use patch_vary_headers to change Vary in Headers of each request. Therefore learn_cache_key function of cache_key will use this vary header to generate different key for different user
# encoding: utf-8
import hashlib
import random
from django.contrib import messages
from django.conf import settings
from django.utils.cache import patch_vary_headers
class AddUserIDHashMiddleware(object):
"""
Middleware that adds a HTTP header X-UserIDHash to request &
responses, with a hash of the user id. All anonymous users (i.e. not
logged in) will have the same value of this. This allows one to cache pages
so that anonymous users can all get the same version of a page, even if
they have session cookies
"""
def _user_id_hash(self, request):
"""
Given a request, try to figure out the UserIDHash
"""
try:
if request.user.is_authenticated():
user_id_string = str(request.user.id)
else:
# Anonymous user
user_id_string = "none"
except Exception:
# Can happen if there is no request.session. This happened in some
# unittests, /shouldn't/ happen in Real World™, but just in case...
if not hasattr(request, 'session'):
# No session, so this is equivalent to an anonymous user
user_id_string = "none"
else:
# request.session exists, but we got an error doing request.user.is_authenticated()
# WTF?!
# TODO add some logging
# default to something different
user_id_string = "unknown"
user_id_string = user_id_string.rjust(10)
# add some salt to prevent guessing the user id
user_id_string = "AddUserIDHashMiddleware(%s)(%s)" % (settings.SECRET_KEY, user_id_string)
# Little bit of obfucation here, SHA1 hash the id string, so no-one will
# be able to guess user id's, or know how many users we have
user_id_string = hashlib.sha1(user_id_string).hexdigest()
# This is a very stupid hack to prevent messages being displayed when the user has message to be shown
# Append a random string to the user id string if there are any messages to be shown. It is *highly* unlikely
# there'll be an overlap, so this will mean this request would not be cached.
#
# Initially, I tried to replace this middleware with a generic one that looks at the messages (as well as user id),
# but if you iterate over the messages, it clears them in the request, so they aren't shown in the html (v. bad!), you can set the
# message storage to not clear, but then they aren't cleared when they are shown, so they are shown every page!
# If you clear the messages after the response, then if there is a 30X response, the messages are cleared on the 30X response
# and not available to be shown in the html.
if len(messages.get_messages(request)) > 0:
user_id_string += '.' + str(random.randint(0, 1000000))
return user_id_string
def process_request(self, request):
# Add request header.
# When using this for caching, the FetchFromCacheMiddleware must be
# below this middleware, and it needs the request to have a
# X-UserIDHash request header so it can look up the cache to
# see if it can return a cached response
request.META['HTTP_X_USERIDHASH'] = self._user_id_hash(request)
def process_response(self, request, response):
# Add response header, based on request.
# The UpdateCacheMiddleware must be above this middleware, so that if
# it wants to store this response in the cache (for serving later),
# then it'll know what key to use to store it under
response['X-UserIDHash'] = self._user_id_hash(request)
# Tell caches in future that this header is important
patch_vary_headers(response, ['X-UserIDHash'])
return response