summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2010-12-21 15:19:19 +0000
committerRussell Keith-Magee <russell@keith-magee.com>2010-12-21 15:19:19 +0000
commit673e6fc7fb243ed44841b9969d26a161c25733b3 (patch)
tree01bb413490cf72d003d6a31b3938686b84476fb8 /django
parent3cf8502d35cdfbb4f868a848b1f38dcc275f6be1 (diff)
Fixed #11675 -- Added support for the PyLibMC cache library. In order to support this, and clean up some other 1.3 caching additions, this patch also includes some changes to the way caches are defined. This means you can now have multiple caches, in the same way you have multiple databases. A huge thanks to Jacob Burch for the work on the PyLibMC backend, and to Jannis for his work on the cache definition changes.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@15005 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
-rw-r--r--django/conf/global_settings.py7
-rw-r--r--django/contrib/gis/db/backends/spatialite/creation.py13
-rw-r--r--django/core/cache/__init__.py124
-rw-r--r--django/core/cache/backends/base.py37
-rw-r--r--django/core/cache/backends/db.py12
-rw-r--r--django/core/cache/backends/dummy.py6
-rw-r--r--django/core/cache/backends/filebased.py10
-rw-r--r--django/core/cache/backends/locmem.py23
-rw-r--r--django/core/cache/backends/memcached.py113
-rw-r--r--django/db/backends/creation.py14
-rw-r--r--django/middleware/cache.py37
-rw-r--r--django/views/decorators/cache.py13
12 files changed, 310 insertions, 99 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index f23c55d20d..0017f46708 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -431,14 +431,15 @@ SESSION_FILE_PATH = None # Directory to store ses
# CACHE #
#########
+# New format
+CACHES = {
+}
# The cache backend to use. See the docstring in django.core.cache for the
# possible values.
CACHE_BACKEND = 'locmem://'
-CACHE_VERSION = 1
-CACHE_KEY_PREFIX = ''
-CACHE_KEY_FUNCTION = None
CACHE_MIDDLEWARE_KEY_PREFIX = ''
CACHE_MIDDLEWARE_SECONDS = 600
+CACHE_MIDDLEWARE_ALIAS = 'default'
####################
# COMMENTS #
diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py
index 07147a1f4a..41baf53e7f 100644
--- a/django/contrib/gis/db/backends/spatialite/creation.py
+++ b/django/contrib/gis/db/backends/spatialite/creation.py
@@ -1,5 +1,7 @@
import os
from django.conf import settings
+from django.core.cache import get_cache
+from django.core.cache.backends.db import BaseDatabaseCache
from django.core.exceptions import ImproperlyConfigured
from django.core.management import call_command
from django.db.backends.sqlite3.creation import DatabaseCreation
@@ -28,11 +30,12 @@ class SpatiaLiteCreation(DatabaseCreation):
self.load_spatialite_sql()
call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
- if settings.CACHE_BACKEND.startswith('db://'):
- from django.core.cache import parse_backend_uri
- _, cache_name, _ = parse_backend_uri(settings.CACHE_BACKEND)
- call_command('createcachetable', cache_name)
-
+ for cache_alias in settings.CACHES:
+ cache = get_cache(cache_alias)
+ if isinstance(cache, BaseDatabaseCache):
+ from django.db import router
+ if router.allow_syncdb(self.connection.alias, cache.cache_model_class):
+ call_command('createcachetable', cache._table, database=self.connection.alias)
# Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database.
cursor = self.connection.cursor()
diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py
index d26eabd0c3..4cc742dedf 100644
--- a/django/core/cache/__init__.py
+++ b/django/core/cache/__init__.py
@@ -12,8 +12,13 @@ get_cache() function made available here. get_cache() takes a backend URI
(e.g. "memcached://127.0.0.1:11211/") and returns an instance of a backend
cache class.
-See docs/cache.txt for information on the public API.
+See docs/topics/cache.txt for information on the public API.
"""
+from django.conf import settings
+from django.core import signals
+from django.core.cache.backends.base import (
+ InvalidCacheBackendError, CacheKeyWarning, BaseCache)
+from django.utils import importlib
try:
# The mod_python version is more efficient, so try importing it first.
@@ -27,10 +32,9 @@ except ImportError:
# PendingDeprecationWarning
from cgi import parse_qsl
-from django.conf import settings
-from django.core import signals
-from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning
-from django.utils import importlib
+__all__ = [
+ 'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS'
+]
# Name for use in settings file --> name of module in "backends" directory.
# Any backend scheme that is not in this dictionary is treated as a Python
@@ -43,6 +47,8 @@ BACKENDS = {
'dummy': 'dummy',
}
+DEFAULT_CACHE_ALIAS = 'default'
+
def parse_backend_uri(backend_uri):
"""
Converts the "backend_uri" into a cache scheme ('db', 'memcached', etc), a
@@ -67,32 +73,102 @@ def parse_backend_uri(backend_uri):
return scheme, host, params
-def get_cache(backend_uri, key_prefix=None, version=None, key_func=None):
- if key_prefix is None:
- key_prefix = settings.CACHE_KEY_PREFIX
- if version is None:
- version = settings.CACHE_VERSION
- if key_func is None:
- key_func = settings.CACHE_KEY_FUNCTION
+if not settings.CACHES:
+ import warnings
+ warnings.warn(
+ "settings.CACHE_* is deprecated; use settings.CACHES instead.",
+ PendingDeprecationWarning
+ )
+ # Mapping for new-style cache backend api
+ backend_classes = {
+ 'memcached': 'memcached.CacheClass',
+ 'locmem': 'locmem.LocMemCache',
+ 'file': 'filebased.FileBasedCache',
+ 'db': 'db.DatabaseCache',
+ 'dummy': 'dummy.DummyCache',
+ }
+ engine, host, params = parse_backend_uri(settings.CACHE_BACKEND)
+ if engine in backend_classes:
+ engine = 'django.core.cache.backends.%s' % backend_classes[engine]
+ defaults = {
+ 'BACKEND': engine,
+ 'LOCATION': host,
+ }
+ defaults.update(params)
+ settings.CACHES[DEFAULT_CACHE_ALIAS] = defaults
- if key_func is not None and not callable(key_func):
- key_func_module_path, key_func_name = key_func.rsplit('.', 1)
- key_func_module = importlib.import_module(key_func_module_path)
- key_func = getattr(key_func_module, key_func_name)
+if DEFAULT_CACHE_ALIAS not in settings.CACHES:
+ raise ImproperlyConfigured("You must define a '%s' cache" % DEFAULT_CACHE_ALIAS)
- scheme, host, params = parse_backend_uri(backend_uri)
- if scheme in BACKENDS:
- name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
+def parse_backend_conf(backend, **kwargs):
+ """
+ Helper function to parse the backend configuration
+ that doesn't use the URI notation.
+ """
+ # Try to get the CACHES entry for the given backend name first
+ conf = settings.CACHES.get(backend, None)
+ if conf is not None:
+ args = conf.copy()
+ backend = args.pop('BACKEND')
+ location = args.pop('LOCATION', '')
+ return backend, location, args
else:
- name = scheme
- module = importlib.import_module(name)
- return module.CacheClass(host, params, key_prefix=key_prefix, version=version, key_func=key_func)
+ # Trying to import the given backend, in case it's a dotted path
+ mod_path, cls_name = backend.rsplit('.', 1)
+ try:
+ mod = importlib.import_module(mod_path)
+ backend_cls = getattr(mod, cls_name)
+ except (AttributeError, ImportError):
+ raise InvalidCacheBackendError("Could not find backend '%s'" % backend)
+ location = kwargs.pop('LOCATION', '')
+ return backend, location, kwargs
+ raise InvalidCacheBackendError(
+ "Couldn't find a cache backend named '%s'" % backend)
+
+def get_cache(backend, **kwargs):
+ """
+ Function to load a cache backend dynamically. This is flexible by design
+ to allow different use cases:
-cache = get_cache(settings.CACHE_BACKEND)
+ To load a backend with the old URI-based notation::
+
+ cache = get_cache('locmem://')
+
+ To load a backend that is pre-defined in the settings::
+
+ cache = get_cache('default')
+
+ To load a backend with its dotted import path,
+ including arbitrary options::
+
+ cache = get_cache('django.core.cache.backends.memcached.MemcachedCache', **{
+ 'LOCATION': '127.0.0.1:11211', 'TIMEOUT': 30,
+ })
+
+ """
+ try:
+ if '://' in backend:
+ # for backwards compatibility
+ backend, location, params = parse_backend_uri(backend)
+ if backend in BACKENDS:
+ backend = 'django.core.cache.backends.%s' % BACKENDS[backend]
+ params.update(kwargs)
+ mod = importlib.import_module(backend)
+ backend_cls = mod.CacheClass
+ else:
+ backend, location, params = parse_backend_conf(backend, **kwargs)
+ mod_path, cls_name = backend.rsplit('.', 1)
+ mod = importlib.import_module(mod_path)
+ backend_cls = getattr(mod, cls_name)
+ except (AttributeError, ImportError), e:
+ raise InvalidCacheBackendError(
+ "Could not find backend '%s': %s" % (backend, e))
+ return backend_cls(location, params)
+
+cache = get_cache(DEFAULT_CACHE_ALIAS)
# Some caches -- python-memcached in particular -- need to do a cleanup at the
# end of a request cycle. If the cache provides a close() method, wire it up
# here.
if hasattr(cache, 'close'):
signals.request_finished.connect(cache.close)
-
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index 1296a853a5..513adb48fe 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -2,8 +2,10 @@
import warnings
+from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
from django.utils.encoding import smart_str
+from django.utils.importlib import import_module
class InvalidCacheBackendError(ImproperlyConfigured):
pass
@@ -15,38 +17,55 @@ class CacheKeyWarning(DjangoRuntimeWarning):
MEMCACHE_MAX_KEY_LENGTH = 250
def default_key_func(key, key_prefix, version):
- """Default function to generate keys.
+ """
+ Default function to generate keys.
Constructs the key used by all other methods. By default it prepends
- the `key_prefix'. CACHE_KEY_FUNCTION can be used to specify an alternate
+ the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
function with custom key making behavior.
"""
return ':'.join([key_prefix, str(version), smart_str(key)])
+def get_key_func(key_func):
+ """
+ Function to decide which key function to use.
+
+ Defaults to ``default_key_func``.
+ """
+ if key_func is not None:
+ if callable(key_func):
+ return key_func
+ else:
+ key_func_module_path, key_func_name = key_func.rsplit('.', 1)
+ key_func_module = import_module(key_func_module_path)
+ return getattr(key_func_module, key_func_name)
+ return default_key_func
+
class BaseCache(object):
- def __init__(self, params, key_prefix='', version=1, key_func=None):
- timeout = params.get('timeout', 300)
+ def __init__(self, params):
+ timeout = params.get('timeout', params.get('TIMEOUT', 300))
try:
timeout = int(timeout)
except (ValueError, TypeError):
timeout = 300
self.default_timeout = timeout
- max_entries = params.get('max_entries', 300)
+ options = params.get('OPTIONS', {})
+ max_entries = params.get('max_entries', options.get('MAX_ENTRIES', 300))
try:
self._max_entries = int(max_entries)
except (ValueError, TypeError):
self._max_entries = 300
- cull_frequency = params.get('cull_frequency', 3)
+ cull_frequency = params.get('cull_frequency', options.get('CULL_FREQUENCY', 3))
try:
self._cull_frequency = int(cull_frequency)
except (ValueError, TypeError):
self._cull_frequency = 3
- self.key_prefix = smart_str(key_prefix)
- self.version = version
- self.key_func = key_func or default_key_func
+ self.key_prefix = smart_str(params.get('KEY_PREFIX', ''))
+ self.version = params.get('VERSION', 1)
+ self.key_func = get_key_func(params.get('KEY_FUNCTION', None))
def make_key(self, key, version=None):
"""Constructs the key used by all other methods. By default it
diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
index 68cd2e015e..495812a48d 100644
--- a/django/core/cache/backends/db.py
+++ b/django/core/cache/backends/db.py
@@ -25,16 +25,16 @@ class Options(object):
self.managed = True
self.proxy = False
-class BaseDatabaseCacheClass(BaseCache):
- def __init__(self, table, params, key_prefix='', version=1, key_func=None):
- BaseCache.__init__(self, params, key_prefix, version, key_func)
+class BaseDatabaseCache(BaseCache):
+ def __init__(self, table, params):
+ BaseCache.__init__(self, params)
self._table = table
class CacheEntry(object):
_meta = Options(table)
self.cache_model_class = CacheEntry
-class CacheClass(BaseDatabaseCacheClass):
+class DatabaseCache(BaseDatabaseCache):
def get(self, key, default=None, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
@@ -140,3 +140,7 @@ class CacheClass(BaseDatabaseCacheClass):
table = connections[db].ops.quote_name(self._table)
cursor = connections[db].cursor()
cursor.execute('DELETE FROM %s' % table)
+
+# For backwards compatibility
+class CacheClass(DatabaseCache):
+ pass
diff --git a/django/core/cache/backends/dummy.py b/django/core/cache/backends/dummy.py
index 7d90ddace4..af8b62c8dd 100644
--- a/django/core/cache/backends/dummy.py
+++ b/django/core/cache/backends/dummy.py
@@ -2,7 +2,7 @@
from django.core.cache.backends.base import BaseCache
-class CacheClass(BaseCache):
+class DummyCache(BaseCache):
def __init__(self, host, *args, **kwargs):
BaseCache.__init__(self, *args, **kwargs)
@@ -40,3 +40,7 @@ class CacheClass(BaseCache):
def clear(self):
pass
+
+# For backwards compatibility
+class CacheClass(DummyCache):
+ pass
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index 1f95faf5ee..b75d636a7a 100644
--- a/django/core/cache/backends/filebased.py
+++ b/django/core/cache/backends/filebased.py
@@ -11,9 +11,9 @@ except ImportError:
from django.core.cache.backends.base import BaseCache
from django.utils.hashcompat import md5_constructor
-class CacheClass(BaseCache):
- def __init__(self, dir, params, key_prefix='', version=1, key_func=None):
- BaseCache.__init__(self, params, key_prefix, version, key_func)
+class FileBasedCache(BaseCache):
+ def __init__(self, dir, params):
+ BaseCache.__init__(self, params)
self._dir = dir
if not os.path.exists(self._dir):
self._createdir()
@@ -161,3 +161,7 @@ class CacheClass(BaseCache):
shutil.rmtree(self._dir)
except (IOError, OSError):
pass
+
+# For backwards compatibility
+class CacheClass(FileBasedCache):
+ pass
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
index 17fd8f33ce..ecec8750ba 100644
--- a/django/core/cache/backends/locmem.py
+++ b/django/core/cache/backends/locmem.py
@@ -9,12 +9,19 @@ except ImportError:
from django.core.cache.backends.base import BaseCache
from django.utils.synch import RWLock
-class CacheClass(BaseCache):
- def __init__(self, _, params, key_prefix='', version=1, key_func=None):
- BaseCache.__init__(self, params, key_prefix, version, key_func)
- self._cache = {}
- self._expire_info = {}
- self._lock = RWLock()
+# Global in-memory store of cache data. Keyed by name, to provide
+# multiple named local memory caches.
+_caches = {}
+_expire_info = {}
+_locks = {}
+
+class LocMemCache(BaseCache):
+ def __init__(self, name, params):
+ BaseCache.__init__(self, params)
+ global _caches, _expire_info, _locks
+ self._cache = _caches.setdefault(name, {})
+ self._expire_info = _expire_info.setdefault(name, {})
+ self._lock = _locks.setdefault(name, RWLock())
def add(self, key, value, timeout=None, version=None):
key = self.make_key(key, version=version)
@@ -133,3 +140,7 @@ class CacheClass(BaseCache):
def clear(self):
self._cache.clear()
self._expire_info.clear()
+
+# For backwards compatibility
+class CacheClass(LocMemCache):
+ pass
diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py
index 4bd8547085..97d4317dec 100644
--- a/django/core/cache/backends/memcached.py
+++ b/django/core/cache/backends/memcached.py
@@ -1,26 +1,34 @@
"Memcached cache backend"
import time
+from threading import local
from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
+from django.utils import importlib
-try:
- import cmemcache as memcache
- import warnings
- warnings.warn(
- "Support for the 'cmemcache' library has been deprecated. Please use python-memcached instead.",
- DeprecationWarning
- )
-except ImportError:
- try:
- import memcache
- except:
- raise InvalidCacheBackendError("Memcached cache backend requires either the 'memcache' or 'cmemcache' library")
+class BaseMemcachedCache(BaseCache):
+ def __init__(self, server, params, library, value_not_found_exception):
+ super(BaseMemcachedCache, self).__init__(params)
+ if isinstance(server, basestring):
+ self._servers = server.split(';')
+ else:
+ self._servers = server
+
+ # The exception type to catch from the underlying library for a key
+ # that was not found. This is a ValueError for python-memcache,
+ # pylibmc.NotFound for pylibmc, and cmemcache will return None without
+ # raising an exception.
+ self.LibraryValueNotFoundException = value_not_found_exception
-class CacheClass(BaseCache):
- def __init__(self, server, params, key_prefix='', version=1, key_func=None):
- BaseCache.__init__(self, params, key_prefix, version, key_func)
- self._cache = memcache.Client(server.split(';'))
+ self._lib = library
+ self._options = params.get('OPTIONS', None)
+
+ @property
+ def _cache(self):
+ """
+ Implements transparent thread-safe access to a memcached client.
+ """
+ return self._lib.Client(self._servers)
def _get_memcache_timeout(self, timeout):
"""
@@ -79,13 +87,13 @@ class CacheClass(BaseCache):
val = self._cache.incr(key, delta)
# python-memcache responds to incr on non-existent keys by
- # raising a ValueError. Cmemcache returns None. In both
- # cases, we should raise a ValueError though.
- except ValueError:
+ # raising a ValueError, pylibmc by raising a pylibmc.NotFound
+ # and Cmemcache returns None. In all cases,
+ # we should raise a ValueError though.
+ except self.LibraryValueNotFoundException:
val = None
if val is None:
raise ValueError("Key '%s' not found" % key)
-
return val
def decr(self, key, delta=1, version=None):
@@ -93,10 +101,11 @@ class CacheClass(BaseCache):
try:
val = self._cache.decr(key, delta)
- # python-memcache responds to decr on non-existent keys by
- # raising a ValueError. Cmemcache returns None. In both
- # cases, we should raise a ValueError though.
- except ValueError:
+ # python-memcache responds to incr on non-existent keys by
+ # raising a ValueError, pylibmc by raising a pylibmc.NotFound
+ # and Cmemcache returns None. In all cases,
+ # we should raise a ValueError though.
+ except self.LibraryValueNotFoundException:
val = None
if val is None:
raise ValueError("Key '%s' not found" % key)
@@ -117,3 +126,59 @@ class CacheClass(BaseCache):
def clear(self):
self._cache.flush_all()
+
+# For backwards compatibility -- the default cache class tries a
+# cascading lookup of cmemcache, then memcache.
+class CacheClass(BaseMemcachedCache):
+ def __init__(self, server, params):
+ try:
+ import cmemcache as memcache
+ import warnings
+ warnings.warn(
+ "Support for the 'cmemcache' library has been deprecated. Please use python-memcached or pyblimc instead.",
+ DeprecationWarning
+ )
+ except ImportError:
+ try:
+ import memcache
+ except:
+ raise InvalidCacheBackendError(
+ "Memcached cache backend requires either the 'memcache' or 'cmemcache' library"
+ )
+ super(CacheClass, self).__init__(server, params,
+ library=memcache,
+ value_not_found_exception=ValueError)
+
+class MemcachedCache(BaseMemcachedCache):
+ "An implementation of a cache binding using python-memcached"
+ def __init__(self, server, params):
+ import memcache
+ super(MemcachedCache, self).__init__(server, params,
+ library=memcache,
+ value_not_found_exception=ValueError)
+
+class PyLibMCCache(BaseMemcachedCache):
+ "An implementation of a cache binding using pylibmc"
+ def __init__(self, server, params):
+ import pylibmc
+ self._local = local()
+ super(PyLibMCCache, self).__init__(server, params,
+ library=pylibmc,
+ value_not_found_exception=pylibmc.NotFound)
+
+ @property
+ def _cache(self):
+ # PylibMC uses cache options as the 'behaviors' attribute.
+ # It also needs to use threadlocals, because some versions of
+ # PylibMC don't play well with the GIL.
+ client = getattr(self._local, 'client', None)
+ if client:
+ return client
+
+ client = self._lib.Client(self._servers)
+ if self._options:
+ client.behaviors = self._options
+
+ self._local.client = client
+
+ return client
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
index 3ba8922a31..db1fd6abb7 100644
--- a/django/db/backends/creation.py
+++ b/django/db/backends/creation.py
@@ -359,12 +359,14 @@ class BaseDatabaseCreation(object):
# (unless you really ask to be flooded)
call_command('syncdb', verbosity=max(verbosity - 1, 0), interactive=False, database=self.connection.alias)
- if settings.CACHE_BACKEND.startswith('db://'):
- from django.core.cache import parse_backend_uri, cache
- from django.db import router
- if router.allow_syncdb(self.connection.alias, cache.cache_model_class):
- _, cache_name, _ = parse_backend_uri(settings.CACHE_BACKEND)
- call_command('createcachetable', cache_name, database=self.connection.alias)
+ from django.core.cache import get_cache
+ from django.core.cache.backends.db import BaseDatabaseCache
+ for cache_alias in settings.CACHES:
+ cache = get_cache(cache_alias)
+ if isinstance(cache, BaseDatabaseCache):
+ from django.db import router
+ if router.allow_syncdb(self.connection.alias, cache.cache_model_class):
+ call_command('createcachetable', cache._table, database=self.connection.alias)
# Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database.
diff --git a/django/middleware/cache.py b/django/middleware/cache.py
index a3076acd22..54f6607db1 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
@@ -49,7 +49,7 @@ More details about how the caching works:
"""
from django.conf import settings
-from django.core.cache import cache
+from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age
class UpdateCacheMiddleware(object):
@@ -65,6 +65,7 @@ class UpdateCacheMiddleware(object):
self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False)
+ self.cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
def process_response(self, request, response):
"""Sets the cache, if needed."""
@@ -85,7 +86,7 @@ class UpdateCacheMiddleware(object):
patch_response_headers(response, timeout)
if timeout:
cache_key = learn_cache_key(request, response, timeout, self.key_prefix)
- cache.set(cache_key, response, timeout)
+ self.cache.set(cache_key, response, timeout)
return response
class FetchFromCacheMiddleware(object):
@@ -100,6 +101,7 @@ class FetchFromCacheMiddleware(object):
self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False)
+ self.cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
def process_request(self, request):
"""
@@ -124,12 +126,12 @@ class FetchFromCacheMiddleware(object):
request._cache_update_cache = True
return None # No cache information available, need to rebuild.
- response = cache.get(cache_key, None)
+ response = self.cache.get(cache_key, None)
# if it wasn't found and we are looking for a HEAD, try looking just for that
if response is None and request.method == 'HEAD':
cache_key = get_cache_key(request, self.key_prefix, 'HEAD')
- response = cache.get(cache_key, None)
+ response = self.cache.get(cache_key, None)
if response is None:
request._cache_update_cache = True
@@ -146,14 +148,33 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware):
Also used as the hook point for the cache decorator, which is generated
using the decorator-from-middleware utility.
"""
- def __init__(self, cache_timeout=None, key_prefix=None, cache_anonymous_only=None):
+ def __init__(self, cache_timeout=None, cache_anonymous_only=None, **kwargs):
self.cache_timeout = cache_timeout
if cache_timeout is None:
self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
- self.key_prefix = key_prefix
- if key_prefix is None:
- self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+
+ # We need to differentiate between "provided, but using default value",
+ # and "not provided". If the value is provided using a default, then
+ # we fall back to system defaults. If it is not provided at all,
+ # we need to use middleware defaults.
+ try:
+ cache_alias = kwargs.get('cache_alias')
+ if cache_alias is None:
+ cache_alias = DEFAULT_CACHE_ALIAS
+ except KeyError:
+ cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
+
+ cache_kwargs = {}
+ try:
+ key_prefix = kwargs.get('key_prefix')
+ if key_prefix is not None:
+ cache_kwargs['KEY_PREFIX'] = key_prefix
+ except KeyError:
+ cache_kwargs['KEY_PREFIX'] = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+
if cache_anonymous_only is None:
self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False)
else:
self.cache_anonymous_only = cache_anonymous_only
+
+ self.cache = get_cache(cache_alias, **cache_kwargs)
diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py
index 577c1ddab8..a836ac5c28 100644
--- a/django/views/decorators/cache.py
+++ b/django/views/decorators/cache.py
@@ -40,23 +40,24 @@ def cache_page(*args, **kwargs):
# We also add some asserts to give better error messages in case people are
# using other ways to call cache_page that no longer work.
+ cache_alias = kwargs.pop('cache', None)
key_prefix = kwargs.pop('key_prefix', None)
- assert not kwargs, "The only keyword argument accepted is key_prefix"
+ assert not kwargs, "The only keyword arguments are cache and key_prefix"
if len(args) > 1:
assert len(args) == 2, "cache_page accepts at most 2 arguments"
if callable(args[0]):
- return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[1], key_prefix=key_prefix)(args[0])
+ return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[1], cache_alias=cache_alias, key_prefix=key_prefix)(args[0])
elif callable(args[1]):
- return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix)(args[1])
+ return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], cache_alias=cache_alias, key_prefix=key_prefix)(args[1])
else:
assert False, "cache_page must be passed a view function if called with two arguments"
elif len(args) == 1:
if callable(args[0]):
- return decorator_from_middleware_with_args(CacheMiddleware)(key_prefix=key_prefix)(args[0])
+ return decorator_from_middleware_with_args(CacheMiddleware)(cache_alias=cache_alias, key_prefix=key_prefix)(args[0])
else:
- return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix)
+ return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], cache_alias=cache_alias, key_prefix=key_prefix)
else:
- return decorator_from_middleware_with_args(CacheMiddleware)(key_prefix=key_prefix)
+ return decorator_from_middleware_with_args(CacheMiddleware)(cache_alias=cache_alias, key_prefix=key_prefix)
def cache_control(**kwargs):