Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py	(revision 14233)
+++ django/conf/global_settings.py	(working copy)
@@ -428,6 +428,9 @@
 # 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_MODULE = None
 CACHE_MIDDLEWARE_KEY_PREFIX = ''
 CACHE_MIDDLEWARE_SECONDS = 600
 
Index: django/core/cache/__init__.py
===================================================================
--- django/core/cache/__init__.py	(revision 14233)
+++ django/core/cache/__init__.py	(working copy)
@@ -24,6 +24,8 @@
 from django.core import signals
 from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning
 from django.utils import importlib
+from django.utils.encoding import smart_str
+from django.utils.hashcompat import md5_constructor
 
 # 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
@@ -60,18 +62,28 @@
 
     return scheme, host, params
 
-def get_cache(backend_uri):
+def get_cache(backend_uri, key_prefix=None, version=None):
+    if key_prefix is None:
+        key_prefix = settings.CACHE_KEY_PREFIX
+    if version is None:
+        version = settings.CACHE_VERSION
+
+    if settings.CACHE_KEY_MODULE is None:
+        key_func = None
+    else:
+        key_func = importlib.import_module(settings.CACHE_KEY_MODULE).make_key
+
     scheme, host, params = parse_backend_uri(backend_uri)
     if scheme in BACKENDS:
         name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
     else:
         name = scheme
     module = importlib.import_module(name)
-    return module.CacheClass(host, params)
+    return module.CacheClass(host, params, key_prefix, version, key_func)
 
 cache = get_cache(settings.CACHE_BACKEND)
 
-# Some caches -- pythont-memcached in particular -- need to do a cleanup at the
+# 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'):
Index: django/core/cache/backends/base.py
===================================================================
--- django/core/cache/backends/base.py	(revision 14233)
+++ django/core/cache/backends/base.py	(working copy)
@@ -2,7 +2,9 @@
 
 import warnings
 
+from django.utils.hashcompat import md5_constructor
 from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
+from django.utils.encoding import smart_str
 
 class InvalidCacheBackendError(ImproperlyConfigured):
     pass
@@ -13,17 +15,43 @@
 # Memcached does not accept keys longer than this.
 MEMCACHE_MAX_KEY_LENGTH = 250
 
+def make_key(key, key_prefix, version):
+    """Default function to generate keys.
+
+    Constructs the key used by all other methods. By default it prepends
+    the `key_prefix'. In cache backend subclasses this can be overriden to
+    provide custom key making behavior.
+    """
+    s = key_prefix + smart_str(key) + str(version)
+    hashed = md5_constructor(s).hexdigest()
+    return hashed
+
 class BaseCache(object):
-    def __init__(self, params):
+    def __init__(self, params, key_prefix='', version=1, key_func=None):
         timeout = params.get('timeout', 300)
         try:
             timeout = int(timeout)
         except (ValueError, TypeError):
             timeout = 300
         self.default_timeout = timeout
+        self.key_prefix = smart_str(key_prefix)
+        self.version = version
+        self.key_func = key_func or make_key
 
-    def add(self, key, value, timeout=None):
+    def _make_key(self, key, version=None):
+        """Constructs the key used by all other methods. By default it prepends
+        the `key_prefix'. In cache backend subclasses this can be overriden to
+        provide custom key making behavior.
         """
+        if version is None:
+            version = self.version
+
+        new_key = self.key_func(key, self.key_prefix, version)
+        self.validate_key(new_key)
+        return new_key
+
+    def add(self, key, value, timeout=None, version=None):
+        """
         Set a value in the cache if the key does not already exist. If
         timeout is given, that timeout will be used for the key; otherwise
         the default cache timeout will be used.
@@ -32,27 +60,27 @@
         """
         raise NotImplementedError
 
-    def get(self, key, default=None):
+    def get(self, key, default=None, version=None):
         """
         Fetch a given key from the cache. If the key does not exist, return
         default, which itself defaults to None.
         """
         raise NotImplementedError
 
-    def set(self, key, value, timeout=None):
+    def set(self, key, value, timeout=None, version=None):
         """
         Set a value in the cache. If timeout is given, that timeout will be
         used for the key; otherwise the default cache timeout will be used.
         """
         raise NotImplementedError
 
-    def delete(self, key):
+    def delete(self, key, version=None):
         """
         Delete a key from the cache, failing silently.
         """
         raise NotImplementedError
 
-    def get_many(self, keys):
+    def get_many(self, keys, version=None):
         """
         Fetch a bunch of keys from the cache. For certain backends (memcached,
         pgsql) this can be *much* faster when fetching multiple values.
@@ -62,34 +90,35 @@
         """
         d = {}
         for k in keys:
-            val = self.get(k)
+            val = self.get(k, version=version)
             if val is not None:
                 d[k] = val
         return d
 
-    def has_key(self, key):
+    def has_key(self, key, version=None):
         """
         Returns True if the key is in the cache and has not expired.
         """
-        return self.get(key) is not None
+        return self.get(key, version=version) is not None
 
-    def incr(self, key, delta=1):
+    def incr(self, key, delta=1, version=None):
         """
         Add delta to value in the cache. If the key does not exist, raise a
         ValueError exception.
         """
-        if key not in self:
+        value = self.get(key, version=version)
+        if value is None:
             raise ValueError("Key '%s' not found" % key)
-        new_value = self.get(key) + delta
-        self.set(key, new_value)
+        new_value = value + delta
+        self.set(key, new_value, version=version)
         return new_value
 
-    def decr(self, key, delta=1):
+    def decr(self, key, delta=1, version=None):
         """
         Subtract delta from value in the cache. If the key does not exist, raise
         a ValueError exception.
         """
-        return self.incr(key, -delta)
+        return self.incr(key, -delta, version=version)
 
     def __contains__(self, key):
         """
@@ -100,7 +129,7 @@
         # if a subclass overrides it.
         return self.has_key(key)
 
-    def set_many(self, data, timeout=None):
+    def set_many(self, data, timeout=None, version=None):
         """
         Set a bunch of values in the cache at once from a dict of key/value
         pairs.  For certain backends (memcached), this is much more efficient
@@ -110,16 +139,16 @@
         the default cache timeout will be used.
         """
         for key, value in data.items():
-            self.set(key, value, timeout)
+            self.set(key, value, timeout=timeout, version=version)
 
-    def delete_many(self, keys):
+    def delete_many(self, keys, version=None):
         """
         Set a bunch of values in the cache at once.  For certain backends
         (memcached), this is much more efficient than calling delete() multiple
         times.
         """
         for key in keys:
-            self.delete(key)
+            self.delete(key, version=version)
 
     def clear(self):
         """Remove *all* values from the cache at once."""
@@ -142,3 +171,24 @@
                         'errors if used with memcached: %r' % key,
                               CacheKeyWarning)
 
+    def incr_version(self, key, delta=1, version=None):
+        """Adds delta to the cache version for the supplied key. Returns the
+        new version.
+        """
+        if version is None:
+            version = self.version
+
+        value = self.get(key, version=version)
+        if value is None:
+            raise ValueError("Key '%s' not found" % key)
+
+        self.set(key, value, version=version+delta)
+        self.delete(key, version=version)
+        return version+delta
+
+    def decr_version(self, key, delta=1, version=None):
+        """Substracts delta from the cache version for the supplied key. Returns
+        the new version.
+        """
+        return self.incr_version(key, -delta, version)
+
Index: django/core/cache/backends/dummy.py
===================================================================
--- django/core/cache/backends/dummy.py	(revision 14233)
+++ django/core/cache/backends/dummy.py	(working copy)
@@ -3,28 +3,28 @@
 from django.core.cache.backends.base import BaseCache
 
 class CacheClass(BaseCache):
-    def __init__(self, *args, **kwargs):
-        pass
+    def __init__(self, host, *args, **kwargs):
+        BaseCache.__init__(self, *args, **kwargs)
 
     def add(self, key, *args, **kwargs):
-        self.validate_key(key)
+        self._make_key(key)
         return True
 
-    def get(self, key, default=None):
-        self.validate_key(key)
+    def get(self, key, default=None, version=None):
+        self._make_key(key)
         return default
 
     def set(self, key, *args, **kwargs):
-        self.validate_key(key)
+        self._make_key(key)
 
     def delete(self, key, *args, **kwargs):
-        self.validate_key(key)
+        self._make_key(key)
 
     def get_many(self, *args, **kwargs):
         return {}
 
     def has_key(self, key, *args, **kwargs):
-        self.validate_key(key)
+        self._make_key(key)
         return False
 
     def set_many(self, *args, **kwargs):
Index: django/core/cache/backends/locmem.py
===================================================================
--- django/core/cache/backends/locmem.py	(revision 14233)
+++ django/core/cache/backends/locmem.py	(working copy)
@@ -10,8 +10,8 @@
 from django.utils.synch import RWLock
 
 class CacheClass(BaseCache):
-    def __init__(self, _, params):
-        BaseCache.__init__(self, params)
+    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 = {}
 
@@ -29,8 +29,8 @@
 
         self._lock = RWLock()
 
-    def add(self, key, value, timeout=None):
-        self.validate_key(key)
+    def add(self, key, value, timeout=None, version=None):
+        key = self._make_key(key, version=version)
         self._lock.writer_enters()
         try:
             exp = self._expire_info.get(key)
@@ -44,8 +44,8 @@
         finally:
             self._lock.writer_leaves()
 
-    def get(self, key, default=None):
-        self.validate_key(key)
+    def get(self, key, default=None, version=None):
+        key = self._make_key(key, version=version)
         self._lock.reader_enters()
         try:
             exp = self._expire_info.get(key)
@@ -77,8 +77,8 @@
         self._cache[key] = value
         self._expire_info[key] = time.time() + timeout
 
-    def set(self, key, value, timeout=None):
-        self.validate_key(key)
+    def set(self, key, value, timeout=None, version=None):
+        key = self._make_key(key, version=version)
         self._lock.writer_enters()
         # Python 2.4 doesn't allow combined try-except-finally blocks.
         try:
@@ -89,8 +89,8 @@
         finally:
             self._lock.writer_leaves()
 
-    def has_key(self, key):
-        self.validate_key(key)
+    def has_key(self, key, version=None):
+        key = self._make_key(key, version=version)
         self._lock.reader_enters()
         try:
             exp = self._expire_info.get(key)
@@ -130,8 +130,8 @@
         except KeyError:
             pass
 
-    def delete(self, key):
-        self.validate_key(key)
+    def delete(self, key, version=None):
+        key = self._make_key(key, version=version)
         self._lock.writer_enters()
         try:
             self._delete(key)
Index: django/core/cache/backends/filebased.py
===================================================================
--- django/core/cache/backends/filebased.py	(revision 14233)
+++ django/core/cache/backends/filebased.py	(working copy)
@@ -12,8 +12,8 @@
 from django.utils.hashcompat import md5_constructor
 
 class CacheClass(BaseCache):
-    def __init__(self, dir, params):
-        BaseCache.__init__(self, params)
+    def __init__(self, dir, params, key_prefix='', version=1, key_func=None):
+        BaseCache.__init__(self, params, key_prefix, version, key_func)
 
         max_entries = params.get('max_entries', 300)
         try:
@@ -31,16 +31,16 @@
         if not os.path.exists(self._dir):
             self._createdir()
 
-    def add(self, key, value, timeout=None):
-        self.validate_key(key)
-        if self.has_key(key):
+    def add(self, key, value, timeout=None, version=None):
+        if self.has_key(key, version=version):
             return False
 
-        self.set(key, value, timeout)
+        self.set(key, value, timeout, version=version)
         return True
 
-    def get(self, key, default=None):
-        self.validate_key(key)
+    def get(self, key, default=None, version=None):
+        key = self._make_key(key, version=version)
+
         fname = self._key_to_file(key)
         try:
             f = open(fname, 'rb')
@@ -57,8 +57,9 @@
             pass
         return default
 
-    def set(self, key, value, timeout=None):
-        self.validate_key(key)
+    def set(self, key, value, timeout=None, version=None):
+        key = self._make_key(key, version=version)
+
         fname = self._key_to_file(key)
         dirname = os.path.dirname(fname)
 
@@ -81,8 +82,8 @@
         except (IOError, OSError):
             pass
 
-    def delete(self, key):
-        self.validate_key(key)
+    def delete(self, key, version=None):
+        key = self._make_key(key, version=version)
         try:
             self._delete(self._key_to_file(key))
         except (IOError, OSError):
@@ -98,8 +99,8 @@
         except (IOError, OSError):
             pass
 
-    def has_key(self, key):
-        self.validate_key(key)
+    def has_key(self, key, version=None):
+        key = self._make_key(key, version=version)
         fname = self._key_to_file(key)
         try:
             f = open(fname, 'rb')
@@ -153,7 +154,7 @@
         Thus, a cache key of "foo" gets turnned into a file named
         ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
         """
-        path = md5_constructor(key.encode('utf-8')).hexdigest()
+        path = md5_constructor(key).hexdigest()
         path = os.path.join(path[:2], path[2:4], path[4:])
         return os.path.join(self._dir, path)
 
Index: django/core/cache/backends/db.py
===================================================================
--- django/core/cache/backends/db.py	(revision 14233)
+++ django/core/cache/backends/db.py	(working copy)
@@ -26,8 +26,8 @@
         self.proxy = False
 
 class CacheClass(BaseCache):
-    def __init__(self, table, params):
-        BaseCache.__init__(self, params)
+    def __init__(self, table, params, key_prefix='', version=1, key_func=None):
+        BaseCache.__init__(self, params, key_prefix, version, key_func)
         self._table = table
 
         class CacheEntry(object):
@@ -45,8 +45,9 @@
         except (ValueError, TypeError):
             self._cull_frequency = 3
 
-    def get(self, key, default=None):
-        self.validate_key(key)
+    def get(self, key, default=None, version=None):
+        key = self._make_key(key, version=version)
+
         db = router.db_for_read(self.cache_model_class)
         table = connections[db].ops.quote_name(self._table)
         cursor = connections[db].cursor()
@@ -65,12 +66,12 @@
         value = connections[db].ops.process_clob(row[1])
         return pickle.loads(base64.decodestring(value))
 
-    def set(self, key, value, timeout=None):
-        self.validate_key(key)
+    def set(self, key, value, timeout=None, version=None):
+        key = self._make_key(key, version=version)
         self._base_set('set', key, value, timeout)
 
-    def add(self, key, value, timeout=None):
-        self.validate_key(key)
+    def add(self, key, value, timeout=None, version=None):
+        key = self._make_key(key, version=version)
         return self._base_set('add', key, value, timeout)
 
     def _base_set(self, mode, key, value, timeout=None):
@@ -105,8 +106,9 @@
             transaction.commit_unless_managed(using=db)
             return True
 
-    def delete(self, key):
-        self.validate_key(key)
+    def delete(self, key, version=None):
+        key = self._make_key(key, version=version)
+
         db = router.db_for_write(self.cache_model_class)
         table = connections[db].ops.quote_name(self._table)
         cursor = connections[db].cursor()
@@ -114,8 +116,9 @@
         cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key])
         transaction.commit_unless_managed(using=db)
 
-    def has_key(self, key):
-        self.validate_key(key)
+    def has_key(self, key, version=None):
+        key = self._make_key(key, version=version)
+
         db = router.db_for_read(self.cache_model_class)
         table = connections[db].ops.quote_name(self._table)
         cursor = connections[db].cursor()
Index: django/core/cache/backends/memcached.py
===================================================================
--- django/core/cache/backends/memcached.py	(revision 14233)
+++ django/core/cache/backends/memcached.py	(working copy)
@@ -3,7 +3,6 @@
 import time
 
 from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
-from django.utils.encoding import smart_unicode, smart_str
 
 try:
     import cmemcache as memcache
@@ -19,8 +18,8 @@
         raise InvalidCacheBackendError("Memcached cache backend requires either the 'memcache' or 'cmemcache' library")
 
 class CacheClass(BaseCache):
-    def __init__(self, server, params):
-        BaseCache.__init__(self, params)
+    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(';'))
 
     def _get_memcache_timeout(self, timeout):
@@ -39,30 +38,43 @@
             timeout += int(time.time())
         return timeout
 
-    def add(self, key, value, timeout=0):
+    def add(self, key, value, timeout=0, version=None):
+        key = self._make_key(key, version=version)
         if isinstance(value, unicode):
             value = value.encode('utf-8')
-        return self._cache.add(smart_str(key), value, self._get_memcache_timeout(timeout))
+        return self._cache.add(key, value, self._get_memcache_timeout(timeout))
 
-    def get(self, key, default=None):
-        val = self._cache.get(smart_str(key))
+    def get(self, key, default=None, version=None):
+        key = self._make_key(key, version=version)
+        val = self._cache.get(key)
         if val is None:
             return default
         return val
 
-    def set(self, key, value, timeout=0):
-        self._cache.set(smart_str(key), value, self._get_memcache_timeout(timeout))
+    def set(self, key, value, timeout=0, version=None):
+        key = self._make_key(key, version=version)
+        self._cache.set(key, value, self._get_memcache_timeout(timeout))
 
-    def delete(self, key):
-        self._cache.delete(smart_str(key))
+    def delete(self, key, version=None):
+        key = self._make_key(key, version=version)
+        self._cache.delete(key)
 
-    def get_many(self, keys):
-        return self._cache.get_multi(map(smart_str,keys))
+    def get_many(self, keys, version=None):
+        new_keys = map(lambda x: self._make_key(x, version=version), keys)
+        ret = self._cache.get_multi(new_keys)
+        if ret:
+            _ = {}
+            m = dict(zip(new_keys, keys))
+            for k, v in ret.items():
+                _[m[k]] = v
+            ret = _
+        return ret
 
     def close(self, **kwargs):
         self._cache.disconnect_all()
 
-    def incr(self, key, delta=1):
+    def incr(self, key, delta=1, version=None):
+        key = self._make_key(key, version=version)
         try:
             val = self._cache.incr(key, delta)
 
@@ -76,7 +88,8 @@
 
         return val
 
-    def decr(self, key, delta=1):
+    def decr(self, key, delta=1, version=None):
+        key = self._make_key(key, version=version)
         try:
             val = self._cache.decr(key, delta)
 
@@ -89,16 +102,18 @@
             raise ValueError("Key '%s' not found" % key)
         return val
 
-    def set_many(self, data, timeout=0):
+    def set_many(self, data, timeout=0, version=None):
         safe_data = {}
         for key, value in data.items():
+            key = self._make_key(key, version=version)
             if isinstance(value, unicode):
                 value = value.encode('utf-8')
-            safe_data[smart_str(key)] = value
+            safe_data[key] = value
         self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout))
 
-    def delete_many(self, keys):
-        self._cache.delete_multi(map(smart_str, keys))
+    def delete_many(self, keys, version=None):
+        l = lambda x: self._make_key(x, version=version)
+        self._cache.delete_multi(map(l, keys))
 
     def clear(self):
         self._cache.flush_all()
Index: tests/regressiontests/cache/tests.py
===================================================================
--- tests/regressiontests/cache/tests.py	(revision 14233)
+++ tests/regressiontests/cache/tests.py	(working copy)
@@ -144,7 +144,19 @@
         "clear does nothing for the dummy cache backend"
         self.cache.clear()
 
+    def test_incr_version(self):
+        "Dummy cache versions can't be incremented"
+        self.cache.set('answer', 42)
+        self.assertRaises(ValueError, self.cache.incr_version, 'answer')
+        self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist')
 
+    def test_decr_version(self):
+        "Dummy cache versions can't be decremented"
+        self.cache.set('answer', 42)
+        self.assertRaises(ValueError, self.cache.decr_version, 'answer')
+        self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist')
+
+
 class BaseCacheTests(object):
     # A common set of tests to apply to all cache backends
     def tearDown(self):
@@ -162,6 +174,18 @@
         self.assertEqual(result, False)
         self.assertEqual(self.cache.get("addkey1"), "value")
 
+    def test_prefix(self):
+        # Test for same cache key conflicts between shared backend
+        self.cache.set('somekey', 'value')
+
+        # should not be set in the prefixed cache
+        self.assertFalse(self.pfx_cache.has_key('somekey'))
+
+        self.pfx_cache.set('somekey', 'value2')
+
+        self.assertEqual(self.cache.get('somekey'), 'value')
+        self.assertEqual(self.pfx_cache.get('somekey'), 'value2')
+
     def test_non_existent(self):
         # Non-existent cache keys return as None/default
         # get with non-existent keys
@@ -375,6 +399,13 @@
         with more liberal key rules. Refs #6447.
 
         """
+        # mimic custom ``make_key`` method being defined since the default will
+        # never show the below warnings
+        def func(key, *args):
+            return key
+
+        old_func = self.cache.key_func
+        self.cache.key_func = func
         # On Python 2.6+ we could use the catch_warnings context
         # manager to test this warning nicely. Since we can't do that
         # yet, the cleanest option is to temporarily ask for
@@ -392,15 +423,31 @@
         # it. The effect will be the same, as long as the Django test
         # runner doesn't add any global warning filters (it currently
         # does not).
+        self.cache.key_func = old_func
         warnings.resetwarnings()
         warnings.simplefilter("ignore", PendingDeprecationWarning)
 
+    def test_incr_version(self):
+        self.cache.set('answer', 42)
+        self.assertEqual(self.cache.incr_version('answer'), 2)
+        self.assertEqual(self.cache.get('answer', version=2), 42)
+        self.assertEqual(self.cache.get('answer'), None)
+        self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist')
+
+    def test_decr_version(self):
+        self.cache.set('answer', 42, version=2)
+        self.assertEqual(self.cache.decr_version('answer', version=2), 1)
+        self.assertEqual(self.cache.get('answer', version=2), None)
+        self.assertEqual(self.cache.get('answer'), 42)
+        self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist', version=2)
+
 class DBCacheTests(unittest.TestCase, BaseCacheTests):
     def setUp(self):
         # Spaces are used in the table name to ensure quoting/escaping is working
         self._table_name = 'test cache table'
         management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False)
         self.cache = get_cache('db://%s?max_entries=30' % self._table_name)
+        self.pfx_cache = get_cache('db://%s' % self._table_name, 'cacheprefix')
 
     def tearDown(self):
         from django.db import connection
@@ -413,6 +460,7 @@
 class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
     def setUp(self):
         self.cache = get_cache('locmem://?max_entries=30')
+        self.pfx_cache = get_cache('locmem://', 'cacheprefix')
 
     def test_cull(self):
         self.perform_cull_test(50, 29)
@@ -425,6 +473,7 @@
     class MemcachedCacheTests(unittest.TestCase, BaseCacheTests):
         def setUp(self):
             self.cache = get_cache(settings.CACHE_BACKEND)
+            self.pfx_cache = get_cache(settings.CACHE_BACKEND, 'cacheprefix')
 
         def test_invalid_keys(self):
             """
@@ -436,11 +485,20 @@
             that a generic exception of some kind is raised.
 
             """
+            # mimic custom ``make_key`` method being defined since the default will
+            # never show the below warnings
+            def func(key, *args):
+                return key
+
+            old_func = self.cache.key_func
+            self.cache.key_func = func
+
             # memcached does not allow whitespace or control characters in keys
             self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value')
             # memcached limits key length to 250
             self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value')
 
+            self.cache.key_func = old_func
 
 class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
     """
@@ -449,11 +507,13 @@
     def setUp(self):
         self.dirname = tempfile.mkdtemp()
         self.cache = get_cache('file://%s?max_entries=30' % self.dirname)
+        self.pfx_cache = get_cache('file://%s' % self.dirname, 'cacheprefix')
 
     def test_hashing(self):
         """Test that keys are hashed into subdirectories correctly"""
         self.cache.set("foo", "bar")
-        keyhash = md5_constructor("foo").hexdigest()
+        key = self.cache._make_key("foo")
+        keyhash = md5_constructor(key).hexdigest()
         keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
         self.assert_(os.path.exists(keypath))
 
@@ -462,7 +522,8 @@
         Make sure that the created subdirectories are correctly removed when empty.
         """
         self.cache.set("foo", "bar")
-        keyhash = md5_constructor("foo").hexdigest()
+        key = self.cache._make_key("foo")
+        keyhash = md5_constructor(key).hexdigest()
         keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
         self.assert_(os.path.exists(keypath))
 
@@ -472,7 +533,7 @@
         self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath))))
 
     def test_cull(self):
-        self.perform_cull_test(50, 28)
+        self.perform_cull_test(50, 29)
 
 class CustomCacheKeyValidationTests(unittest.TestCase):
     """
@@ -495,16 +556,16 @@
 
     def setUp(self):
         self.path = '/cache/test/'
-        self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
-        self.old_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
+        self.old_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+        self.old_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
         self.orig_use_i18n = settings.USE_I18N
         settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix'
         settings.CACHE_MIDDLEWARE_SECONDS = 1
         settings.USE_I18N = False
 
     def tearDown(self):
-        settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_settings_key_prefix
-        settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds
+        settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_cache_middleware_key_prefix
+        settings.CACHE_MIDDLEWARE_SECONDS = self.old_cache_middleware_seconds
         settings.USE_I18N = self.orig_use_i18n
 
     def _get_request(self, path):
@@ -557,6 +618,16 @@
         learn_cache_key(request, response)
         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
 
+class PrefixedCacheUtils(CacheUtils):
+    def setUp(self):
+        super(PrefixedCacheUtils, self).setUp()
+        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
+        settings.CACHE_KEY_PREFIX = 'cacheprefix'
+
+    def tearDown(self):
+        super(PrefixedCacheUtils, self).tearDown()
+        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
+
 class CacheI18nTest(unittest.TestCase):
 
     def setUp(self):
@@ -652,5 +723,15 @@
         get_cache_data = FetchFromCacheMiddleware().process_request(request)
         self.assertEqual(get_cache_data.content, es_message)
 
+class PrefixedCacheI18nTest(CacheI18nTest):
+    def setUp(self):
+        super(PrefixedCacheI18nTest, self).setUp()
+        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
+        settings.CACHE_KEY_PREFIX = 'cacheprefix'
+
+    def tearDown(self):
+        super(PrefixedCacheI18nTest, self).tearDown()
+        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
+
 if __name__ == '__main__':
     unittest.main()
Index: docs/topics/cache.txt
===================================================================
--- docs/topics/cache.txt	(revision 14233)
+++ docs/topics/cache.txt	(working copy)
@@ -680,6 +680,98 @@
 ...and use the dotted Python path to this module as the scheme portion of your
 :setting:`CACHE_BACKEND`.
 
+Cache Prefix
+------------
+
+.. versionadded:: 1.3
+
+It is a common occurence to have a shared cache instance running on your development
+or production server for use across multiple projects, i.e. all sites are pointing
+to the memcached instance running on port 11211. As a result, cache key conflicts
+may arise which could result in data from one site being used by another.
+
+To alleviate this, CACHE_KEY_PREFIX can be set. This will be prepended to all keys
+that are used with the cache backend, transparently::
+
+    # in settings, CACHE_KEY_PREFIX = 'myproject_'
+
+    >>> cache.set('my_key', 'hello world!') # set with key 'myproject_my_key'
+    >>> cache.get('my_key') # retrieved with key 'myproject_my_key'
+    'hello world!'
+
+Of course, for sites that _do_ share content, simply set the CACHE_KEY_PREFIX for
+both sites to the same value. The default value for CACHE_KEY_PREFIX is the empty
+string ``''``.
+
+.. note::
+   
+   This does *not* conflict with the CACHE_MIDDLEWARE_KEY_PREFIX and can be used
+   in conjunction with it. CACHE_KEY_PREFIX acts as a global prefix for a
+   particular cache instance, therefore it will be prepended to the
+   CACHE_MIDDLEWARE_KEY_PREFIX transparently.
+
+
+Cache Versioning
+----------------
+
+.. versionadded:: 1.3
+
+Cache versioning can be a simple solution to dealing with invalid cache. The
+setting ``CACHE_VERSION`` provides a means of defining which version of cache
+your project will use. This is useful primarily for project upgrades or
+downgrades. For example, if a project is in production and new features are
+being added to it, the cache being used may not be useful any longer. The
+``CACHE_VERSION`` can be incremented and all previous vesions of the cache 
+will be ignored.
+
+.. note::
+
+    This technique simulates "invalidating" your cache, but does not
+    actually free up the space used by the other cache versions. Assuming
+    a reasonable ``max_entries`` and `cull_frequency`` has been defined,
+    this should not be a problem since the old cache will be culled as needed.
+
+For more complex cache versioning strategies, the cache version can specified
+at runtime as such::
+
+    >>> cache.set('my_key', 'hello world!', version=2)
+    >>> cache.get('my_key', version=2) # hello world!
+    >>> cache.get('my_key', version=3) # None
+
+If the version is not supplied, it defaults to ``CACHE_VERSION``. This flexibility
+allows for interesting caching strategies such as cache dependency checking. For
+example, if an object's cache version has been incremented, any dependent objects
+can be checked (and rebuilt) in cache with the incremented version.
+
+Two helper functions for reusing other versions of cache exist::
+
+    # CACHE_VERSION = 1
+
+    >>> cache.set('my_key', 'hello world!')
+
+    >>> v2 = cache.incr_version('my_key') # 2
+    >>> cache.get('my_key') # None
+    >>> cache.get('my_key', version=v) # hello world!
+
+    >>> v1 = cache.decr_version('my_key', version=v2) # 1
+    >>> cache.get('my_key', version=v2) # None
+    >>> cache.get('my_key') # hello world!   
+
+Each method returns the new version number associated with the key.
+
+Cache Key Module
+----------------
+
+.. versionadded:: 1.3
+
+The setting ``CACHE_KEY_MODULE`` can be specified which must contain a function
+named ``make_key`` that handles constructing the key that is ultimately used
+when interacting with the cache backend. The function is passed the raw key
+(what the user specifies), the ``CACHE_KEY_PREFIX``, and the ``CACHE_VERSION``.
+The default function simply concatenates the three arguments and takes the md5
+hexdigest of the result. In most cases the default will suffice and a custom
+will not have to be defined.
+
 Upstream caches
 ===============
 
