diff -r 9aed8047e568 django/conf/global_settings.py
--- a/django/conf/global_settings.py	Thu Nov 18 01:53:38 2010 +0000
+++ b/django/conf/global_settings.py	Fri Nov 19 22:34:44 2010 +0800
@@ -433,6 +433,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_FUNCTION = None
 CACHE_MIDDLEWARE_KEY_PREFIX = ''
 CACHE_MIDDLEWARE_SECONDS = 600
 
diff -r 9aed8047e568 django/core/cache/__init__.py
--- a/django/core/cache/__init__.py	Thu Nov 18 01:53:38 2010 +0000
+++ b/django/core/cache/__init__.py	Fri Nov 19 22:34:44 2010 +0800
@@ -67,18 +67,30 @@
 
     return scheme, host, params
 
-def get_cache(backend_uri):
+def get_cache(backend_uri, key_prefix=None, version=None, key_func=None):
+    if key_prefix is None:
+        key_prefix = settings.CACHE_KEY_PREFIX
+    if version is None:
+        version = settings.CACHE_VERSION
+    if key_func is None:
+        key_func = settings.CACHE_KEY_FUNCTION
+
+    if key_func is not None and not callable(key_func):
+        key_func_module_path, key_func_name = settings.CACHE_KEY_FUNCTION.rsplit('.', 1)
+        key_func_module = importlib.import_module(key_func_module_path)
+        key_func = getattr(key_func_module, key_func_name)
+
     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'):
diff -r 9aed8047e568 django/core/cache/backends/base.py
--- a/django/core/cache/backends/base.py	Thu Nov 18 01:53:38 2010 +0000
+++ b/django/core/cache/backends/base.py	Fri Nov 19 22:34:44 2010 +0800
@@ -3,6 +3,7 @@
 import warnings
 
 from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
+from django.utils.encoding import smart_str
 
 class InvalidCacheBackendError(ImproperlyConfigured):
     pass
@@ -13,8 +14,17 @@
 # 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'. CACHE_KEY_FUNCTION can be used to specify an alternate
+    function with custom key making behavior.
+    """
+    return ':'.join([key_prefix, str(version), smart_str(key)])
+
 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)
@@ -34,7 +44,23 @@
         except (ValueError, TypeError):
             self._cull_frequency = 3
 
-    def add(self, key, value, timeout=None):
+        self.key_prefix = smart_str(key_prefix)
+        self.version = version
+        self.key_func = key_func or make_key
+
+    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
@@ -44,27 +70,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.
@@ -74,34 +100,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):
         """
@@ -112,7 +139,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
@@ -122,16 +149,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."""
@@ -154,3 +181,23 @@
                         '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)
diff -r 9aed8047e568 django/core/cache/backends/db.py
--- a/django/core/cache/backends/db.py	Thu Nov 18 01:53:38 2010 +0000
+++ b/django/core/cache/backends/db.py	Fri Nov 19 22:34:44 2010 +0800
@@ -26,8 +26,8 @@
         self.proxy = False
 
 class BaseDatabaseCacheClass(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):
@@ -35,8 +35,8 @@
         self.cache_model_class = CacheEntry
 
 class CacheClass(BaseDatabaseCacheClass):
-    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()
@@ -55,12 +55,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):
@@ -95,8 +95,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()
@@ -104,8 +105,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()
diff -r 9aed8047e568 django/core/cache/backends/dummy.py
--- a/django/core/cache/backends/dummy.py	Thu Nov 18 01:53:38 2010 +0000
+++ b/django/core/cache/backends/dummy.py	Fri Nov 19 22:34:44 2010 +0800
@@ -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):
diff -r 9aed8047e568 django/core/cache/backends/filebased.py
--- a/django/core/cache/backends/filebased.py	Thu Nov 18 01:53:38 2010 +0000
+++ b/django/core/cache/backends/filebased.py	Fri Nov 19 22:34:44 2010 +0800
@@ -12,22 +12,22 @@
 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)
         self._dir = dir
         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')
@@ -44,8 +44,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)
 
@@ -68,8 +69,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):
@@ -85,8 +86,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')
@@ -140,7 +141,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)
 
diff -r 9aed8047e568 django/core/cache/backends/locmem.py
--- a/django/core/cache/backends/locmem.py	Thu Nov 18 01:53:38 2010 +0000
+++ b/django/core/cache/backends/locmem.py	Fri Nov 19 22:34:44 2010 +0800
@@ -10,14 +10,14 @@
 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 = {}
         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)
@@ -31,8 +31,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)
@@ -64,8 +64,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:
@@ -76,8 +76,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)
@@ -117,8 +117,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)
diff -r 9aed8047e568 django/core/cache/backends/memcached.py
--- a/django/core/cache/backends/memcached.py	Thu Nov 18 01:53:38 2010 +0000
+++ b/django/core/cache/backends/memcached.py	Fri Nov 19 22:34:44 2010 +0800
@@ -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()
diff -r 9aed8047e568 docs/ref/settings.txt
--- a/docs/ref/settings.txt	Thu Nov 18 01:53:38 2010 +0000
+++ b/docs/ref/settings.txt	Fri Nov 19 22:34:44 2010 +0800
@@ -136,6 +136,25 @@
 
 The cache backend to use. See :doc:`/topics/cache`.
 
+.. setting:: CACHE_KEY_FUNCTION
+
+CACHE_KEY_FUNCTION
+------------------
+
+Default: ``None``
+
+A string containing a dotted path to a function that defines how to
+compose a prefix, version and key into a final cache key. The default
+implementation is equivalent to the function::
+
+    def make_key(key, key_prefix, version):
+        return ':'.join([key_prefix, str(version), smart_str(key)])
+
+You may use any key function you want, as long as it adheres to the same
+prototype.
+
+See the :ref:`cache documentation <cache_key_transformation>` for more information.
+
 .. setting:: CACHE_MIDDLEWARE_ANONYMOUS_ONLY
 
 CACHE_MIDDLEWARE_ANONYMOUS_ONLY
@@ -172,6 +191,30 @@
 The default number of seconds to cache a page when the caching middleware or
 ``cache_page()`` decorator is used.
 
+.. setting:: CACHE_PREFIX
+
+CACHE_PREFIX
+------------
+
+Default: ``''`` (Empty string)
+
+A string that will be automatically included (prepended by default) to
+all cache keys used by the Django server.
+
+See the :ref:`cache documentation <cache_key_prefixing>` for more information.
+
+.. setting:: CACHE_VERSION
+
+CACHE_VERSION
+-------------
+
+Default: ``1``
+
+The default version number for cache keys generated by the Django server.
+
+See the :ref:`cache documentation <cache_versioning>` for more information.
+
+
 .. setting:: CSRF_COOKIE_DOMAIN
 
 CSRF_COOKIE_DOMAIN
diff -r 9aed8047e568 docs/releases/1.3.txt
--- a/docs/releases/1.3.txt	Thu Nov 18 01:53:38 2010 +0000
+++ b/docs/releases/1.3.txt	Fri Nov 19 22:34:44 2010 +0800
@@ -155,6 +155,9 @@
       :meth:`~django.test.client.Client.assertNumQueries` -- making it
       easier to test the database activity associated with a view.
 
+    * :ref:`Versioning <cache_versioning>`, :ref:`site-wide prefixing
+      <cache_key_prefixing>` and :ref:`transformation
+      <cache_key_transformation>` has been added to the cache API.
 
 .. _backwards-incompatible-changes-1.3:
 
diff -r 9aed8047e568 docs/topics/cache.txt
--- a/docs/topics/cache.txt	Thu Nov 18 01:53:38 2010 +0000
+++ b/docs/topics/cache.txt	Fri Nov 19 22:34:44 2010 +0800
@@ -643,6 +643,102 @@
     However, if the backend doesn't natively provide an increment/decrement
     operation, it will be implemented using a two-step retrieve/update.
 
+.. _cache_key_prefixing:
+
+Cache key prefixing
+-------------------
+
+.. versionadded:: 1.3
+
+If you are sharing a cache instance between servers, or between your
+production and development environments, it's possible for data cached
+by one server to be used by another server. If the format of cached
+data is different between servers, this can lead to some very hard to
+diagnose problems.
+
+To prevent this, Django provides the ability to prefix all cache keys
+used by a server. When a particular cache key is saved or retrieved,
+Django will automatically prefix the cache key with the value of the
+:setting:`CACHE_KEY_PREFIX` setting.
+
+By ensuring each Django instance has a different
+:setting:`CACHE_KEY_PREFIX`, you can ensure that there will be no
+collisions in cache values.
+
+.. _cache_versioning:
+
+Cache versioning
+----------------
+
+.. versionadded:: 1.3
+
+When you change running code that uses cached values, you may need to
+purge any existing cached values. The easiest way to do this is to
+flush the entire cache, but this may result in the loss of cache
+values that are still valid and useful, but are lost because the
+entire cache is purged.
+
+Django provides a better way to target individual cache values.
+Django's cache framework has a system-wide version identifier,
+specified using the :setting:`CACHE_VERSION` setting. The value of
+this setting is automatically combined with the cache prefix and the
+user-provided cache key to obtain the final cache key.
+
+By default, any key request will automatically include the site
+default cache key version. However, the primitive cache functions all
+include a ``version`` argument, so you can specify a particular cache
+key version to set or get. For example::
+
+    # Set version 2 of a cache key
+    >>> cache.set('my_key', 'hello world!', version=2)
+    # Get the default version (assuming version=1)
+    >>> cache.get('my_key')
+    None
+    # Get version 2 of the same key
+    >>> cache.get('my_key', version=2)
+    'hello world!'
+
+The value of a specific key can be incremented and decremented using
+the :func:`incr_version()` and :func:`decr_version()` methods. This
+enables specific keys to be bumped to a new version, leaving other
+keys unaffected. Continuing our previous example::
+
+    # Increment the version of 'my_key'
+    >>> cache.incr_version('my_key')
+    # The default version still isn't available
+    >>> cache.get('my_key')
+    None
+    # Version 2 isn't available, either
+    >>> cache.get('my_key', version=2)
+    None
+    # But version 3 *is* availble
+    >>> cache.get('my_key', version=3)
+    'hello world!'
+
+.. _cache_key_transformation:
+
+Cache key transformation
+------------------------
+
+.. versionadded:: 1.3
+
+As described in the previous two sections, the cache key provided by a
+user is not used verbatim -- it is combined with the cache prefix and
+key version to provide a final cache key. By default, the three parts
+are joined using colons to produce a final string::
+
+    def make_key(key, key_prefix, version):
+        return ':'.join([key_prefix, str(version), smart_str(key)])
+
+If you want to combine the parts in different ways, or apply other
+processing to the final key (e.g., taking a hash digest of the key
+parts), you can provide a custom key function.
+
+The setting :setting:`CACHE_KEY_FUNCTION` specifies a dotted-path to
+a function matching the prototype of :func:`make_key()` above. If
+provided, this custom key function will be used instead of the default
+key combining function.
+
 Cache key warnings
 ------------------
 
diff -r 9aed8047e568 tests/regressiontests/cache/tests.py
--- a/tests/regressiontests/cache/tests.py	Thu Nov 18 01:53:38 2010 +0000
+++ b/tests/regressiontests/cache/tests.py	Fri Nov 19 22:34:44 2010 +0800
@@ -145,11 +145,21 @@
         "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):
-        self.cache.clear()
 
     def test_simple(self):
         # Simple cache set/get works
@@ -163,6 +173,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.prefix_cache.has_key('somekey'))
+
+        self.prefix_cache.set('somekey', 'value2')
+
+        self.assertEqual(self.cache.get('somekey'), 'value')
+        self.assertEqual(self.prefix_cache.get('somekey'), 'value2')
+
     def test_non_existent(self):
         # Non-existent cache keys return as None/default
         # get with non-existent keys
@@ -376,6 +398,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
@@ -390,6 +419,242 @@
             self.assertRaises(CacheKeyWarning, self.cache.set, 'a' * 251, 'value')
         finally:
             restore_warnings_state(_warnings_state)
+            self.cache.key_func = old_func
+
+    def test_cache_versioning_get_set(self):
+        # set, using default version = 1
+        self.cache.set('answer1', 42)
+        self.assertEqual(self.cache.get('answer1'), 42)
+        self.assertEqual(self.cache.get('answer1', version=1), 42)
+        self.assertEqual(self.cache.get('answer1', version=2), None)
+
+        self.assertEqual(self.v2_cache.get('answer1'), None)
+        # print '---'
+        # print 'c1',self.cache._cache
+        # print 'v2',self.v2_cache._cache
+        self.assertEqual(self.v2_cache.get('answer1', version=1), 42)
+        self.assertEqual(self.v2_cache.get('answer1', version=2), None)
+
+        # set, default version = 1, but manually override version = 2
+        self.cache.set('answer2', 42, version=2)
+        self.assertEqual(self.cache.get('answer2'), None)
+        self.assertEqual(self.cache.get('answer2', version=1), None)
+        self.assertEqual(self.cache.get('answer2', version=2), 42)
+
+        self.assertEqual(self.v2_cache.get('answer2'), 42)
+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
+        self.assertEqual(self.v2_cache.get('answer2', version=2), 42)
+
+        # v2 set, using default version = 2
+        self.v2_cache.set('answer3', 42)
+        self.assertEqual(self.cache.get('answer3'), None)
+        self.assertEqual(self.cache.get('answer3', version=1), None)
+        self.assertEqual(self.cache.get('answer3', version=2), 42)
+
+        self.assertEqual(self.v2_cache.get('answer3'), 42)
+        self.assertEqual(self.v2_cache.get('answer3', version=1), None)
+        self.assertEqual(self.v2_cache.get('answer3', version=2), 42)
+
+        # v2 set, default version = 2, but manually override version = 1
+        self.v2_cache.set('answer4', 42, version=1)
+        self.assertEqual(self.cache.get('answer4'), 42)
+        self.assertEqual(self.cache.get('answer4', version=1), 42)
+        self.assertEqual(self.cache.get('answer4', version=2), None)
+
+        self.assertEqual(self.v2_cache.get('answer4'), None)
+        self.assertEqual(self.v2_cache.get('answer4', version=1), 42)
+        self.assertEqual(self.v2_cache.get('answer4', version=2), None)
+
+    def test_cache_versioning_has_key(self):
+        self.cache.set('answer1', 42)
+
+        # has_key
+        self.assertTrue(self.cache.has_key('answer1'))
+        self.assertTrue(self.cache.has_key('answer1', version=1))
+        self.assertFalse(self.cache.has_key('answer1', version=2))
+
+        self.assertFalse(self.v2_cache.has_key('answer1'))
+        self.assertTrue(self.v2_cache.has_key('answer1', version=1))
+        self.assertFalse(self.v2_cache.has_key('answer1', version=2))
+
+    def test_cache_versioning_delete(self):
+        self.cache.set('answer1', 37, version=1)
+        self.cache.set('answer1', 42, version=2)
+        self.cache.delete('answer1')
+        self.assertEqual(self.cache.get('answer1', version=1), None)
+        self.assertEqual(self.cache.get('answer1', version=2), 42)
+
+        self.cache.set('answer2', 37, version=1)
+        self.cache.set('answer2', 42, version=2)
+        self.cache.delete('answer2', version=2)
+        self.assertEqual(self.cache.get('answer2', version=1), 37)
+        self.assertEqual(self.cache.get('answer2', version=2), None)
+
+        self.cache.set('answer3', 37, version=1)
+        self.cache.set('answer3', 42, version=2)
+        self.v2_cache.delete('answer3')
+        self.assertEqual(self.cache.get('answer3', version=1), 37)
+        self.assertEqual(self.cache.get('answer3', version=2), None)
+
+        self.cache.set('answer4', 37, version=1)
+        self.cache.set('answer4', 42, version=2)
+        self.v2_cache.delete('answer4', version=1)
+        self.assertEqual(self.cache.get('answer4', version=1), None)
+        self.assertEqual(self.cache.get('answer4', version=2), 42)
+
+    def test_cache_versioning_incr_decr(self):
+        self.cache.set('answer1', 37, version=1)
+        self.cache.set('answer1', 42, version=2)
+        self.cache.incr('answer1')
+        self.assertEqual(self.cache.get('answer1', version=1), 38)
+        self.assertEqual(self.cache.get('answer1', version=2), 42)
+        self.cache.decr('answer1')
+        self.assertEqual(self.cache.get('answer1', version=1), 37)
+        self.assertEqual(self.cache.get('answer1', version=2), 42)
+
+        self.cache.set('answer2', 37, version=1)
+        self.cache.set('answer2', 42, version=2)
+        self.cache.incr('answer2', version=2)
+        self.assertEqual(self.cache.get('answer2', version=1), 37)
+        self.assertEqual(self.cache.get('answer2', version=2), 43)
+        self.cache.decr('answer2', version=2)
+        self.assertEqual(self.cache.get('answer2', version=1), 37)
+        self.assertEqual(self.cache.get('answer2', version=2), 42)
+
+        self.cache.set('answer3', 37, version=1)
+        self.cache.set('answer3', 42, version=2)
+        self.v2_cache.incr('answer3')
+        self.assertEqual(self.cache.get('answer3', version=1), 37)
+        self.assertEqual(self.cache.get('answer3', version=2), 43)
+        self.v2_cache.decr('answer3')
+        self.assertEqual(self.cache.get('answer3', version=1), 37)
+        self.assertEqual(self.cache.get('answer3', version=2), 42)
+
+        self.cache.set('answer4', 37, version=1)
+        self.cache.set('answer4', 42, version=2)
+        self.v2_cache.incr('answer4', version=1)
+        self.assertEqual(self.cache.get('answer4', version=1), 38)
+        self.assertEqual(self.cache.get('answer4', version=2), 42)
+        self.v2_cache.decr('answer4', version=1)
+        self.assertEqual(self.cache.get('answer4', version=1), 37)
+        self.assertEqual(self.cache.get('answer4', version=2), 42)
+
+    def test_cache_versioning_get_set_many(self):
+        # set, using default version = 1
+        self.cache.set_many({'ford1': 37, 'arthur1': 42})
+        self.assertEqual(self.cache.get_many(['ford1','arthur1']),
+                         {'ford1': 37, 'arthur1': 42})
+        self.assertEqual(self.cache.get_many(['ford1','arthur1'], version=1),
+                         {'ford1': 37, 'arthur1': 42})
+        self.assertEqual(self.cache.get_many(['ford1','arthur1'], version=2), {})
+
+        self.assertEqual(self.v2_cache.get_many(['ford1','arthur1']), {})
+        self.assertEqual(self.v2_cache.get_many(['ford1','arthur1'], version=1),
+                         {'ford1': 37, 'arthur1': 42})
+        self.assertEqual(self.v2_cache.get_many(['ford1','arthur1'], version=2), {})
+
+        # set, default version = 1, but manually override version = 2
+        self.cache.set_many({'ford2': 37, 'arthur2': 42}, version=2)
+        self.assertEqual(self.cache.get_many(['ford2','arthur2']), {})
+        self.assertEqual(self.cache.get_many(['ford2','arthur2'], version=1), {})
+        self.assertEqual(self.cache.get_many(['ford2','arthur2'], version=2),
+                         {'ford2': 37, 'arthur2': 42})
+
+        self.assertEqual(self.v2_cache.get_many(['ford2','arthur2']),
+                         {'ford2': 37, 'arthur2': 42})
+        self.assertEqual(self.v2_cache.get_many(['ford2','arthur2'], version=1), {})
+        self.assertEqual(self.v2_cache.get_many(['ford2','arthur2'], version=2),
+                         {'ford2': 37, 'arthur2': 42})
+
+        # v2 set, using default version = 2
+        self.v2_cache.set_many({'ford3': 37, 'arthur3': 42})
+        self.assertEqual(self.cache.get_many(['ford3','arthur3']), {})
+        self.assertEqual(self.cache.get_many(['ford3','arthur3'], version=1), {})
+        self.assertEqual(self.cache.get_many(['ford3','arthur3'], version=2),
+                         {'ford3': 37, 'arthur3': 42})
+
+        self.assertEqual(self.v2_cache.get_many(['ford3','arthur3']),
+                         {'ford3': 37, 'arthur3': 42})
+        self.assertEqual(self.v2_cache.get_many(['ford3','arthur3'], version=1), {})
+        self.assertEqual(self.v2_cache.get_many(['ford3','arthur3'], version=2),
+                         {'ford3': 37, 'arthur3': 42})
+
+        # v2 set, default version = 2, but manually override version = 1
+        self.v2_cache.set_many({'ford4': 37, 'arthur4': 42}, version=1)
+        self.assertEqual(self.cache.get_many(['ford4','arthur4']),
+                         {'ford4': 37, 'arthur4': 42})
+        self.assertEqual(self.cache.get_many(['ford4','arthur4'], version=1),
+                         {'ford4': 37, 'arthur4': 42})
+        self.assertEqual(self.cache.get_many(['ford4','arthur4'], version=2), {})
+
+        self.assertEqual(self.v2_cache.get_many(['ford4','arthur4']), {})
+        self.assertEqual(self.v2_cache.get_many(['ford4','arthur4'], version=1),
+                         {'ford4': 37, 'arthur4': 42})
+        self.assertEqual(self.v2_cache.get_many(['ford4','arthur4'], version=2), {})
+
+    def test_incr_version(self):
+        self.cache.set('answer', 42, version=2)
+        self.assertEqual(self.cache.get('answer'), None)
+        self.assertEqual(self.cache.get('answer', version=1), None)
+        self.assertEqual(self.cache.get('answer', version=2), 42)
+        self.assertEqual(self.cache.get('answer', version=3), None)
+
+        self.assertEqual(self.cache.incr_version('answer', version=2), 3)
+        self.assertEqual(self.cache.get('answer'), None)
+        self.assertEqual(self.cache.get('answer', version=1), None)
+        self.assertEqual(self.cache.get('answer', version=2), None)
+        self.assertEqual(self.cache.get('answer', version=3), 42)
+
+        self.v2_cache.set('answer2', 42)
+        self.assertEqual(self.v2_cache.get('answer2'), 42)
+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
+        self.assertEqual(self.v2_cache.get('answer2', version=2), 42)
+        self.assertEqual(self.v2_cache.get('answer2', version=3), None)
+
+        self.assertEqual(self.v2_cache.incr_version('answer2'), 3)
+        self.assertEqual(self.v2_cache.get('answer2'), None)
+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
+        self.assertEqual(self.v2_cache.get('answer2', version=2), None)
+        self.assertEqual(self.v2_cache.get('answer2', version=3), 42)
+
+        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.get('answer'), None)
+        self.assertEqual(self.cache.get('answer', version=1), None)
+        self.assertEqual(self.cache.get('answer', version=2), 42)
+
+        self.assertEqual(self.cache.decr_version('answer', version=2), 1)
+        self.assertEqual(self.cache.get('answer'), 42)
+        self.assertEqual(self.cache.get('answer', version=1), 42)
+        self.assertEqual(self.cache.get('answer', version=2), None)
+
+        self.v2_cache.set('answer2', 42)
+        self.assertEqual(self.v2_cache.get('answer2'), 42)
+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
+        self.assertEqual(self.v2_cache.get('answer2', version=2), 42)
+
+        self.assertEqual(self.v2_cache.decr_version('answer2'), 1)
+        self.assertEqual(self.v2_cache.get('answer2'), None)
+        self.assertEqual(self.v2_cache.get('answer2', version=1), 42)
+        self.assertEqual(self.v2_cache.get('answer2', version=2), None)
+
+        self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist', version=2)
+
+    def test_custom_key_func(self):
+        # Two caches with different key functions aren't visible to each other
+        self.cache.set('answer1', 42)
+        self.assertEqual(self.cache.get('answer1'), 42)
+        self.assertEqual(self.custom_key_cache.get('answer1'), None)
+
+        self.custom_key_cache.set('answer2', 42)
+        self.assertEqual(self.cache.get('answer2'), None)
+        self.assertEqual(self.custom_key_cache.get('answer2'), 42)
+
+def custom_key_func(key, key_prefix, version):
+    "A customized cache key function"
+    return 'CUSTOM-' + '-'.join([key_prefix, str(version), key])
 
 class DBCacheTests(unittest.TestCase, BaseCacheTests):
     def setUp(self):
@@ -397,6 +662,9 @@
         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.prefix_cache = get_cache('db://%s' % self._table_name, key_prefix='cacheprefix')
+        self.v2_cache = get_cache('db://%s' % self._table_name, version=2)
+        self.custom_key_cache = get_cache('db://%s' % self._table_name, key_func=custom_key_func)
 
     def tearDown(self):
         from django.db import connection
@@ -413,6 +681,20 @@
 class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
     def setUp(self):
         self.cache = get_cache('locmem://?max_entries=30')
+        self.prefix_cache = get_cache('locmem://', key_prefix='cacheprefix')
+        self.v2_cache = get_cache('locmem://', version=2)
+        self.custom_key_cache = get_cache('locmem://?max_entries=30', key_func=custom_key_func)
+
+        # LocMem requires a hack to make the other caches
+        # share a data store with the 'normal' cache.
+        self.prefix_cache._cache = self.cache._cache
+        self.prefix_cache._expire_info = self.cache._expire_info
+
+        self.v2_cache._cache = self.cache._cache
+        self.v2_cache._expire_info = self.cache._expire_info
+
+        self.custom_key_cache._cache = self.cache._cache
+        self.custom_key_cache._expire_info = self.cache._expire_info
 
     def test_cull(self):
         self.perform_cull_test(50, 29)
@@ -428,6 +710,15 @@
 class MemcachedCacheTests(unittest.TestCase, BaseCacheTests):
     def setUp(self):
         self.cache = get_cache(settings.CACHE_BACKEND)
+        self.prefix_cache = get_cache(settings.CACHE_BACKEND, key_prefix='cacheprefix')
+        self.v2_cache = get_cache(settings.CACHE_BACKEND, version=2)
+        self.custom_key_cache = get_cache(settings.CACHE_BACKEND, key_func=custom_key_func)
+
+    def tearDown(self):
+        self.cache.clear()
+        self.prefix_cache.clear()
+        self.v2_cache.clear()
+        self.custom_key_cache.clear()
 
     def test_invalid_keys(self):
         """
@@ -439,10 +730,16 @@
         that a generic exception of some kind is raised.
 
         """
+        _warnings_state = get_warnings_state()
+        warnings.simplefilter("ignore", CacheKeyWarning)
+
         # 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')
+
+        restore_warnings_state(_warnings_state)
+
 MemcachedCacheTests = unittest.skipUnless(settings.CACHE_BACKEND.startswith('memcached://'), "memcached not available")(MemcachedCacheTests)
 
 class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
@@ -452,11 +749,21 @@
     def setUp(self):
         self.dirname = tempfile.mkdtemp()
         self.cache = get_cache('file://%s?max_entries=30' % self.dirname)
+        self.prefix_cache = get_cache('file://%s' % self.dirname, key_prefix='cacheprefix')
+        self.v2_cache = get_cache('file://%s' % self.dirname, version=2)
+        self.custom_key_cache = get_cache('file://%s' % self.dirname, key_func=custom_key_func)
+
+    def tearDown(self):
+        self.cache.clear()
+        self.prefix_cache.clear()
+        self.v2_cache.clear()
+        self.custom_key_cache.clear()
 
     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))
 
@@ -465,7 +772,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))
 
@@ -475,7 +783,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):
     """
@@ -498,16 +806,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, method='GET'):
@@ -561,6 +869,16 @@
         learn_cache_key(request, response)
         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.HEAD.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 CacheHEADTest(unittest.TestCase):
 
     def setUp(self):
@@ -714,5 +1032,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()
