Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #13795 -- Added a site-wide cache prefix and cache versioning. …

…Thanks to bruth for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14623 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 99d247f4cb0c22d19a4482a72a7a93584a5189da 1 parent 261aee2
Russell Keith-Magee authored November 19, 2010
3  django/conf/global_settings.py
@@ -433,6 +433,9 @@
433 433
 # The cache backend to use.  See the docstring in django.core.cache for the
434 434
 # possible values.
435 435
 CACHE_BACKEND = 'locmem://'
  436
+CACHE_VERSION = 1
  437
+CACHE_KEY_PREFIX = ''
  438
+CACHE_KEY_FUNCTION = None
436 439
 CACHE_MIDDLEWARE_KEY_PREFIX = ''
437 440
 CACHE_MIDDLEWARE_SECONDS = 600
438 441
 
18  django/core/cache/__init__.py
@@ -67,18 +67,30 @@ def parse_backend_uri(backend_uri):
67 67
 
68 68
     return scheme, host, params
69 69
 
70  
-def get_cache(backend_uri):
  70
+def get_cache(backend_uri, key_prefix=None, version=None, key_func=None):
  71
+    if key_prefix is None:
  72
+        key_prefix = settings.CACHE_KEY_PREFIX
  73
+    if version is None:
  74
+        version = settings.CACHE_VERSION
  75
+    if key_func is None:
  76
+        key_func = settings.CACHE_KEY_FUNCTION
  77
+
  78
+    if key_func is not None and not callable(key_func):
  79
+        key_func_module_path, key_func_name = key_func.rsplit('.', 1)
  80
+        key_func_module = importlib.import_module(key_func_module_path)
  81
+        key_func = getattr(key_func_module, key_func_name)
  82
+
71 83
     scheme, host, params = parse_backend_uri(backend_uri)
72 84
     if scheme in BACKENDS:
73 85
         name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
74 86
     else:
75 87
         name = scheme
76 88
     module = importlib.import_module(name)
77  
-    return module.CacheClass(host, params)
  89
+    return module.CacheClass(host, params, key_prefix=key_prefix, version=version, key_func=key_func)
78 90
 
79 91
 cache = get_cache(settings.CACHE_BACKEND)
80 92
 
81  
-# Some caches -- pythont-memcached in particular -- need to do a cleanup at the
  93
+# Some caches -- python-memcached in particular -- need to do a cleanup at the
82 94
 # end of a request cycle. If the cache provides a close() method, wire it up
83 95
 # here.
84 96
 if hasattr(cache, 'close'):
87  django/core/cache/backends/base.py
@@ -3,6 +3,7 @@
3 3
 import warnings
4 4
 
5 5
 from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
  6
+from django.utils.encoding import smart_str
6 7
 
7 8
 class InvalidCacheBackendError(ImproperlyConfigured):
8 9
     pass
@@ -13,8 +14,17 @@ class CacheKeyWarning(DjangoRuntimeWarning):
13 14
 # Memcached does not accept keys longer than this.
14 15
 MEMCACHE_MAX_KEY_LENGTH = 250
15 16
 
  17
+def default_key_func(key, key_prefix, version):
  18
+    """Default function to generate keys.
  19
+
  20
+    Constructs the key used by all other methods. By default it prepends
  21
+    the `key_prefix'. CACHE_KEY_FUNCTION can be used to specify an alternate
  22
+    function with custom key making behavior.
  23
+    """
  24
+    return ':'.join([key_prefix, str(version), smart_str(key)])
  25
+
16 26
 class BaseCache(object):
17  
-    def __init__(self, params):
  27
+    def __init__(self, params, key_prefix='', version=1, key_func=None):
18 28
         timeout = params.get('timeout', 300)
19 29
         try:
20 30
             timeout = int(timeout)
@@ -34,7 +44,25 @@ def __init__(self, params):
34 44
         except (ValueError, TypeError):
35 45
             self._cull_frequency = 3
36 46
 
37  
-    def add(self, key, value, timeout=None):
  47
+        self.key_prefix = smart_str(key_prefix)
  48
+        self.version = version
  49
+        self.key_func = key_func or default_key_func
  50
+
  51
+    def make_key(self, key, version=None):
  52
+        """Constructs the key used by all other methods. By default it
  53
+        uses the key_func to generate a key (which, by default,
  54
+        prepends the `key_prefix' and 'version'). An different key
  55
+        function can be provided at the time of cache construction;
  56
+        alternatively, you can subclass the cache backend to provide
  57
+        custom key making behavior.
  58
+        """
  59
+        if version is None:
  60
+            version = self.version
  61
+
  62
+        new_key = self.key_func(key, self.key_prefix, version)
  63
+        return new_key
  64
+
  65
+    def add(self, key, value, timeout=None, version=None):
38 66
         """
39 67
         Set a value in the cache if the key does not already exist. If
40 68
         timeout is given, that timeout will be used for the key; otherwise
@@ -44,27 +72,27 @@ def add(self, key, value, timeout=None):
44 72
         """
45 73
         raise NotImplementedError
46 74
 
47  
-    def get(self, key, default=None):
  75
+    def get(self, key, default=None, version=None):
48 76
         """
49 77
         Fetch a given key from the cache. If the key does not exist, return
50 78
         default, which itself defaults to None.
51 79
         """
52 80
         raise NotImplementedError
53 81
 
54  
-    def set(self, key, value, timeout=None):
  82
+    def set(self, key, value, timeout=None, version=None):
55 83
         """
56 84
         Set a value in the cache. If timeout is given, that timeout will be
57 85
         used for the key; otherwise the default cache timeout will be used.
58 86
         """
59 87
         raise NotImplementedError
60 88
 
61  
-    def delete(self, key):
  89
+    def delete(self, key, version=None):
62 90
         """
63 91
         Delete a key from the cache, failing silently.
64 92
         """
65 93
         raise NotImplementedError
66 94
 
67  
-    def get_many(self, keys):
  95
+    def get_many(self, keys, version=None):
68 96
         """
69 97
         Fetch a bunch of keys from the cache. For certain backends (memcached,
70 98
         pgsql) this can be *much* faster when fetching multiple values.
@@ -74,34 +102,35 @@ def get_many(self, keys):
74 102
         """
75 103
         d = {}
76 104
         for k in keys:
77  
-            val = self.get(k)
  105
+            val = self.get(k, version=version)
78 106
             if val is not None:
79 107
                 d[k] = val
80 108
         return d
81 109
 
82  
-    def has_key(self, key):
  110
+    def has_key(self, key, version=None):
83 111
         """
84 112
         Returns True if the key is in the cache and has not expired.
85 113
         """
86  
-        return self.get(key) is not None
  114
+        return self.get(key, version=version) is not None
87 115
 
88  
-    def incr(self, key, delta=1):
  116
+    def incr(self, key, delta=1, version=None):
89 117
         """
90 118
         Add delta to value in the cache. If the key does not exist, raise a
91 119
         ValueError exception.
92 120
         """
93  
-        if key not in self:
  121
+        value = self.get(key, version=version)
  122
+        if value is None:
94 123
             raise ValueError("Key '%s' not found" % key)
95  
-        new_value = self.get(key) + delta
96  
-        self.set(key, new_value)
  124
+        new_value = value + delta
  125
+        self.set(key, new_value, version=version)
97 126
         return new_value
98 127
 
99  
-    def decr(self, key, delta=1):
  128
+    def decr(self, key, delta=1, version=None):
100 129
         """
101 130
         Subtract delta from value in the cache. If the key does not exist, raise
102 131
         a ValueError exception.
103 132
         """
104  
-        return self.incr(key, -delta)
  133
+        return self.incr(key, -delta, version=version)
105 134
 
106 135
     def __contains__(self, key):
107 136
         """
@@ -112,7 +141,7 @@ def __contains__(self, key):
112 141
         # if a subclass overrides it.
113 142
         return self.has_key(key)
114 143
 
115  
-    def set_many(self, data, timeout=None):
  144
+    def set_many(self, data, timeout=None, version=None):
116 145
         """
117 146
         Set a bunch of values in the cache at once from a dict of key/value
118 147
         pairs.  For certain backends (memcached), this is much more efficient
@@ -122,16 +151,16 @@ def set_many(self, data, timeout=None):
122 151
         the default cache timeout will be used.
123 152
         """
124 153
         for key, value in data.items():
125  
-            self.set(key, value, timeout)
  154
+            self.set(key, value, timeout=timeout, version=version)
126 155
 
127  
-    def delete_many(self, keys):
  156
+    def delete_many(self, keys, version=None):
128 157
         """
129 158
         Set a bunch of values in the cache at once.  For certain backends
130 159
         (memcached), this is much more efficient than calling delete() multiple
131 160
         times.
132 161
         """
133 162
         for key in keys:
134  
-            self.delete(key)
  163
+            self.delete(key, version=version)
135 164
 
136 165
     def clear(self):
137 166
         """Remove *all* values from the cache at once."""
@@ -154,3 +183,23 @@ def validate_key(self, key):
154 183
                         'errors if used with memcached: %r' % key,
155 184
                               CacheKeyWarning)
156 185
 
  186
+    def incr_version(self, key, delta=1, version=None):
  187
+        """Adds delta to the cache version for the supplied key. Returns the
  188
+        new version.
  189
+        """
  190
+        if version is None:
  191
+            version = self.version
  192
+
  193
+        value = self.get(key, version=version)
  194
+        if value is None:
  195
+            raise ValueError("Key '%s' not found" % key)
  196
+
  197
+        self.set(key, value, version=version+delta)
  198
+        self.delete(key, version=version)
  199
+        return version+delta
  200
+
  201
+    def decr_version(self, key, delta=1, version=None):
  202
+        """Substracts delta from the cache version for the supplied key. Returns
  203
+        the new version.
  204
+        """
  205
+        return self.incr_version(key, -delta, version)
21  django/core/cache/backends/db.py
@@ -26,8 +26,8 @@ def __init__(self, table):
26 26
         self.proxy = False
27 27
 
28 28
 class BaseDatabaseCacheClass(BaseCache):
29  
-    def __init__(self, table, params):
30  
-        BaseCache.__init__(self, params)
  29
+    def __init__(self, table, params, key_prefix='', version=1, key_func=None):
  30
+        BaseCache.__init__(self, params, key_prefix, version, key_func)
31 31
         self._table = table
32 32
 
33 33
         class CacheEntry(object):
@@ -35,7 +35,8 @@ class CacheEntry(object):
35 35
         self.cache_model_class = CacheEntry
36 36
 
37 37
 class CacheClass(BaseDatabaseCacheClass):
38  
-    def get(self, key, default=None):
  38
+    def get(self, key, default=None, version=None):
  39
+        key = self.make_key(key, version=version)
39 40
         self.validate_key(key)
40 41
         db = router.db_for_read(self.cache_model_class)
41 42
         table = connections[db].ops.quote_name(self._table)
@@ -55,11 +56,13 @@ def get(self, key, default=None):
55 56
         value = connections[db].ops.process_clob(row[1])
56 57
         return pickle.loads(base64.decodestring(value))
57 58
 
58  
-    def set(self, key, value, timeout=None):
  59
+    def set(self, key, value, timeout=None, version=None):
  60
+        key = self.make_key(key, version=version)
59 61
         self.validate_key(key)
60 62
         self._base_set('set', key, value, timeout)
61 63
 
62  
-    def add(self, key, value, timeout=None):
  64
+    def add(self, key, value, timeout=None, version=None):
  65
+        key = self.make_key(key, version=version)
63 66
         self.validate_key(key)
64 67
         return self._base_set('add', key, value, timeout)
65 68
 
@@ -95,8 +98,10 @@ def _base_set(self, mode, key, value, timeout=None):
95 98
             transaction.commit_unless_managed(using=db)
96 99
             return True
97 100
 
98  
-    def delete(self, key):
  101
+    def delete(self, key, version=None):
  102
+        key = self.make_key(key, version=version)
99 103
         self.validate_key(key)
  104
+
100 105
         db = router.db_for_write(self.cache_model_class)
101 106
         table = connections[db].ops.quote_name(self._table)
102 107
         cursor = connections[db].cursor()
@@ -104,8 +109,10 @@ def delete(self, key):
104 109
         cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key])
105 110
         transaction.commit_unless_managed(using=db)
106 111
 
107  
-    def has_key(self, key):
  112
+    def has_key(self, key, version=None):
  113
+        key = self.make_key(key, version=version)
108 114
         self.validate_key(key)
  115
+
109 116
         db = router.db_for_read(self.cache_model_class)
110 117
         table = connections[db].ops.quote_name(self._table)
111 118
         cursor = connections[db].cursor()
25  django/core/cache/backends/dummy.py
@@ -3,34 +3,39 @@
3 3
 from django.core.cache.backends.base import BaseCache
4 4
 
5 5
 class CacheClass(BaseCache):
6  
-    def __init__(self, *args, **kwargs):
7  
-        pass
  6
+    def __init__(self, host, *args, **kwargs):
  7
+        BaseCache.__init__(self, *args, **kwargs)
8 8
 
9  
-    def add(self, key, *args, **kwargs):
  9
+    def add(self, key, value, timeout=None, version=None):
  10
+        key = self.make_key(key, version=version)
10 11
         self.validate_key(key)
11 12
         return True
12 13
 
13  
-    def get(self, key, default=None):
  14
+    def get(self, key, default=None, version=None):
  15
+        key = self.make_key(key, version=version)
14 16
         self.validate_key(key)
15 17
         return default
16 18
 
17  
-    def set(self, key, *args, **kwargs):
  19
+    def set(self, key, value, timeout=None, version=None):
  20
+        key = self.make_key(key, version=version)
18 21
         self.validate_key(key)
19 22
 
20  
-    def delete(self, key, *args, **kwargs):
  23
+    def delete(self, key, version=None):
  24
+        key = self.make_key(key, version=version)
21 25
         self.validate_key(key)
22 26
 
23  
-    def get_many(self, *args, **kwargs):
  27
+    def get_many(self, keys, version=None):
24 28
         return {}
25 29
 
26  
-    def has_key(self, key, *args, **kwargs):
  30
+    def has_key(self, key, version=None):
  31
+        key = self.make_key(key, version=version)
27 32
         self.validate_key(key)
28 33
         return False
29 34
 
30  
-    def set_many(self, *args, **kwargs):
  35
+    def set_many(self, data, version=None):
31 36
         pass
32 37
 
33  
-    def delete_many(self, *args, **kwargs):
  38
+    def delete_many(self, keys, version=None):
34 39
         pass
35 40
 
36 41
     def clear(self):
27  django/core/cache/backends/filebased.py
@@ -12,22 +12,23 @@
12 12
 from django.utils.hashcompat import md5_constructor
13 13
 
14 14
 class CacheClass(BaseCache):
15  
-    def __init__(self, dir, params):
16  
-        BaseCache.__init__(self, params)
  15
+    def __init__(self, dir, params, key_prefix='', version=1, key_func=None):
  16
+        BaseCache.__init__(self, params, key_prefix, version, key_func)
17 17
         self._dir = dir
18 18
         if not os.path.exists(self._dir):
19 19
             self._createdir()
20 20
 
21  
-    def add(self, key, value, timeout=None):
22  
-        self.validate_key(key)
23  
-        if self.has_key(key):
  21
+    def add(self, key, value, timeout=None, version=None):
  22
+        if self.has_key(key, version=version):
24 23
             return False
25 24
 
26  
-        self.set(key, value, timeout)
  25
+        self.set(key, value, timeout, version=version)
27 26
         return True
28 27
 
29  
-    def get(self, key, default=None):
  28
+    def get(self, key, default=None, version=None):
  29
+        key = self.make_key(key, version=version)
30 30
         self.validate_key(key)
  31
+
31 32
         fname = self._key_to_file(key)
32 33
         try:
33 34
             f = open(fname, 'rb')
@@ -44,8 +45,10 @@ def get(self, key, default=None):
44 45
             pass
45 46
         return default
46 47
 
47  
-    def set(self, key, value, timeout=None):
  48
+    def set(self, key, value, timeout=None, version=None):
  49
+        key = self.make_key(key, version=version)
48 50
         self.validate_key(key)
  51
+
49 52
         fname = self._key_to_file(key)
50 53
         dirname = os.path.dirname(fname)
51 54
 
@@ -68,7 +71,8 @@ def set(self, key, value, timeout=None):
68 71
         except (IOError, OSError):
69 72
             pass
70 73
 
71  
-    def delete(self, key):
  74
+    def delete(self, key, version=None):
  75
+        key = self.make_key(key, version=version)
72 76
         self.validate_key(key)
73 77
         try:
74 78
             self._delete(self._key_to_file(key))
@@ -85,7 +89,8 @@ def _delete(self, fname):
85 89
         except (IOError, OSError):
86 90
             pass
87 91
 
88  
-    def has_key(self, key):
  92
+    def has_key(self, key, version=None):
  93
+        key = self.make_key(key, version=version)
89 94
         self.validate_key(key)
90 95
         fname = self._key_to_file(key)
91 96
         try:
@@ -140,7 +145,7 @@ def _key_to_file(self, key):
140 145
         Thus, a cache key of "foo" gets turnned into a file named
141 146
         ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
142 147
         """
143  
-        path = md5_constructor(key.encode('utf-8')).hexdigest()
  148
+        path = md5_constructor(key).hexdigest()
144 149
         path = os.path.join(path[:2], path[2:4], path[4:])
145 150
         return os.path.join(self._dir, path)
146 151
 
19  django/core/cache/backends/locmem.py
@@ -10,13 +10,14 @@
10 10
 from django.utils.synch import RWLock
11 11
 
12 12
 class CacheClass(BaseCache):
13  
-    def __init__(self, _, params):
14  
-        BaseCache.__init__(self, params)
  13
+    def __init__(self, _, params, key_prefix='', version=1, key_func=None):
  14
+        BaseCache.__init__(self, params, key_prefix, version, key_func)
15 15
         self._cache = {}
16 16
         self._expire_info = {}
17 17
         self._lock = RWLock()
18 18
 
19  
-    def add(self, key, value, timeout=None):
  19
+    def add(self, key, value, timeout=None, version=None):
  20
+        key = self.make_key(key, version=version)
20 21
         self.validate_key(key)
21 22
         self._lock.writer_enters()
22 23
         try:
@@ -31,7 +32,8 @@ def add(self, key, value, timeout=None):
31 32
         finally:
32 33
             self._lock.writer_leaves()
33 34
 
34  
-    def get(self, key, default=None):
  35
+    def get(self, key, default=None, version=None):
  36
+        key = self.make_key(key, version=version)
35 37
         self.validate_key(key)
36 38
         self._lock.reader_enters()
37 39
         try:
@@ -64,7 +66,8 @@ def _set(self, key, value, timeout=None):
64 66
         self._cache[key] = value
65 67
         self._expire_info[key] = time.time() + timeout
66 68
 
67  
-    def set(self, key, value, timeout=None):
  69
+    def set(self, key, value, timeout=None, version=None):
  70
+        key = self.make_key(key, version=version)
68 71
         self.validate_key(key)
69 72
         self._lock.writer_enters()
70 73
         # Python 2.4 doesn't allow combined try-except-finally blocks.
@@ -76,7 +79,8 @@ def set(self, key, value, timeout=None):
76 79
         finally:
77 80
             self._lock.writer_leaves()
78 81
 
79  
-    def has_key(self, key):
  82
+    def has_key(self, key, version=None):
  83
+        key = self.make_key(key, version=version)
80 84
         self.validate_key(key)
81 85
         self._lock.reader_enters()
82 86
         try:
@@ -117,7 +121,8 @@ def _delete(self, key):
117 121
         except KeyError:
118 122
             pass
119 123
 
120  
-    def delete(self, key):
  124
+    def delete(self, key, version=None):
  125
+        key = self.make_key(key, version=version)
121 126
         self.validate_key(key)
122 127
         self._lock.writer_enters()
123 128
         try:
57  django/core/cache/backends/memcached.py
@@ -3,7 +3,6 @@
3 3
 import time
4 4
 
5 5
 from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
6  
-from django.utils.encoding import smart_unicode, smart_str
7 6
 
8 7
 try:
9 8
     import cmemcache as memcache
@@ -19,8 +18,8 @@
19 18
         raise InvalidCacheBackendError("Memcached cache backend requires either the 'memcache' or 'cmemcache' library")
20 19
 
21 20
 class CacheClass(BaseCache):
22  
-    def __init__(self, server, params):
23  
-        BaseCache.__init__(self, params)
  21
+    def __init__(self, server, params, key_prefix='', version=1, key_func=None):
  22
+        BaseCache.__init__(self, params, key_prefix, version, key_func)
24 23
         self._cache = memcache.Client(server.split(';'))
25 24
 
26 25
     def _get_memcache_timeout(self, timeout):
@@ -39,30 +38,43 @@ def _get_memcache_timeout(self, timeout):
39 38
             timeout += int(time.time())
40 39
         return timeout
41 40
 
42  
-    def add(self, key, value, timeout=0):
  41
+    def add(self, key, value, timeout=0, version=None):
  42
+        key = self.make_key(key, version=version)
43 43
         if isinstance(value, unicode):
44 44
             value = value.encode('utf-8')
45  
-        return self._cache.add(smart_str(key), value, self._get_memcache_timeout(timeout))
  45
+        return self._cache.add(key, value, self._get_memcache_timeout(timeout))
46 46
 
47  
-    def get(self, key, default=None):
48  
-        val = self._cache.get(smart_str(key))
  47
+    def get(self, key, default=None, version=None):
  48
+        key = self.make_key(key, version=version)
  49
+        val = self._cache.get(key)
49 50
         if val is None:
50 51
             return default
51 52
         return val
52 53
 
53  
-    def set(self, key, value, timeout=0):
54  
-        self._cache.set(smart_str(key), value, self._get_memcache_timeout(timeout))
55  
-
56  
-    def delete(self, key):
57  
-        self._cache.delete(smart_str(key))
58  
-
59  
-    def get_many(self, keys):
60  
-        return self._cache.get_multi(map(smart_str,keys))
  54
+    def set(self, key, value, timeout=0, version=None):
  55
+        key = self.make_key(key, version=version)
  56
+        self._cache.set(key, value, self._get_memcache_timeout(timeout))
  57
+
  58
+    def delete(self, key, version=None):
  59
+        key = self.make_key(key, version=version)
  60
+        self._cache.delete(key)
  61
+
  62
+    def get_many(self, keys, version=None):
  63
+        new_keys = map(lambda x: self.make_key(x, version=version), keys)
  64
+        ret = self._cache.get_multi(new_keys)
  65
+        if ret:
  66
+            _ = {}
  67
+            m = dict(zip(new_keys, keys))
  68
+            for k, v in ret.items():
  69
+                _[m[k]] = v
  70
+            ret = _
  71
+        return ret
61 72
 
62 73
     def close(self, **kwargs):
63 74
         self._cache.disconnect_all()
64 75
 
65  
-    def incr(self, key, delta=1):
  76
+    def incr(self, key, delta=1, version=None):
  77
+        key = self.make_key(key, version=version)
66 78
         try:
67 79
             val = self._cache.incr(key, delta)
68 80
 
@@ -76,7 +88,8 @@ def incr(self, key, delta=1):
76 88
 
77 89
         return val
78 90
 
79  
-    def decr(self, key, delta=1):
  91
+    def decr(self, key, delta=1, version=None):
  92
+        key = self.make_key(key, version=version)
80 93
         try:
81 94
             val = self._cache.decr(key, delta)
82 95
 
@@ -89,16 +102,18 @@ def decr(self, key, delta=1):
89 102
             raise ValueError("Key '%s' not found" % key)
90 103
         return val
91 104
 
92  
-    def set_many(self, data, timeout=0):
  105
+    def set_many(self, data, timeout=0, version=None):
93 106
         safe_data = {}
94 107
         for key, value in data.items():
  108
+            key = self.make_key(key, version=version)
95 109
             if isinstance(value, unicode):
96 110
                 value = value.encode('utf-8')
97  
-            safe_data[smart_str(key)] = value
  111
+            safe_data[key] = value
98 112
         self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout))
99 113
 
100  
-    def delete_many(self, keys):
101  
-        self._cache.delete_multi(map(smart_str, keys))
  114
+    def delete_many(self, keys, version=None):
  115
+        l = lambda x: self.make_key(x, version=version)
  116
+        self._cache.delete_multi(map(l, keys))
102 117
 
103 118
     def clear(self):
104 119
         self._cache.flush_all()
43  docs/ref/settings.txt
@@ -136,6 +136,25 @@ Default: ``'locmem://'``
136 136
 
137 137
 The cache backend to use. See :doc:`/topics/cache`.
138 138
 
  139
+.. setting:: CACHE_KEY_FUNCTION
  140
+
  141
+CACHE_KEY_FUNCTION
  142
+------------------
  143
+
  144
+Default: ``None``
  145
+
  146
+A string containing a dotted path to a function that defines how to
  147
+compose a prefix, version and key into a final cache key. The default
  148
+implementation is equivalent to the function::
  149
+
  150
+    def make_key(key, key_prefix, version):
  151
+        return ':'.join([key_prefix, str(version), smart_str(key)])
  152
+
  153
+You may use any key function you want, as long as it has the same
  154
+argument signature.
  155
+
  156
+See the :ref:`cache documentation <cache_key_transformation>` for more information.
  157
+
139 158
 .. setting:: CACHE_MIDDLEWARE_ANONYMOUS_ONLY
140 159
 
141 160
 CACHE_MIDDLEWARE_ANONYMOUS_ONLY
@@ -172,6 +191,30 @@ Default: ``600``
172 191
 The default number of seconds to cache a page when the caching middleware or
173 192
 ``cache_page()`` decorator is used.
174 193
 
  194
+.. setting:: CACHE_PREFIX
  195
+
  196
+CACHE_PREFIX
  197
+------------
  198
+
  199
+Default: ``''`` (Empty string)
  200
+
  201
+A string that will be automatically included (prepended by default) to
  202
+all cache keys used by the Django server.
  203
+
  204
+See the :ref:`cache documentation <cache_key_prefixing>` for more information.
  205
+
  206
+.. setting:: CACHE_VERSION
  207
+
  208
+CACHE_VERSION
  209
+-------------
  210
+
  211
+Default: ``1``
  212
+
  213
+The default version number for cache keys generated by the Django server.
  214
+
  215
+See the :ref:`cache documentation <cache_versioning>` for more information.
  216
+
  217
+
175 218
 .. setting:: CSRF_COOKIE_DOMAIN
176 219
 
177 220
 CSRF_COOKIE_DOMAIN
3  docs/releases/1.3.txt
@@ -155,6 +155,9 @@ requests. These include:
155 155
       :meth:`~django.test.client.Client.assertNumQueries` -- making it
156 156
       easier to test the database activity associated with a view.
157 157
 
  158
+    * :ref:`Versioning <cache_versioning>`, :ref:`site-wide prefixing
  159
+      <cache_key_prefixing>` and :ref:`transformation
  160
+      <cache_key_transformation>` has been added to the cache API.
158 161
 
159 162
 .. _backwards-incompatible-changes-1.3:
160 163
 
95  docs/topics/cache.txt
@@ -643,6 +643,101 @@ nonexistent cache key.::
643 643
     However, if the backend doesn't natively provide an increment/decrement
644 644
     operation, it will be implemented using a two-step retrieve/update.
645 645
 
  646
+.. _cache_key_prefixing:
  647
+
  648
+Cache key prefixing
  649
+-------------------
  650
+
  651
+.. versionadded:: 1.3
  652
+
  653
+If you are sharing a cache instance between servers, or between your
  654
+production and development environments, it's possible for data cached
  655
+by one server to be used by another server. If the format of cached
  656
+data is different between servers, this can lead to some very hard to
  657
+diagnose problems.
  658
+
  659
+To prevent this, Django provides the ability to prefix all cache keys
  660
+used by a server. When a particular cache key is saved or retrieved,
  661
+Django will automatically prefix the cache key with the value of the
  662
+:setting:`CACHE_KEY_PREFIX` setting.
  663
+
  664
+By ensuring each Django instance has a different
  665
+:setting:`CACHE_KEY_PREFIX`, you can ensure that there will be no
  666
+collisions in cache values.
  667
+
  668
+.. _cache_versioning:
  669
+
  670
+Cache versioning
  671
+----------------
  672
+
  673
+.. versionadded:: 1.3
  674
+
  675
+When you change running code that uses cached values, you may need to
  676
+purge any existing cached values. The easiest way to do this is to
  677
+flush the entire cache, but this can lead to the loss of cache values
  678
+that are still valid and useful.
  679
+
  680
+Django provides a better way to target individual cache values.
  681
+Django's cache framework has a system-wide version identifier,
  682
+specified using the :setting:`CACHE_VERSION` setting. The value of
  683
+this setting is automatically combined with the cache prefix and the
  684
+user-provided cache key to obtain the final cache key.
  685
+
  686
+By default, any key request will automatically include the site
  687
+default cache key version. However, the primitive cache functions all
  688
+include a ``version`` argument, so you can specify a particular cache
  689
+key version to set or get. For example::
  690
+
  691
+    # Set version 2 of a cache key
  692
+    >>> cache.set('my_key', 'hello world!', version=2)
  693
+    # Get the default version (assuming version=1)
  694
+    >>> cache.get('my_key')
  695
+    None
  696
+    # Get version 2 of the same key
  697
+    >>> cache.get('my_key', version=2)
  698
+    'hello world!'
  699
+
  700
+The version of a specific key can be incremented and decremented using
  701
+the :func:`incr_version()` and :func:`decr_version()` methods. This
  702
+enables specific keys to be bumped to a new version, leaving other
  703
+keys unaffected. Continuing our previous example::
  704
+
  705
+    # Increment the version of 'my_key'
  706
+    >>> cache.incr_version('my_key')
  707
+    # The default version still isn't available
  708
+    >>> cache.get('my_key')
  709
+    None
  710
+    # Version 2 isn't available, either
  711
+    >>> cache.get('my_key', version=2)
  712
+    None
  713
+    # But version 3 *is* availble
  714
+    >>> cache.get('my_key', version=3)
  715
+    'hello world!'
  716
+
  717
+.. _cache_key_transformation:
  718
+
  719
+Cache key transformation
  720
+------------------------
  721
+
  722
+.. versionadded:: 1.3
  723
+
  724
+As described in the previous two sections, the cache key provided by a
  725
+user is not used verbatim -- it is combined with the cache prefix and
  726
+key version to provide a final cache key. By default, the three parts
  727
+are joined using colons to produce a final string::
  728
+
  729
+    def make_key(key, key_prefix, version):
  730
+        return ':'.join([key_prefix, str(version), smart_str(key)])
  731
+
  732
+If you want to combine the parts in different ways, or apply other
  733
+processing to the final key (e.g., taking a hash digest of the key
  734
+parts), you can provide a custom key function.
  735
+
  736
+The setting :setting:`CACHE_KEY_FUNCTION` specifies a dotted-path to
  737
+a function matching the prototype of :func:`make_key()` above. If
  738
+provided, this custom key function will be used instead of the default
  739
+key combining function.
  740
+
646 741
 Cache key warnings
647 742
 ------------------
648 743
 
385  tests/regressiontests/cache/tests.py
@@ -145,11 +145,21 @@ def test_clear(self):
145 145
         "clear does nothing for the dummy cache backend"
146 146
         self.cache.clear()
147 147
 
  148
+    def test_incr_version(self):
  149
+        "Dummy cache versions can't be incremented"
  150
+        self.cache.set('answer', 42)
  151
+        self.assertRaises(ValueError, self.cache.incr_version, 'answer')
  152
+        self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist')
  153
+
  154
+    def test_decr_version(self):
  155
+        "Dummy cache versions can't be decremented"
  156
+        self.cache.set('answer', 42)
  157
+        self.assertRaises(ValueError, self.cache.decr_version, 'answer')
  158
+        self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist')
  159
+
148 160
 
149 161
 class BaseCacheTests(object):
150 162
     # A common set of tests to apply to all cache backends
151  
-    def tearDown(self):
152  
-        self.cache.clear()
153 163
 
154 164
     def test_simple(self):
155 165
         # Simple cache set/get works
@@ -163,6 +173,18 @@ def test_add(self):
163 173
         self.assertEqual(result, False)
164 174
         self.assertEqual(self.cache.get("addkey1"), "value")
165 175
 
  176
+    def test_prefix(self):
  177
+        # Test for same cache key conflicts between shared backend
  178
+        self.cache.set('somekey', 'value')
  179
+
  180
+        # should not be set in the prefixed cache
  181
+        self.assertFalse(self.prefix_cache.has_key('somekey'))
  182
+
  183
+        self.prefix_cache.set('somekey', 'value2')
  184
+
  185
+        self.assertEqual(self.cache.get('somekey'), 'value')
  186
+        self.assertEqual(self.prefix_cache.get('somekey'), 'value2')
  187
+
166 188
     def test_non_existent(self):
167 189
         # Non-existent cache keys return as None/default
168 190
         # get with non-existent keys
@@ -376,6 +398,13 @@ def test_invalid_keys(self):
376 398
         with more liberal key rules. Refs #6447.
377 399
 
378 400
         """
  401
+        # mimic custom ``make_key`` method being defined since the default will
  402
+        # never show the below warnings
  403
+        def func(key, *args):
  404
+            return key
  405
+
  406
+        old_func = self.cache.key_func
  407
+        self.cache.key_func = func
379 408
         # On Python 2.6+ we could use the catch_warnings context
380 409
         # manager to test this warning nicely. Since we can't do that
381 410
         # yet, the cleanest option is to temporarily ask for
@@ -390,6 +419,285 @@ def test_invalid_keys(self):
390 419
             self.assertRaises(CacheKeyWarning, self.cache.set, 'a' * 251, 'value')
391 420
         finally:
392 421
             restore_warnings_state(_warnings_state)
  422
+            self.cache.key_func = old_func
  423
+
  424
+    def test_cache_versioning_get_set(self):
  425
+        # set, using default version = 1
  426
+        self.cache.set('answer1', 42)
  427
+        self.assertEqual(self.cache.get('answer1'), 42)
  428
+        self.assertEqual(self.cache.get('answer1', version=1), 42)
  429
+        self.assertEqual(self.cache.get('answer1', version=2), None)
  430
+
  431
+        self.assertEqual(self.v2_cache.get('answer1'), None)
  432
+        # print '---'
  433
+        # print 'c1',self.cache._cache
  434
+        # print 'v2',self.v2_cache._cache
  435
+        self.assertEqual(self.v2_cache.get('answer1', version=1), 42)
  436
+        self.assertEqual(self.v2_cache.get('answer1', version=2), None)
  437
+
  438
+        # set, default version = 1, but manually override version = 2
  439
+        self.cache.set('answer2', 42, version=2)
  440
+        self.assertEqual(self.cache.get('answer2'), None)
  441
+        self.assertEqual(self.cache.get('answer2', version=1), None)
  442
+        self.assertEqual(self.cache.get('answer2', version=2), 42)
  443
+
  444
+        self.assertEqual(self.v2_cache.get('answer2'), 42)
  445
+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
  446
+        self.assertEqual(self.v2_cache.get('answer2', version=2), 42)
  447
+
  448
+        # v2 set, using default version = 2
  449
+        self.v2_cache.set('answer3', 42)
  450
+        self.assertEqual(self.cache.get('answer3'), None)
  451
+        self.assertEqual(self.cache.get('answer3', version=1), None)
  452
+        self.assertEqual(self.cache.get('answer3', version=2), 42)
  453
+
  454
+        self.assertEqual(self.v2_cache.get('answer3'), 42)
  455
+        self.assertEqual(self.v2_cache.get('answer3', version=1), None)
  456
+        self.assertEqual(self.v2_cache.get('answer3', version=2), 42)
  457
+
  458
+        # v2 set, default version = 2, but manually override version = 1
  459
+        self.v2_cache.set('answer4', 42, version=1)
  460
+        self.assertEqual(self.cache.get('answer4'), 42)
  461
+        self.assertEqual(self.cache.get('answer4', version=1), 42)
  462
+        self.assertEqual(self.cache.get('answer4', version=2), None)
  463
+
  464
+        self.assertEqual(self.v2_cache.get('answer4'), None)
  465
+        self.assertEqual(self.v2_cache.get('answer4', version=1), 42)
  466
+        self.assertEqual(self.v2_cache.get('answer4', version=2), None)
  467
+
  468
+    def test_cache_versioning_add(self):
  469
+
  470
+        # add, default version = 1, but manually override version = 2
  471
+        self.cache.add('answer1', 42, version=2)
  472
+        self.assertEqual(self.cache.get('answer1', version=1), None)
  473
+        self.assertEqual(self.cache.get('answer1', version=2), 42)
  474
+
  475
+        self.cache.add('answer1', 37, version=2)
  476
+        self.assertEqual(self.cache.get('answer1', version=1), None)
  477
+        self.assertEqual(self.cache.get('answer1', version=2), 42)
  478
+
  479
+        self.cache.add('answer1', 37, version=1)
  480
+        self.assertEqual(self.cache.get('answer1', version=1), 37)
  481
+        self.assertEqual(self.cache.get('answer1', version=2), 42)
  482
+
  483
+        # v2 add, using default version = 2
  484
+        self.v2_cache.add('answer2', 42)
  485
+        self.assertEqual(self.cache.get('answer2', version=1), None)
  486
+        self.assertEqual(self.cache.get('answer2', version=2), 42)
  487
+
  488
+        self.v2_cache.add('answer2', 37)
  489
+        self.assertEqual(self.cache.get('answer2', version=1), None)
  490
+        self.assertEqual(self.cache.get('answer2', version=2), 42)
  491
+
  492
+        self.v2_cache.add('answer2', 37, version=1)
  493
+        self.assertEqual(self.cache.get('answer2', version=1), 37)
  494
+        self.assertEqual(self.cache.get('answer2', version=2), 42)
  495
+
  496
+        # v2 add, default version = 2, but manually override version = 1
  497
+        self.v2_cache.add('answer3', 42, version=1)
  498
+        self.assertEqual(self.cache.get('answer3', version=1), 42)
  499
+        self.assertEqual(self.cache.get('answer3', version=2), None)
  500
+
  501
+        self.v2_cache.add('answer3', 37, version=1)
  502
+        self.assertEqual(self.cache.get('answer3', version=1), 42)
  503
+        self.assertEqual(self.cache.get('answer3', version=2), None)
  504
+
  505
+        self.v2_cache.add('answer3', 37)
  506
+        self.assertEqual(self.cache.get('answer3', version=1), 42)
  507
+        self.assertEqual(self.cache.get('answer3', version=2), 37)
  508
+
  509
+    def test_cache_versioning_has_key(self):
  510
+        self.cache.set('answer1', 42)
  511
+
  512
+        # has_key
  513
+        self.assertTrue(self.cache.has_key('answer1'))
  514
+        self.assertTrue(self.cache.has_key('answer1', version=1))
  515
+        self.assertFalse(self.cache.has_key('answer1', version=2))
  516
+
  517
+        self.assertFalse(self.v2_cache.has_key('answer1'))
  518
+        self.assertTrue(self.v2_cache.has_key('answer1', version=1))
  519
+        self.assertFalse(self.v2_cache.has_key('answer1', version=2))
  520
+
  521
+    def test_cache_versioning_delete(self):
  522
+        self.cache.set('answer1', 37, version=1)
  523
+        self.cache.set('answer1', 42, version=2)
  524
+        self.cache.delete('answer1')
  525
+        self.assertEqual(self.cache.get('answer1', version=1), None)
  526
+        self.assertEqual(self.cache.get('answer1', version=2), 42)
  527
+
  528
+        self.cache.set('answer2', 37, version=1)
  529
+        self.cache.set('answer2', 42, version=2)
  530
+        self.cache.delete('answer2', version=2)
  531
+        self.assertEqual(self.cache.get('answer2', version=1), 37)
  532
+        self.assertEqual(self.cache.get('answer2', version=2), None)
  533
+
  534
+        self.cache.set('answer3', 37, version=1)
  535
+        self.cache.set('answer3', 42, version=2)
  536
+        self.v2_cache.delete('answer3')
  537
+        self.assertEqual(self.cache.get('answer3', version=1), 37)
  538
+        self.assertEqual(self.cache.get('answer3', version=2), None)
  539
+
  540
+        self.cache.set('answer4', 37, version=1)
  541
+        self.cache.set('answer4', 42, version=2)
  542
+        self.v2_cache.delete('answer4', version=1)
  543
+        self.assertEqual(self.cache.get('answer4', version=1), None)
  544
+        self.assertEqual(self.cache.get('answer4', version=2), 42)
  545
+
  546
+    def test_cache_versioning_incr_decr(self):
  547
+        self.cache.set('answer1', 37, version=1)
  548
+        self.cache.set('answer1', 42, version=2)
  549
+        self.cache.incr('answer1')
  550
+        self.assertEqual(self.cache.get('answer1', version=1), 38)
  551
+        self.assertEqual(self.cache.get('answer1', version=2), 42)
  552
+        self.cache.decr('answer1')
  553
+        self.assertEqual(self.cache.get('answer1', version=1), 37)
  554
+        self.assertEqual(self.cache.get('answer1', version=2), 42)
  555
+
  556
+        self.cache.set('answer2', 37, version=1)
  557
+        self.cache.set('answer2', 42, version=2)
  558
+        self.cache.incr('answer2', version=2)
  559
+        self.assertEqual(self.cache.get('answer2', version=1), 37)
  560
+        self.assertEqual(self.cache.get('answer2', version=2), 43)
  561
+        self.cache.decr('answer2', version=2)
  562
+        self.assertEqual(self.cache.get('answer2', version=1), 37)
  563
+        self.assertEqual(self.cache.get('answer2', version=2), 42)
  564
+
  565
+        self.cache.set('answer3', 37, version=1)
  566
+        self.cache.set('answer3', 42, version=2)
  567
+        self.v2_cache.incr('answer3')
  568
+        self.assertEqual(self.cache.get('answer3', version=1), 37)
  569
+        self.assertEqual(self.cache.get('answer3', version=2), 43)
  570
+        self.v2_cache.decr('answer3')
  571
+        self.assertEqual(self.cache.get('answer3', version=1), 37)
  572
+        self.assertEqual(self.cache.get('answer3', version=2), 42)
  573
+
  574
+        self.cache.set('answer4', 37, version=1)
  575
+        self.cache.set('answer4', 42, version=2)
  576
+        self.v2_cache.incr('answer4', version=1)
  577
+        self.assertEqual(self.cache.get('answer4', version=1), 38)
  578
+        self.assertEqual(self.cache.get('answer4', version=2), 42)
  579
+        self.v2_cache.decr('answer4', version=1)
  580
+        self.assertEqual(self.cache.get('answer4', version=1), 37)
  581
+        self.assertEqual(self.cache.get('answer4', version=2), 42)
  582
+
  583
+    def test_cache_versioning_get_set_many(self):
  584
+        # set, using default version = 1
  585
+        self.cache.set_many({'ford1': 37, 'arthur1': 42})
  586
+        self.assertEqual(self.cache.get_many(['ford1','arthur1']),
  587
+                         {'ford1': 37, 'arthur1': 42})
  588
+        self.assertEqual(self.cache.get_many(['ford1','arthur1'], version=1),
  589
+                         {'ford1': 37, 'arthur1': 42})
  590
+        self.assertEqual(self.cache.get_many(['ford1','arthur1'], version=2), {})
  591
+
  592
+        self.assertEqual(self.v2_cache.get_many(['ford1','arthur1']), {})
  593
+        self.assertEqual(self.v2_cache.get_many(['ford1','arthur1'], version=1),
  594
+                         {'ford1': 37, 'arthur1': 42})
  595
+        self.assertEqual(self.v2_cache.get_many(['ford1','arthur1'], version=2), {})
  596
+
  597
+        # set, default version = 1, but manually override version = 2
  598
+        self.cache.set_many({'ford2': 37, 'arthur2': 42}, version=2)
  599
+        self.assertEqual(self.cache.get_many(['ford2','arthur2']), {})
  600
+        self.assertEqual(self.cache.get_many(['ford2','arthur2'], version=1), {})
  601
+        self.assertEqual(self.cache.get_many(['ford2','arthur2'], version=2),
  602
+                         {'ford2': 37, 'arthur2': 42})
  603
+
  604
+        self.assertEqual(self.v2_cache.get_many(['ford2','arthur2']),
  605
+                         {'ford2': 37, 'arthur2': 42})
  606
+        self.assertEqual(self.v2_cache.get_many(['ford2','arthur2'], version=1), {})
  607
+        self.assertEqual(self.v2_cache.get_many(['ford2','arthur2'], version=2),
  608
+                         {'ford2': 37, 'arthur2': 42})
  609
+
  610
+        # v2 set, using default version = 2
  611
+        self.v2_cache.set_many({'ford3': 37, 'arthur3': 42})
  612
+        self.assertEqual(self.cache.get_many(['ford3','arthur3']), {})
  613
+        self.assertEqual(self.cache.get_many(['ford3','arthur3'], version=1), {})
  614
+        self.assertEqual(self.cache.get_many(['ford3','arthur3'], version=2),
  615
+                         {'ford3': 37, 'arthur3': 42})
  616
+
  617
+        self.assertEqual(self.v2_cache.get_many(['ford3','arthur3']),
  618
+                         {'ford3': 37, 'arthur3': 42})
  619
+        self.assertEqual(self.v2_cache.get_many(['ford3','arthur3'], version=1), {})
  620
+        self.assertEqual(self.v2_cache.get_many(['ford3','arthur3'], version=2),
  621
+                         {'ford3': 37, 'arthur3': 42})
  622
+
  623
+        # v2 set, default version = 2, but manually override version = 1
  624
+        self.v2_cache.set_many({'ford4': 37, 'arthur4': 42}, version=1)
  625
+        self.assertEqual(self.cache.get_many(['ford4','arthur4']),
  626
+                         {'ford4': 37, 'arthur4': 42})
  627
+        self.assertEqual(self.cache.get_many(['ford4','arthur4'], version=1),
  628
+                         {'ford4': 37, 'arthur4': 42})
  629
+        self.assertEqual(self.cache.get_many(['ford4','arthur4'], version=2), {})
  630
+
  631
+        self.assertEqual(self.v2_cache.get_many(['ford4','arthur4']), {})
  632
+        self.assertEqual(self.v2_cache.get_many(['ford4','arthur4'], version=1),
  633
+                         {'ford4': 37, 'arthur4': 42})
  634
+        self.assertEqual(self.v2_cache.get_many(['ford4','arthur4'], version=2), {})
  635
+
  636
+    def test_incr_version(self):
  637
+        self.cache.set('answer', 42, version=2)
  638
+        self.assertEqual(self.cache.get('answer'), None)
  639
+        self.assertEqual(self.cache.get('answer', version=1), None)
  640
+        self.assertEqual(self.cache.get('answer', version=2), 42)
  641
+        self.assertEqual(self.cache.get('answer', version=3), None)
  642
+
  643
+        self.assertEqual(self.cache.incr_version('answer', version=2), 3)
  644
+        self.assertEqual(self.cache.get('answer'), None)
  645
+        self.assertEqual(self.cache.get('answer', version=1), None)
  646
+        self.assertEqual(self.cache.get('answer', version=2), None)
  647
+        self.assertEqual(self.cache.get('answer', version=3), 42)
  648
+
  649
+        self.v2_cache.set('answer2', 42)
  650
+        self.assertEqual(self.v2_cache.get('answer2'), 42)
  651
+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
  652
+        self.assertEqual(self.v2_cache.get('answer2', version=2), 42)
  653
+        self.assertEqual(self.v2_cache.get('answer2', version=3), None)
  654
+
  655
+        self.assertEqual(self.v2_cache.incr_version('answer2'), 3)
  656
+        self.assertEqual(self.v2_cache.get('answer2'), None)
  657
+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
  658
+        self.assertEqual(self.v2_cache.get('answer2', version=2), None)
  659
+        self.assertEqual(self.v2_cache.get('answer2', version=3), 42)
  660
+
  661
+        self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist')
  662
+
  663
+    def test_decr_version(self):
  664
+        self.cache.set('answer', 42, version=2)
  665
+        self.assertEqual(self.cache.get('answer'), None)