Code

Ticket #13795: cache_key_prefix_14233.diff

File cache_key_prefix_14233.diff, 34.2 KB (added by bruth, 4 years ago)
Line 
1Index: django/conf/global_settings.py
2===================================================================
3--- django/conf/global_settings.py      (revision 14233)
4+++ django/conf/global_settings.py      (working copy)
5@@ -428,6 +428,9 @@
6 # The cache backend to use.  See the docstring in django.core.cache for the
7 # possible values.
8 CACHE_BACKEND = 'locmem://'
9+CACHE_VERSION = 1
10+CACHE_KEY_PREFIX = ''
11+CACHE_KEY_MODULE = None
12 CACHE_MIDDLEWARE_KEY_PREFIX = ''
13 CACHE_MIDDLEWARE_SECONDS = 600
14 
15Index: django/core/cache/__init__.py
16===================================================================
17--- django/core/cache/__init__.py       (revision 14233)
18+++ django/core/cache/__init__.py       (working copy)
19@@ -24,6 +24,8 @@
20 from django.core import signals
21 from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning
22 from django.utils import importlib
23+from django.utils.encoding import smart_str
24+from django.utils.hashcompat import md5_constructor
25 
26 # Name for use in settings file --> name of module in "backends" directory.
27 # Any backend scheme that is not in this dictionary is treated as a Python
28@@ -60,18 +62,28 @@
29 
30     return scheme, host, params
31 
32-def get_cache(backend_uri):
33+def get_cache(backend_uri, key_prefix=None, version=None):
34+    if key_prefix is None:
35+        key_prefix = settings.CACHE_KEY_PREFIX
36+    if version is None:
37+        version = settings.CACHE_VERSION
38+
39+    if settings.CACHE_KEY_MODULE is None:
40+        key_func = None
41+    else:
42+        key_func = importlib.import_module(settings.CACHE_KEY_MODULE).make_key
43+
44     scheme, host, params = parse_backend_uri(backend_uri)
45     if scheme in BACKENDS:
46         name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
47     else:
48         name = scheme
49     module = importlib.import_module(name)
50-    return module.CacheClass(host, params)
51+    return module.CacheClass(host, params, key_prefix, version, key_func)
52 
53 cache = get_cache(settings.CACHE_BACKEND)
54 
55-# Some caches -- pythont-memcached in particular -- need to do a cleanup at the
56+# Some caches -- python-memcached in particular -- need to do a cleanup at the
57 # end of a request cycle. If the cache provides a close() method, wire it up
58 # here.
59 if hasattr(cache, 'close'):
60Index: django/core/cache/backends/base.py
61===================================================================
62--- django/core/cache/backends/base.py  (revision 14233)
63+++ django/core/cache/backends/base.py  (working copy)
64@@ -2,7 +2,9 @@
65 
66 import warnings
67 
68+from django.utils.hashcompat import md5_constructor
69 from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
70+from django.utils.encoding import smart_str
71 
72 class InvalidCacheBackendError(ImproperlyConfigured):
73     pass
74@@ -13,17 +15,43 @@
75 # Memcached does not accept keys longer than this.
76 MEMCACHE_MAX_KEY_LENGTH = 250
77 
78+def make_key(key, key_prefix, version):
79+    """Default function to generate keys.
80+
81+    Constructs the key used by all other methods. By default it prepends
82+    the `key_prefix'. In cache backend subclasses this can be overriden to
83+    provide custom key making behavior.
84+    """
85+    s = key_prefix + smart_str(key) + str(version)
86+    hashed = md5_constructor(s).hexdigest()
87+    return hashed
88+
89 class BaseCache(object):
90-    def __init__(self, params):
91+    def __init__(self, params, key_prefix='', version=1, key_func=None):
92         timeout = params.get('timeout', 300)
93         try:
94             timeout = int(timeout)
95         except (ValueError, TypeError):
96             timeout = 300
97         self.default_timeout = timeout
98+        self.key_prefix = smart_str(key_prefix)
99+        self.version = version
100+        self.key_func = key_func or make_key
101 
102-    def add(self, key, value, timeout=None):
103+    def _make_key(self, key, version=None):
104+        """Constructs the key used by all other methods. By default it prepends
105+        the `key_prefix'. In cache backend subclasses this can be overriden to
106+        provide custom key making behavior.
107         """
108+        if version is None:
109+            version = self.version
110+
111+        new_key = self.key_func(key, self.key_prefix, version)
112+        self.validate_key(new_key)
113+        return new_key
114+
115+    def add(self, key, value, timeout=None, version=None):
116+        """
117         Set a value in the cache if the key does not already exist. If
118         timeout is given, that timeout will be used for the key; otherwise
119         the default cache timeout will be used.
120@@ -32,27 +60,27 @@
121         """
122         raise NotImplementedError
123 
124-    def get(self, key, default=None):
125+    def get(self, key, default=None, version=None):
126         """
127         Fetch a given key from the cache. If the key does not exist, return
128         default, which itself defaults to None.
129         """
130         raise NotImplementedError
131 
132-    def set(self, key, value, timeout=None):
133+    def set(self, key, value, timeout=None, version=None):
134         """
135         Set a value in the cache. If timeout is given, that timeout will be
136         used for the key; otherwise the default cache timeout will be used.
137         """
138         raise NotImplementedError
139 
140-    def delete(self, key):
141+    def delete(self, key, version=None):
142         """
143         Delete a key from the cache, failing silently.
144         """
145         raise NotImplementedError
146 
147-    def get_many(self, keys):
148+    def get_many(self, keys, version=None):
149         """
150         Fetch a bunch of keys from the cache. For certain backends (memcached,
151         pgsql) this can be *much* faster when fetching multiple values.
152@@ -62,34 +90,35 @@
153         """
154         d = {}
155         for k in keys:
156-            val = self.get(k)
157+            val = self.get(k, version=version)
158             if val is not None:
159                 d[k] = val
160         return d
161 
162-    def has_key(self, key):
163+    def has_key(self, key, version=None):
164         """
165         Returns True if the key is in the cache and has not expired.
166         """
167-        return self.get(key) is not None
168+        return self.get(key, version=version) is not None
169 
170-    def incr(self, key, delta=1):
171+    def incr(self, key, delta=1, version=None):
172         """
173         Add delta to value in the cache. If the key does not exist, raise a
174         ValueError exception.
175         """
176-        if key not in self:
177+        value = self.get(key, version=version)
178+        if value is None:
179             raise ValueError("Key '%s' not found" % key)
180-        new_value = self.get(key) + delta
181-        self.set(key, new_value)
182+        new_value = value + delta
183+        self.set(key, new_value, version=version)
184         return new_value
185 
186-    def decr(self, key, delta=1):
187+    def decr(self, key, delta=1, version=None):
188         """
189         Subtract delta from value in the cache. If the key does not exist, raise
190         a ValueError exception.
191         """
192-        return self.incr(key, -delta)
193+        return self.incr(key, -delta, version=version)
194 
195     def __contains__(self, key):
196         """
197@@ -100,7 +129,7 @@
198         # if a subclass overrides it.
199         return self.has_key(key)
200 
201-    def set_many(self, data, timeout=None):
202+    def set_many(self, data, timeout=None, version=None):
203         """
204         Set a bunch of values in the cache at once from a dict of key/value
205         pairs.  For certain backends (memcached), this is much more efficient
206@@ -110,16 +139,16 @@
207         the default cache timeout will be used.
208         """
209         for key, value in data.items():
210-            self.set(key, value, timeout)
211+            self.set(key, value, timeout=timeout, version=version)
212 
213-    def delete_many(self, keys):
214+    def delete_many(self, keys, version=None):
215         """
216         Set a bunch of values in the cache at once.  For certain backends
217         (memcached), this is much more efficient than calling delete() multiple
218         times.
219         """
220         for key in keys:
221-            self.delete(key)
222+            self.delete(key, version=version)
223 
224     def clear(self):
225         """Remove *all* values from the cache at once."""
226@@ -142,3 +171,24 @@
227                         'errors if used with memcached: %r' % key,
228                               CacheKeyWarning)
229 
230+    def incr_version(self, key, delta=1, version=None):
231+        """Adds delta to the cache version for the supplied key. Returns the
232+        new version.
233+        """
234+        if version is None:
235+            version = self.version
236+
237+        value = self.get(key, version=version)
238+        if value is None:
239+            raise ValueError("Key '%s' not found" % key)
240+
241+        self.set(key, value, version=version+delta)
242+        self.delete(key, version=version)
243+        return version+delta
244+
245+    def decr_version(self, key, delta=1, version=None):
246+        """Substracts delta from the cache version for the supplied key. Returns
247+        the new version.
248+        """
249+        return self.incr_version(key, -delta, version)
250+
251Index: django/core/cache/backends/dummy.py
252===================================================================
253--- django/core/cache/backends/dummy.py (revision 14233)
254+++ django/core/cache/backends/dummy.py (working copy)
255@@ -3,28 +3,28 @@
256 from django.core.cache.backends.base import BaseCache
257 
258 class CacheClass(BaseCache):
259-    def __init__(self, *args, **kwargs):
260-        pass
261+    def __init__(self, host, *args, **kwargs):
262+        BaseCache.__init__(self, *args, **kwargs)
263 
264     def add(self, key, *args, **kwargs):
265-        self.validate_key(key)
266+        self._make_key(key)
267         return True
268 
269-    def get(self, key, default=None):
270-        self.validate_key(key)
271+    def get(self, key, default=None, version=None):
272+        self._make_key(key)
273         return default
274 
275     def set(self, key, *args, **kwargs):
276-        self.validate_key(key)
277+        self._make_key(key)
278 
279     def delete(self, key, *args, **kwargs):
280-        self.validate_key(key)
281+        self._make_key(key)
282 
283     def get_many(self, *args, **kwargs):
284         return {}
285 
286     def has_key(self, key, *args, **kwargs):
287-        self.validate_key(key)
288+        self._make_key(key)
289         return False
290 
291     def set_many(self, *args, **kwargs):
292Index: django/core/cache/backends/locmem.py
293===================================================================
294--- django/core/cache/backends/locmem.py        (revision 14233)
295+++ django/core/cache/backends/locmem.py        (working copy)
296@@ -10,8 +10,8 @@
297 from django.utils.synch import RWLock
298 
299 class CacheClass(BaseCache):
300-    def __init__(self, _, params):
301-        BaseCache.__init__(self, params)
302+    def __init__(self, _, params, key_prefix='', version=1, key_func=None):
303+        BaseCache.__init__(self, params, key_prefix, version, key_func)
304         self._cache = {}
305         self._expire_info = {}
306 
307@@ -29,8 +29,8 @@
308 
309         self._lock = RWLock()
310 
311-    def add(self, key, value, timeout=None):
312-        self.validate_key(key)
313+    def add(self, key, value, timeout=None, version=None):
314+        key = self._make_key(key, version=version)
315         self._lock.writer_enters()
316         try:
317             exp = self._expire_info.get(key)
318@@ -44,8 +44,8 @@
319         finally:
320             self._lock.writer_leaves()
321 
322-    def get(self, key, default=None):
323-        self.validate_key(key)
324+    def get(self, key, default=None, version=None):
325+        key = self._make_key(key, version=version)
326         self._lock.reader_enters()
327         try:
328             exp = self._expire_info.get(key)
329@@ -77,8 +77,8 @@
330         self._cache[key] = value
331         self._expire_info[key] = time.time() + timeout
332 
333-    def set(self, key, value, timeout=None):
334-        self.validate_key(key)
335+    def set(self, key, value, timeout=None, version=None):
336+        key = self._make_key(key, version=version)
337         self._lock.writer_enters()
338         # Python 2.4 doesn't allow combined try-except-finally blocks.
339         try:
340@@ -89,8 +89,8 @@
341         finally:
342             self._lock.writer_leaves()
343 
344-    def has_key(self, key):
345-        self.validate_key(key)
346+    def has_key(self, key, version=None):
347+        key = self._make_key(key, version=version)
348         self._lock.reader_enters()
349         try:
350             exp = self._expire_info.get(key)
351@@ -130,8 +130,8 @@
352         except KeyError:
353             pass
354 
355-    def delete(self, key):
356-        self.validate_key(key)
357+    def delete(self, key, version=None):
358+        key = self._make_key(key, version=version)
359         self._lock.writer_enters()
360         try:
361             self._delete(key)
362Index: django/core/cache/backends/filebased.py
363===================================================================
364--- django/core/cache/backends/filebased.py     (revision 14233)
365+++ django/core/cache/backends/filebased.py     (working copy)
366@@ -12,8 +12,8 @@
367 from django.utils.hashcompat import md5_constructor
368 
369 class CacheClass(BaseCache):
370-    def __init__(self, dir, params):
371-        BaseCache.__init__(self, params)
372+    def __init__(self, dir, params, key_prefix='', version=1, key_func=None):
373+        BaseCache.__init__(self, params, key_prefix, version, key_func)
374 
375         max_entries = params.get('max_entries', 300)
376         try:
377@@ -31,16 +31,16 @@
378         if not os.path.exists(self._dir):
379             self._createdir()
380 
381-    def add(self, key, value, timeout=None):
382-        self.validate_key(key)
383-        if self.has_key(key):
384+    def add(self, key, value, timeout=None, version=None):
385+        if self.has_key(key, version=version):
386             return False
387 
388-        self.set(key, value, timeout)
389+        self.set(key, value, timeout, version=version)
390         return True
391 
392-    def get(self, key, default=None):
393-        self.validate_key(key)
394+    def get(self, key, default=None, version=None):
395+        key = self._make_key(key, version=version)
396+
397         fname = self._key_to_file(key)
398         try:
399             f = open(fname, 'rb')
400@@ -57,8 +57,9 @@
401             pass
402         return default
403 
404-    def set(self, key, value, timeout=None):
405-        self.validate_key(key)
406+    def set(self, key, value, timeout=None, version=None):
407+        key = self._make_key(key, version=version)
408+
409         fname = self._key_to_file(key)
410         dirname = os.path.dirname(fname)
411 
412@@ -81,8 +82,8 @@
413         except (IOError, OSError):
414             pass
415 
416-    def delete(self, key):
417-        self.validate_key(key)
418+    def delete(self, key, version=None):
419+        key = self._make_key(key, version=version)
420         try:
421             self._delete(self._key_to_file(key))
422         except (IOError, OSError):
423@@ -98,8 +99,8 @@
424         except (IOError, OSError):
425             pass
426 
427-    def has_key(self, key):
428-        self.validate_key(key)
429+    def has_key(self, key, version=None):
430+        key = self._make_key(key, version=version)
431         fname = self._key_to_file(key)
432         try:
433             f = open(fname, 'rb')
434@@ -153,7 +154,7 @@
435         Thus, a cache key of "foo" gets turnned into a file named
436         ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
437         """
438-        path = md5_constructor(key.encode('utf-8')).hexdigest()
439+        path = md5_constructor(key).hexdigest()
440         path = os.path.join(path[:2], path[2:4], path[4:])
441         return os.path.join(self._dir, path)
442 
443Index: django/core/cache/backends/db.py
444===================================================================
445--- django/core/cache/backends/db.py    (revision 14233)
446+++ django/core/cache/backends/db.py    (working copy)
447@@ -26,8 +26,8 @@
448         self.proxy = False
449 
450 class CacheClass(BaseCache):
451-    def __init__(self, table, params):
452-        BaseCache.__init__(self, params)
453+    def __init__(self, table, params, key_prefix='', version=1, key_func=None):
454+        BaseCache.__init__(self, params, key_prefix, version, key_func)
455         self._table = table
456 
457         class CacheEntry(object):
458@@ -45,8 +45,9 @@
459         except (ValueError, TypeError):
460             self._cull_frequency = 3
461 
462-    def get(self, key, default=None):
463-        self.validate_key(key)
464+    def get(self, key, default=None, version=None):
465+        key = self._make_key(key, version=version)
466+
467         db = router.db_for_read(self.cache_model_class)
468         table = connections[db].ops.quote_name(self._table)
469         cursor = connections[db].cursor()
470@@ -65,12 +66,12 @@
471         value = connections[db].ops.process_clob(row[1])
472         return pickle.loads(base64.decodestring(value))
473 
474-    def set(self, key, value, timeout=None):
475-        self.validate_key(key)
476+    def set(self, key, value, timeout=None, version=None):
477+        key = self._make_key(key, version=version)
478         self._base_set('set', key, value, timeout)
479 
480-    def add(self, key, value, timeout=None):
481-        self.validate_key(key)
482+    def add(self, key, value, timeout=None, version=None):
483+        key = self._make_key(key, version=version)
484         return self._base_set('add', key, value, timeout)
485 
486     def _base_set(self, mode, key, value, timeout=None):
487@@ -105,8 +106,9 @@
488             transaction.commit_unless_managed(using=db)
489             return True
490 
491-    def delete(self, key):
492-        self.validate_key(key)
493+    def delete(self, key, version=None):
494+        key = self._make_key(key, version=version)
495+
496         db = router.db_for_write(self.cache_model_class)
497         table = connections[db].ops.quote_name(self._table)
498         cursor = connections[db].cursor()
499@@ -114,8 +116,9 @@
500         cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key])
501         transaction.commit_unless_managed(using=db)
502 
503-    def has_key(self, key):
504-        self.validate_key(key)
505+    def has_key(self, key, version=None):
506+        key = self._make_key(key, version=version)
507+
508         db = router.db_for_read(self.cache_model_class)
509         table = connections[db].ops.quote_name(self._table)
510         cursor = connections[db].cursor()
511Index: django/core/cache/backends/memcached.py
512===================================================================
513--- django/core/cache/backends/memcached.py     (revision 14233)
514+++ django/core/cache/backends/memcached.py     (working copy)
515@@ -3,7 +3,6 @@
516 import time
517 
518 from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
519-from django.utils.encoding import smart_unicode, smart_str
520 
521 try:
522     import cmemcache as memcache
523@@ -19,8 +18,8 @@
524         raise InvalidCacheBackendError("Memcached cache backend requires either the 'memcache' or 'cmemcache' library")
525 
526 class CacheClass(BaseCache):
527-    def __init__(self, server, params):
528-        BaseCache.__init__(self, params)
529+    def __init__(self, server, params, key_prefix='', version=1, key_func=None):
530+        BaseCache.__init__(self, params, key_prefix, version, key_func)
531         self._cache = memcache.Client(server.split(';'))
532 
533     def _get_memcache_timeout(self, timeout):
534@@ -39,30 +38,43 @@
535             timeout += int(time.time())
536         return timeout
537 
538-    def add(self, key, value, timeout=0):
539+    def add(self, key, value, timeout=0, version=None):
540+        key = self._make_key(key, version=version)
541         if isinstance(value, unicode):
542             value = value.encode('utf-8')
543-        return self._cache.add(smart_str(key), value, self._get_memcache_timeout(timeout))
544+        return self._cache.add(key, value, self._get_memcache_timeout(timeout))
545 
546-    def get(self, key, default=None):
547-        val = self._cache.get(smart_str(key))
548+    def get(self, key, default=None, version=None):
549+        key = self._make_key(key, version=version)
550+        val = self._cache.get(key)
551         if val is None:
552             return default
553         return val
554 
555-    def set(self, key, value, timeout=0):
556-        self._cache.set(smart_str(key), value, self._get_memcache_timeout(timeout))
557+    def set(self, key, value, timeout=0, version=None):
558+        key = self._make_key(key, version=version)
559+        self._cache.set(key, value, self._get_memcache_timeout(timeout))
560 
561-    def delete(self, key):
562-        self._cache.delete(smart_str(key))
563+    def delete(self, key, version=None):
564+        key = self._make_key(key, version=version)
565+        self._cache.delete(key)
566 
567-    def get_many(self, keys):
568-        return self._cache.get_multi(map(smart_str,keys))
569+    def get_many(self, keys, version=None):
570+        new_keys = map(lambda x: self._make_key(x, version=version), keys)
571+        ret = self._cache.get_multi(new_keys)
572+        if ret:
573+            _ = {}
574+            m = dict(zip(new_keys, keys))
575+            for k, v in ret.items():
576+                _[m[k]] = v
577+            ret = _
578+        return ret
579 
580     def close(self, **kwargs):
581         self._cache.disconnect_all()
582 
583-    def incr(self, key, delta=1):
584+    def incr(self, key, delta=1, version=None):
585+        key = self._make_key(key, version=version)
586         try:
587             val = self._cache.incr(key, delta)
588 
589@@ -76,7 +88,8 @@
590 
591         return val
592 
593-    def decr(self, key, delta=1):
594+    def decr(self, key, delta=1, version=None):
595+        key = self._make_key(key, version=version)
596         try:
597             val = self._cache.decr(key, delta)
598 
599@@ -89,16 +102,18 @@
600             raise ValueError("Key '%s' not found" % key)
601         return val
602 
603-    def set_many(self, data, timeout=0):
604+    def set_many(self, data, timeout=0, version=None):
605         safe_data = {}
606         for key, value in data.items():
607+            key = self._make_key(key, version=version)
608             if isinstance(value, unicode):
609                 value = value.encode('utf-8')
610-            safe_data[smart_str(key)] = value
611+            safe_data[key] = value
612         self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout))
613 
614-    def delete_many(self, keys):
615-        self._cache.delete_multi(map(smart_str, keys))
616+    def delete_many(self, keys, version=None):
617+        l = lambda x: self._make_key(x, version=version)
618+        self._cache.delete_multi(map(l, keys))
619 
620     def clear(self):
621         self._cache.flush_all()
622Index: tests/regressiontests/cache/tests.py
623===================================================================
624--- tests/regressiontests/cache/tests.py        (revision 14233)
625+++ tests/regressiontests/cache/tests.py        (working copy)
626@@ -144,7 +144,19 @@
627         "clear does nothing for the dummy cache backend"
628         self.cache.clear()
629 
630+    def test_incr_version(self):
631+        "Dummy cache versions can't be incremented"
632+        self.cache.set('answer', 42)
633+        self.assertRaises(ValueError, self.cache.incr_version, 'answer')
634+        self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist')
635 
636+    def test_decr_version(self):
637+        "Dummy cache versions can't be decremented"
638+        self.cache.set('answer', 42)
639+        self.assertRaises(ValueError, self.cache.decr_version, 'answer')
640+        self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist')
641+
642+
643 class BaseCacheTests(object):
644     # A common set of tests to apply to all cache backends
645     def tearDown(self):
646@@ -162,6 +174,18 @@
647         self.assertEqual(result, False)
648         self.assertEqual(self.cache.get("addkey1"), "value")
649 
650+    def test_prefix(self):
651+        # Test for same cache key conflicts between shared backend
652+        self.cache.set('somekey', 'value')
653+
654+        # should not be set in the prefixed cache
655+        self.assertFalse(self.pfx_cache.has_key('somekey'))
656+
657+        self.pfx_cache.set('somekey', 'value2')
658+
659+        self.assertEqual(self.cache.get('somekey'), 'value')
660+        self.assertEqual(self.pfx_cache.get('somekey'), 'value2')
661+
662     def test_non_existent(self):
663         # Non-existent cache keys return as None/default
664         # get with non-existent keys
665@@ -375,6 +399,13 @@
666         with more liberal key rules. Refs #6447.
667 
668         """
669+        # mimic custom ``make_key`` method being defined since the default will
670+        # never show the below warnings
671+        def func(key, *args):
672+            return key
673+
674+        old_func = self.cache.key_func
675+        self.cache.key_func = func
676         # On Python 2.6+ we could use the catch_warnings context
677         # manager to test this warning nicely. Since we can't do that
678         # yet, the cleanest option is to temporarily ask for
679@@ -392,15 +423,31 @@
680         # it. The effect will be the same, as long as the Django test
681         # runner doesn't add any global warning filters (it currently
682         # does not).
683+        self.cache.key_func = old_func
684         warnings.resetwarnings()
685         warnings.simplefilter("ignore", PendingDeprecationWarning)
686 
687+    def test_incr_version(self):
688+        self.cache.set('answer', 42)
689+        self.assertEqual(self.cache.incr_version('answer'), 2)
690+        self.assertEqual(self.cache.get('answer', version=2), 42)
691+        self.assertEqual(self.cache.get('answer'), None)
692+        self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist')
693+
694+    def test_decr_version(self):
695+        self.cache.set('answer', 42, version=2)
696+        self.assertEqual(self.cache.decr_version('answer', version=2), 1)
697+        self.assertEqual(self.cache.get('answer', version=2), None)
698+        self.assertEqual(self.cache.get('answer'), 42)
699+        self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist', version=2)
700+
701 class DBCacheTests(unittest.TestCase, BaseCacheTests):
702     def setUp(self):
703         # Spaces are used in the table name to ensure quoting/escaping is working
704         self._table_name = 'test cache table'
705         management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False)
706         self.cache = get_cache('db://%s?max_entries=30' % self._table_name)
707+        self.pfx_cache = get_cache('db://%s' % self._table_name, 'cacheprefix')
708 
709     def tearDown(self):
710         from django.db import connection
711@@ -413,6 +460,7 @@
712 class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
713     def setUp(self):
714         self.cache = get_cache('locmem://?max_entries=30')
715+        self.pfx_cache = get_cache('locmem://', 'cacheprefix')
716 
717     def test_cull(self):
718         self.perform_cull_test(50, 29)
719@@ -425,6 +473,7 @@
720     class MemcachedCacheTests(unittest.TestCase, BaseCacheTests):
721         def setUp(self):
722             self.cache = get_cache(settings.CACHE_BACKEND)
723+            self.pfx_cache = get_cache(settings.CACHE_BACKEND, 'cacheprefix')
724 
725         def test_invalid_keys(self):
726             """
727@@ -436,11 +485,20 @@
728             that a generic exception of some kind is raised.
729 
730             """
731+            # mimic custom ``make_key`` method being defined since the default will
732+            # never show the below warnings
733+            def func(key, *args):
734+                return key
735+
736+            old_func = self.cache.key_func
737+            self.cache.key_func = func
738+
739             # memcached does not allow whitespace or control characters in keys
740             self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value')
741             # memcached limits key length to 250
742             self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value')
743 
744+            self.cache.key_func = old_func
745 
746 class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
747     """
748@@ -449,11 +507,13 @@
749     def setUp(self):
750         self.dirname = tempfile.mkdtemp()
751         self.cache = get_cache('file://%s?max_entries=30' % self.dirname)
752+        self.pfx_cache = get_cache('file://%s' % self.dirname, 'cacheprefix')
753 
754     def test_hashing(self):
755         """Test that keys are hashed into subdirectories correctly"""
756         self.cache.set("foo", "bar")
757-        keyhash = md5_constructor("foo").hexdigest()
758+        key = self.cache._make_key("foo")
759+        keyhash = md5_constructor(key).hexdigest()
760         keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
761         self.assert_(os.path.exists(keypath))
762 
763@@ -462,7 +522,8 @@
764         Make sure that the created subdirectories are correctly removed when empty.
765         """
766         self.cache.set("foo", "bar")
767-        keyhash = md5_constructor("foo").hexdigest()
768+        key = self.cache._make_key("foo")
769+        keyhash = md5_constructor(key).hexdigest()
770         keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
771         self.assert_(os.path.exists(keypath))
772 
773@@ -472,7 +533,7 @@
774         self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath))))
775 
776     def test_cull(self):
777-        self.perform_cull_test(50, 28)
778+        self.perform_cull_test(50, 29)
779 
780 class CustomCacheKeyValidationTests(unittest.TestCase):
781     """
782@@ -495,16 +556,16 @@
783 
784     def setUp(self):
785         self.path = '/cache/test/'
786-        self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
787-        self.old_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
788+        self.old_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
789+        self.old_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
790         self.orig_use_i18n = settings.USE_I18N
791         settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix'
792         settings.CACHE_MIDDLEWARE_SECONDS = 1
793         settings.USE_I18N = False
794 
795     def tearDown(self):
796-        settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_settings_key_prefix
797-        settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds
798+        settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_cache_middleware_key_prefix
799+        settings.CACHE_MIDDLEWARE_SECONDS = self.old_cache_middleware_seconds
800         settings.USE_I18N = self.orig_use_i18n
801 
802     def _get_request(self, path):
803@@ -557,6 +618,16 @@
804         learn_cache_key(request, response)
805         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
806 
807+class PrefixedCacheUtils(CacheUtils):
808+    def setUp(self):
809+        super(PrefixedCacheUtils, self).setUp()
810+        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
811+        settings.CACHE_KEY_PREFIX = 'cacheprefix'
812+
813+    def tearDown(self):
814+        super(PrefixedCacheUtils, self).tearDown()
815+        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
816+
817 class CacheI18nTest(unittest.TestCase):
818 
819     def setUp(self):
820@@ -652,5 +723,15 @@
821         get_cache_data = FetchFromCacheMiddleware().process_request(request)
822         self.assertEqual(get_cache_data.content, es_message)
823 
824+class PrefixedCacheI18nTest(CacheI18nTest):
825+    def setUp(self):
826+        super(PrefixedCacheI18nTest, self).setUp()
827+        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
828+        settings.CACHE_KEY_PREFIX = 'cacheprefix'
829+
830+    def tearDown(self):
831+        super(PrefixedCacheI18nTest, self).tearDown()
832+        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
833+
834 if __name__ == '__main__':
835     unittest.main()
836Index: docs/topics/cache.txt
837===================================================================
838--- docs/topics/cache.txt       (revision 14233)
839+++ docs/topics/cache.txt       (working copy)
840@@ -680,6 +680,98 @@
841 ...and use the dotted Python path to this module as the scheme portion of your
842 :setting:`CACHE_BACKEND`.
843 
844+Cache Prefix
845+------------
846+
847+.. versionadded:: 1.3
848+
849+It is a common occurence to have a shared cache instance running on your development
850+or production server for use across multiple projects, i.e. all sites are pointing
851+to the memcached instance running on port 11211. As a result, cache key conflicts
852+may arise which could result in data from one site being used by another.
853+
854+To alleviate this, CACHE_KEY_PREFIX can be set. This will be prepended to all keys
855+that are used with the cache backend, transparently::
856+
857+    # in settings, CACHE_KEY_PREFIX = 'myproject_'
858+
859+    >>> cache.set('my_key', 'hello world!') # set with key 'myproject_my_key'
860+    >>> cache.get('my_key') # retrieved with key 'myproject_my_key'
861+    'hello world!'
862+
863+Of course, for sites that _do_ share content, simply set the CACHE_KEY_PREFIX for
864+both sites to the same value. The default value for CACHE_KEY_PREFIX is the empty
865+string ``''``.
866+
867+.. note::
868+   
869+   This does *not* conflict with the CACHE_MIDDLEWARE_KEY_PREFIX and can be used
870+   in conjunction with it. CACHE_KEY_PREFIX acts as a global prefix for a
871+   particular cache instance, therefore it will be prepended to the
872+   CACHE_MIDDLEWARE_KEY_PREFIX transparently.
873+
874+
875+Cache Versioning
876+----------------
877+
878+.. versionadded:: 1.3
879+
880+Cache versioning can be a simple solution to dealing with invalid cache. The
881+setting ``CACHE_VERSION`` provides a means of defining which version of cache
882+your project will use. This is useful primarily for project upgrades or
883+downgrades. For example, if a project is in production and new features are
884+being added to it, the cache being used may not be useful any longer. The
885+``CACHE_VERSION`` can be incremented and all previous vesions of the cache
886+will be ignored.
887+
888+.. note::
889+
890+    This technique simulates "invalidating" your cache, but does not
891+    actually free up the space used by the other cache versions. Assuming
892+    a reasonable ``max_entries`` and `cull_frequency`` has been defined,
893+    this should not be a problem since the old cache will be culled as needed.
894+
895+For more complex cache versioning strategies, the cache version can specified
896+at runtime as such::
897+
898+    >>> cache.set('my_key', 'hello world!', version=2)
899+    >>> cache.get('my_key', version=2) # hello world!
900+    >>> cache.get('my_key', version=3) # None
901+
902+If the version is not supplied, it defaults to ``CACHE_VERSION``. This flexibility
903+allows for interesting caching strategies such as cache dependency checking. For
904+example, if an object's cache version has been incremented, any dependent objects
905+can be checked (and rebuilt) in cache with the incremented version.
906+
907+Two helper functions for reusing other versions of cache exist::
908+
909+    # CACHE_VERSION = 1
910+
911+    >>> cache.set('my_key', 'hello world!')
912+
913+    >>> v2 = cache.incr_version('my_key') # 2
914+    >>> cache.get('my_key') # None
915+    >>> cache.get('my_key', version=v) # hello world!
916+
917+    >>> v1 = cache.decr_version('my_key', version=v2) # 1
918+    >>> cache.get('my_key', version=v2) # None
919+    >>> cache.get('my_key') # hello world!   
920+
921+Each method returns the new version number associated with the key.
922+
923+Cache Key Module
924+----------------
925+
926+.. versionadded:: 1.3
927+
928+The setting ``CACHE_KEY_MODULE`` can be specified which must contain a function
929+named ``make_key`` that handles constructing the key that is ultimately used
930+when interacting with the cache backend. The function is passed the raw key
931+(what the user specifies), the ``CACHE_KEY_PREFIX``, and the ``CACHE_VERSION``.
932+The default function simply concatenates the three arguments and takes the md5
933+hexdigest of the result. In most cases the default will suffice and a custom
934+will not have to be defined.
935+
936 Upstream caches
937 ===============
938