Code

Ticket #13795: t13795-rc1.diff

File t13795-rc1.diff, 48.1 KB (added by russellm, 3 years ago)

RC1 of site-wide caching prefix

Line 
1diff -r 9aed8047e568 django/conf/global_settings.py
2--- a/django/conf/global_settings.py    Thu Nov 18 01:53:38 2010 +0000
3+++ b/django/conf/global_settings.py    Fri Nov 19 22:34:44 2010 +0800
4@@ -433,6 +433,9 @@
5 # The cache backend to use.  See the docstring in django.core.cache for the
6 # possible values.
7 CACHE_BACKEND = 'locmem://'
8+CACHE_VERSION = 1
9+CACHE_KEY_PREFIX = ''
10+CACHE_KEY_FUNCTION = None
11 CACHE_MIDDLEWARE_KEY_PREFIX = ''
12 CACHE_MIDDLEWARE_SECONDS = 600
13 
14diff -r 9aed8047e568 django/core/cache/__init__.py
15--- a/django/core/cache/__init__.py     Thu Nov 18 01:53:38 2010 +0000
16+++ b/django/core/cache/__init__.py     Fri Nov 19 22:34:44 2010 +0800
17@@ -67,18 +67,30 @@
18 
19     return scheme, host, params
20 
21-def get_cache(backend_uri):
22+def get_cache(backend_uri, key_prefix=None, version=None, key_func=None):
23+    if key_prefix is None:
24+        key_prefix = settings.CACHE_KEY_PREFIX
25+    if version is None:
26+        version = settings.CACHE_VERSION
27+    if key_func is None:
28+        key_func = settings.CACHE_KEY_FUNCTION
29+
30+    if key_func is not None and not callable(key_func):
31+        key_func_module_path, key_func_name = settings.CACHE_KEY_FUNCTION.rsplit('.', 1)
32+        key_func_module = importlib.import_module(key_func_module_path)
33+        key_func = getattr(key_func_module, key_func_name)
34+
35     scheme, host, params = parse_backend_uri(backend_uri)
36     if scheme in BACKENDS:
37         name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
38     else:
39         name = scheme
40     module = importlib.import_module(name)
41-    return module.CacheClass(host, params)
42+    return module.CacheClass(host, params, key_prefix, version, key_func)
43 
44 cache = get_cache(settings.CACHE_BACKEND)
45 
46-# Some caches -- pythont-memcached in particular -- need to do a cleanup at the
47+# Some caches -- python-memcached in particular -- need to do a cleanup at the
48 # end of a request cycle. If the cache provides a close() method, wire it up
49 # here.
50 if hasattr(cache, 'close'):
51diff -r 9aed8047e568 django/core/cache/backends/base.py
52--- a/django/core/cache/backends/base.py        Thu Nov 18 01:53:38 2010 +0000
53+++ b/django/core/cache/backends/base.py        Fri Nov 19 22:34:44 2010 +0800
54@@ -3,6 +3,7 @@
55 import warnings
56 
57 from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
58+from django.utils.encoding import smart_str
59 
60 class InvalidCacheBackendError(ImproperlyConfigured):
61     pass
62@@ -13,8 +14,17 @@
63 # Memcached does not accept keys longer than this.
64 MEMCACHE_MAX_KEY_LENGTH = 250
65 
66+def make_key(key, key_prefix, version):
67+    """Default function to generate keys.
68+
69+    Constructs the key used by all other methods. By default it prepends
70+    the `key_prefix'. CACHE_KEY_FUNCTION can be used to specify an alternate
71+    function with custom key making behavior.
72+    """
73+    return ':'.join([key_prefix, str(version), smart_str(key)])
74+
75 class BaseCache(object):
76-    def __init__(self, params):
77+    def __init__(self, params, key_prefix='', version=1, key_func=None):
78         timeout = params.get('timeout', 300)
79         try:
80             timeout = int(timeout)
81@@ -34,7 +44,23 @@
82         except (ValueError, TypeError):
83             self._cull_frequency = 3
84 
85-    def add(self, key, value, timeout=None):
86+        self.key_prefix = smart_str(key_prefix)
87+        self.version = version
88+        self.key_func = key_func or make_key
89+
90+    def _make_key(self, key, version=None):
91+        """Constructs the key used by all other methods. By default it prepends
92+        the `key_prefix'. In cache backend subclasses this can be overriden to
93+        provide custom key making behavior.
94+        """
95+        if version is None:
96+            version = self.version
97+
98+        new_key = self.key_func(key, self.key_prefix, version)
99+        self.validate_key(new_key)
100+        return new_key
101+
102+    def add(self, key, value, timeout=None, version=None):
103         """
104         Set a value in the cache if the key does not already exist. If
105         timeout is given, that timeout will be used for the key; otherwise
106@@ -44,27 +70,27 @@
107         """
108         raise NotImplementedError
109 
110-    def get(self, key, default=None):
111+    def get(self, key, default=None, version=None):
112         """
113         Fetch a given key from the cache. If the key does not exist, return
114         default, which itself defaults to None.
115         """
116         raise NotImplementedError
117 
118-    def set(self, key, value, timeout=None):
119+    def set(self, key, value, timeout=None, version=None):
120         """
121         Set a value in the cache. If timeout is given, that timeout will be
122         used for the key; otherwise the default cache timeout will be used.
123         """
124         raise NotImplementedError
125 
126-    def delete(self, key):
127+    def delete(self, key, version=None):
128         """
129         Delete a key from the cache, failing silently.
130         """
131         raise NotImplementedError
132 
133-    def get_many(self, keys):
134+    def get_many(self, keys, version=None):
135         """
136         Fetch a bunch of keys from the cache. For certain backends (memcached,
137         pgsql) this can be *much* faster when fetching multiple values.
138@@ -74,34 +100,35 @@
139         """
140         d = {}
141         for k in keys:
142-            val = self.get(k)
143+            val = self.get(k, version=version)
144             if val is not None:
145                 d[k] = val
146         return d
147 
148-    def has_key(self, key):
149+    def has_key(self, key, version=None):
150         """
151         Returns True if the key is in the cache and has not expired.
152         """
153-        return self.get(key) is not None
154+        return self.get(key, version=version) is not None
155 
156-    def incr(self, key, delta=1):
157+    def incr(self, key, delta=1, version=None):
158         """
159         Add delta to value in the cache. If the key does not exist, raise a
160         ValueError exception.
161         """
162-        if key not in self:
163+        value = self.get(key, version=version)
164+        if value is None:
165             raise ValueError("Key '%s' not found" % key)
166-        new_value = self.get(key) + delta
167-        self.set(key, new_value)
168+        new_value = value + delta
169+        self.set(key, new_value, version=version)
170         return new_value
171 
172-    def decr(self, key, delta=1):
173+    def decr(self, key, delta=1, version=None):
174         """
175         Subtract delta from value in the cache. If the key does not exist, raise
176         a ValueError exception.
177         """
178-        return self.incr(key, -delta)
179+        return self.incr(key, -delta, version=version)
180 
181     def __contains__(self, key):
182         """
183@@ -112,7 +139,7 @@
184         # if a subclass overrides it.
185         return self.has_key(key)
186 
187-    def set_many(self, data, timeout=None):
188+    def set_many(self, data, timeout=None, version=None):
189         """
190         Set a bunch of values in the cache at once from a dict of key/value
191         pairs.  For certain backends (memcached), this is much more efficient
192@@ -122,16 +149,16 @@
193         the default cache timeout will be used.
194         """
195         for key, value in data.items():
196-            self.set(key, value, timeout)
197+            self.set(key, value, timeout=timeout, version=version)
198 
199-    def delete_many(self, keys):
200+    def delete_many(self, keys, version=None):
201         """
202         Set a bunch of values in the cache at once.  For certain backends
203         (memcached), this is much more efficient than calling delete() multiple
204         times.
205         """
206         for key in keys:
207-            self.delete(key)
208+            self.delete(key, version=version)
209 
210     def clear(self):
211         """Remove *all* values from the cache at once."""
212@@ -154,3 +181,23 @@
213                         'errors if used with memcached: %r' % key,
214                               CacheKeyWarning)
215 
216+    def incr_version(self, key, delta=1, version=None):
217+        """Adds delta to the cache version for the supplied key. Returns the
218+        new version.
219+        """
220+        if version is None:
221+            version = self.version
222+
223+        value = self.get(key, version=version)
224+        if value is None:
225+            raise ValueError("Key '%s' not found" % key)
226+
227+        self.set(key, value, version=version+delta)
228+        self.delete(key, version=version)
229+        return version+delta
230+
231+    def decr_version(self, key, delta=1, version=None):
232+        """Substracts delta from the cache version for the supplied key. Returns
233+        the new version.
234+        """
235+        return self.incr_version(key, -delta, version)
236diff -r 9aed8047e568 django/core/cache/backends/db.py
237--- a/django/core/cache/backends/db.py  Thu Nov 18 01:53:38 2010 +0000
238+++ b/django/core/cache/backends/db.py  Fri Nov 19 22:34:44 2010 +0800
239@@ -26,8 +26,8 @@
240         self.proxy = False
241 
242 class BaseDatabaseCacheClass(BaseCache):
243-    def __init__(self, table, params):
244-        BaseCache.__init__(self, params)
245+    def __init__(self, table, params, key_prefix='', version=1, key_func=None):
246+        BaseCache.__init__(self, params, key_prefix, version, key_func)
247         self._table = table
248 
249         class CacheEntry(object):
250@@ -35,8 +35,8 @@
251         self.cache_model_class = CacheEntry
252 
253 class CacheClass(BaseDatabaseCacheClass):
254-    def get(self, key, default=None):
255-        self.validate_key(key)
256+    def get(self, key, default=None, version=None):
257+        key = self._make_key(key, version=version)
258         db = router.db_for_read(self.cache_model_class)
259         table = connections[db].ops.quote_name(self._table)
260         cursor = connections[db].cursor()
261@@ -55,12 +55,12 @@
262         value = connections[db].ops.process_clob(row[1])
263         return pickle.loads(base64.decodestring(value))
264 
265-    def set(self, key, value, timeout=None):
266-        self.validate_key(key)
267+    def set(self, key, value, timeout=None, version=None):
268+        key = self._make_key(key, version=version)
269         self._base_set('set', key, value, timeout)
270 
271-    def add(self, key, value, timeout=None):
272-        self.validate_key(key)
273+    def add(self, key, value, timeout=None, version=None):
274+        key = self._make_key(key, version=version)
275         return self._base_set('add', key, value, timeout)
276 
277     def _base_set(self, mode, key, value, timeout=None):
278@@ -95,8 +95,9 @@
279             transaction.commit_unless_managed(using=db)
280             return True
281 
282-    def delete(self, key):
283-        self.validate_key(key)
284+    def delete(self, key, version=None):
285+        key = self._make_key(key, version=version)
286+
287         db = router.db_for_write(self.cache_model_class)
288         table = connections[db].ops.quote_name(self._table)
289         cursor = connections[db].cursor()
290@@ -104,8 +105,9 @@
291         cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key])
292         transaction.commit_unless_managed(using=db)
293 
294-    def has_key(self, key):
295-        self.validate_key(key)
296+    def has_key(self, key, version=None):
297+        key = self._make_key(key, version=version)
298+
299         db = router.db_for_read(self.cache_model_class)
300         table = connections[db].ops.quote_name(self._table)
301         cursor = connections[db].cursor()
302diff -r 9aed8047e568 django/core/cache/backends/dummy.py
303--- a/django/core/cache/backends/dummy.py       Thu Nov 18 01:53:38 2010 +0000
304+++ b/django/core/cache/backends/dummy.py       Fri Nov 19 22:34:44 2010 +0800
305@@ -3,28 +3,28 @@
306 from django.core.cache.backends.base import BaseCache
307 
308 class CacheClass(BaseCache):
309-    def __init__(self, *args, **kwargs):
310-        pass
311+    def __init__(self, host, *args, **kwargs):
312+        BaseCache.__init__(self, *args, **kwargs)
313 
314     def add(self, key, *args, **kwargs):
315-        self.validate_key(key)
316+        self._make_key(key)
317         return True
318 
319-    def get(self, key, default=None):
320-        self.validate_key(key)
321+    def get(self, key, default=None, version=None):
322+        self._make_key(key)
323         return default
324 
325     def set(self, key, *args, **kwargs):
326-        self.validate_key(key)
327+        self._make_key(key)
328 
329     def delete(self, key, *args, **kwargs):
330-        self.validate_key(key)
331+        self._make_key(key)
332 
333     def get_many(self, *args, **kwargs):
334         return {}
335 
336     def has_key(self, key, *args, **kwargs):
337-        self.validate_key(key)
338+        self._make_key(key)
339         return False
340 
341     def set_many(self, *args, **kwargs):
342diff -r 9aed8047e568 django/core/cache/backends/filebased.py
343--- a/django/core/cache/backends/filebased.py   Thu Nov 18 01:53:38 2010 +0000
344+++ b/django/core/cache/backends/filebased.py   Fri Nov 19 22:34:44 2010 +0800
345@@ -12,22 +12,22 @@
346 from django.utils.hashcompat import md5_constructor
347 
348 class CacheClass(BaseCache):
349-    def __init__(self, dir, params):
350-        BaseCache.__init__(self, params)
351+    def __init__(self, dir, params, key_prefix='', version=1, key_func=None):
352+        BaseCache.__init__(self, params, key_prefix, version, key_func)
353         self._dir = dir
354         if not os.path.exists(self._dir):
355             self._createdir()
356 
357-    def add(self, key, value, timeout=None):
358-        self.validate_key(key)
359-        if self.has_key(key):
360+    def add(self, key, value, timeout=None, version=None):
361+        if self.has_key(key, version=version):
362             return False
363 
364-        self.set(key, value, timeout)
365+        self.set(key, value, timeout, version=version)
366         return True
367 
368-    def get(self, key, default=None):
369-        self.validate_key(key)
370+    def get(self, key, default=None, version=None):
371+        key = self._make_key(key, version=version)
372+
373         fname = self._key_to_file(key)
374         try:
375             f = open(fname, 'rb')
376@@ -44,8 +44,9 @@
377             pass
378         return default
379 
380-    def set(self, key, value, timeout=None):
381-        self.validate_key(key)
382+    def set(self, key, value, timeout=None, version=None):
383+        key = self._make_key(key, version=version)
384+
385         fname = self._key_to_file(key)
386         dirname = os.path.dirname(fname)
387 
388@@ -68,8 +69,8 @@
389         except (IOError, OSError):
390             pass
391 
392-    def delete(self, key):
393-        self.validate_key(key)
394+    def delete(self, key, version=None):
395+        key = self._make_key(key, version=version)
396         try:
397             self._delete(self._key_to_file(key))
398         except (IOError, OSError):
399@@ -85,8 +86,8 @@
400         except (IOError, OSError):
401             pass
402 
403-    def has_key(self, key):
404-        self.validate_key(key)
405+    def has_key(self, key, version=None):
406+        key = self._make_key(key, version=version)
407         fname = self._key_to_file(key)
408         try:
409             f = open(fname, 'rb')
410@@ -140,7 +141,7 @@
411         Thus, a cache key of "foo" gets turnned into a file named
412         ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
413         """
414-        path = md5_constructor(key.encode('utf-8')).hexdigest()
415+        path = md5_constructor(key).hexdigest()
416         path = os.path.join(path[:2], path[2:4], path[4:])
417         return os.path.join(self._dir, path)
418 
419diff -r 9aed8047e568 django/core/cache/backends/locmem.py
420--- a/django/core/cache/backends/locmem.py      Thu Nov 18 01:53:38 2010 +0000
421+++ b/django/core/cache/backends/locmem.py      Fri Nov 19 22:34:44 2010 +0800
422@@ -10,14 +10,14 @@
423 from django.utils.synch import RWLock
424 
425 class CacheClass(BaseCache):
426-    def __init__(self, _, params):
427-        BaseCache.__init__(self, params)
428+    def __init__(self, _, params, key_prefix='', version=1, key_func=None):
429+        BaseCache.__init__(self, params, key_prefix, version, key_func)
430         self._cache = {}
431         self._expire_info = {}
432         self._lock = RWLock()
433 
434-    def add(self, key, value, timeout=None):
435-        self.validate_key(key)
436+    def add(self, key, value, timeout=None, version=None):
437+        key = self._make_key(key, version=version)
438         self._lock.writer_enters()
439         try:
440             exp = self._expire_info.get(key)
441@@ -31,8 +31,8 @@
442         finally:
443             self._lock.writer_leaves()
444 
445-    def get(self, key, default=None):
446-        self.validate_key(key)
447+    def get(self, key, default=None, version=None):
448+        key = self._make_key(key, version=version)
449         self._lock.reader_enters()
450         try:
451             exp = self._expire_info.get(key)
452@@ -64,8 +64,8 @@
453         self._cache[key] = value
454         self._expire_info[key] = time.time() + timeout
455 
456-    def set(self, key, value, timeout=None):
457-        self.validate_key(key)
458+    def set(self, key, value, timeout=None, version=None):
459+        key = self._make_key(key, version=version)
460         self._lock.writer_enters()
461         # Python 2.4 doesn't allow combined try-except-finally blocks.
462         try:
463@@ -76,8 +76,8 @@
464         finally:
465             self._lock.writer_leaves()
466 
467-    def has_key(self, key):
468-        self.validate_key(key)
469+    def has_key(self, key, version=None):
470+        key = self._make_key(key, version=version)
471         self._lock.reader_enters()
472         try:
473             exp = self._expire_info.get(key)
474@@ -117,8 +117,8 @@
475         except KeyError:
476             pass
477 
478-    def delete(self, key):
479-        self.validate_key(key)
480+    def delete(self, key, version=None):
481+        key = self._make_key(key, version=version)
482         self._lock.writer_enters()
483         try:
484             self._delete(key)
485diff -r 9aed8047e568 django/core/cache/backends/memcached.py
486--- a/django/core/cache/backends/memcached.py   Thu Nov 18 01:53:38 2010 +0000
487+++ b/django/core/cache/backends/memcached.py   Fri Nov 19 22:34:44 2010 +0800
488@@ -3,7 +3,6 @@
489 import time
490 
491 from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
492-from django.utils.encoding import smart_unicode, smart_str
493 
494 try:
495     import cmemcache as memcache
496@@ -19,8 +18,8 @@
497         raise InvalidCacheBackendError("Memcached cache backend requires either the 'memcache' or 'cmemcache' library")
498 
499 class CacheClass(BaseCache):
500-    def __init__(self, server, params):
501-        BaseCache.__init__(self, params)
502+    def __init__(self, server, params, key_prefix='', version=1, key_func=None):
503+        BaseCache.__init__(self, params, key_prefix, version, key_func)
504         self._cache = memcache.Client(server.split(';'))
505 
506     def _get_memcache_timeout(self, timeout):
507@@ -39,30 +38,43 @@
508             timeout += int(time.time())
509         return timeout
510 
511-    def add(self, key, value, timeout=0):
512+    def add(self, key, value, timeout=0, version=None):
513+        key = self._make_key(key, version=version)
514         if isinstance(value, unicode):
515             value = value.encode('utf-8')
516-        return self._cache.add(smart_str(key), value, self._get_memcache_timeout(timeout))
517+        return self._cache.add(key, value, self._get_memcache_timeout(timeout))
518 
519-    def get(self, key, default=None):
520-        val = self._cache.get(smart_str(key))
521+    def get(self, key, default=None, version=None):
522+        key = self._make_key(key, version=version)
523+        val = self._cache.get(key)
524         if val is None:
525             return default
526         return val
527 
528-    def set(self, key, value, timeout=0):
529-        self._cache.set(smart_str(key), value, self._get_memcache_timeout(timeout))
530+    def set(self, key, value, timeout=0, version=None):
531+        key = self._make_key(key, version=version)
532+        self._cache.set(key, value, self._get_memcache_timeout(timeout))
533 
534-    def delete(self, key):
535-        self._cache.delete(smart_str(key))
536+    def delete(self, key, version=None):
537+        key = self._make_key(key, version=version)
538+        self._cache.delete(key)
539 
540-    def get_many(self, keys):
541-        return self._cache.get_multi(map(smart_str,keys))
542+    def get_many(self, keys, version=None):
543+        new_keys = map(lambda x: self._make_key(x, version=version), keys)
544+        ret = self._cache.get_multi(new_keys)
545+        if ret:
546+            _ = {}
547+            m = dict(zip(new_keys, keys))
548+            for k, v in ret.items():
549+                _[m[k]] = v
550+            ret = _
551+        return ret
552 
553     def close(self, **kwargs):
554         self._cache.disconnect_all()
555 
556-    def incr(self, key, delta=1):
557+    def incr(self, key, delta=1, version=None):
558+        key = self._make_key(key, version=version)
559         try:
560             val = self._cache.incr(key, delta)
561 
562@@ -76,7 +88,8 @@
563 
564         return val
565 
566-    def decr(self, key, delta=1):
567+    def decr(self, key, delta=1, version=None):
568+        key = self._make_key(key, version=version)
569         try:
570             val = self._cache.decr(key, delta)
571 
572@@ -89,16 +102,18 @@
573             raise ValueError("Key '%s' not found" % key)
574         return val
575 
576-    def set_many(self, data, timeout=0):
577+    def set_many(self, data, timeout=0, version=None):
578         safe_data = {}
579         for key, value in data.items():
580+            key = self._make_key(key, version=version)
581             if isinstance(value, unicode):
582                 value = value.encode('utf-8')
583-            safe_data[smart_str(key)] = value
584+            safe_data[key] = value
585         self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout))
586 
587-    def delete_many(self, keys):
588-        self._cache.delete_multi(map(smart_str, keys))
589+    def delete_many(self, keys, version=None):
590+        l = lambda x: self._make_key(x, version=version)
591+        self._cache.delete_multi(map(l, keys))
592 
593     def clear(self):
594         self._cache.flush_all()
595diff -r 9aed8047e568 docs/ref/settings.txt
596--- a/docs/ref/settings.txt     Thu Nov 18 01:53:38 2010 +0000
597+++ b/docs/ref/settings.txt     Fri Nov 19 22:34:44 2010 +0800
598@@ -136,6 +136,25 @@
599 
600 The cache backend to use. See :doc:`/topics/cache`.
601 
602+.. setting:: CACHE_KEY_FUNCTION
603+
604+CACHE_KEY_FUNCTION
605+------------------
606+
607+Default: ``None``
608+
609+A string containing a dotted path to a function that defines how to
610+compose a prefix, version and key into a final cache key. The default
611+implementation is equivalent to the function::
612+
613+    def make_key(key, key_prefix, version):
614+        return ':'.join([key_prefix, str(version), smart_str(key)])
615+
616+You may use any key function you want, as long as it adheres to the same
617+prototype.
618+
619+See the :ref:`cache documentation <cache_key_transformation>` for more information.
620+
621 .. setting:: CACHE_MIDDLEWARE_ANONYMOUS_ONLY
622 
623 CACHE_MIDDLEWARE_ANONYMOUS_ONLY
624@@ -172,6 +191,30 @@
625 The default number of seconds to cache a page when the caching middleware or
626 ``cache_page()`` decorator is used.
627 
628+.. setting:: CACHE_PREFIX
629+
630+CACHE_PREFIX
631+------------
632+
633+Default: ``''`` (Empty string)
634+
635+A string that will be automatically included (prepended by default) to
636+all cache keys used by the Django server.
637+
638+See the :ref:`cache documentation <cache_key_prefixing>` for more information.
639+
640+.. setting:: CACHE_VERSION
641+
642+CACHE_VERSION
643+-------------
644+
645+Default: ``1``
646+
647+The default version number for cache keys generated by the Django server.
648+
649+See the :ref:`cache documentation <cache_versioning>` for more information.
650+
651+
652 .. setting:: CSRF_COOKIE_DOMAIN
653 
654 CSRF_COOKIE_DOMAIN
655diff -r 9aed8047e568 docs/releases/1.3.txt
656--- a/docs/releases/1.3.txt     Thu Nov 18 01:53:38 2010 +0000
657+++ b/docs/releases/1.3.txt     Fri Nov 19 22:34:44 2010 +0800
658@@ -155,6 +155,9 @@
659       :meth:`~django.test.client.Client.assertNumQueries` -- making it
660       easier to test the database activity associated with a view.
661 
662+    * :ref:`Versioning <cache_versioning>`, :ref:`site-wide prefixing
663+      <cache_key_prefixing>` and :ref:`transformation
664+      <cache_key_transformation>` has been added to the cache API.
665 
666 .. _backwards-incompatible-changes-1.3:
667 
668diff -r 9aed8047e568 docs/topics/cache.txt
669--- a/docs/topics/cache.txt     Thu Nov 18 01:53:38 2010 +0000
670+++ b/docs/topics/cache.txt     Fri Nov 19 22:34:44 2010 +0800
671@@ -643,6 +643,102 @@
672     However, if the backend doesn't natively provide an increment/decrement
673     operation, it will be implemented using a two-step retrieve/update.
674 
675+.. _cache_key_prefixing:
676+
677+Cache key prefixing
678+-------------------
679+
680+.. versionadded:: 1.3
681+
682+If you are sharing a cache instance between servers, or between your
683+production and development environments, it's possible for data cached
684+by one server to be used by another server. If the format of cached
685+data is different between servers, this can lead to some very hard to
686+diagnose problems.
687+
688+To prevent this, Django provides the ability to prefix all cache keys
689+used by a server. When a particular cache key is saved or retrieved,
690+Django will automatically prefix the cache key with the value of the
691+:setting:`CACHE_KEY_PREFIX` setting.
692+
693+By ensuring each Django instance has a different
694+:setting:`CACHE_KEY_PREFIX`, you can ensure that there will be no
695+collisions in cache values.
696+
697+.. _cache_versioning:
698+
699+Cache versioning
700+----------------
701+
702+.. versionadded:: 1.3
703+
704+When you change running code that uses cached values, you may need to
705+purge any existing cached values. The easiest way to do this is to
706+flush the entire cache, but this may result in the loss of cache
707+values that are still valid and useful, but are lost because the
708+entire cache is purged.
709+
710+Django provides a better way to target individual cache values.
711+Django's cache framework has a system-wide version identifier,
712+specified using the :setting:`CACHE_VERSION` setting. The value of
713+this setting is automatically combined with the cache prefix and the
714+user-provided cache key to obtain the final cache key.
715+
716+By default, any key request will automatically include the site
717+default cache key version. However, the primitive cache functions all
718+include a ``version`` argument, so you can specify a particular cache
719+key version to set or get. For example::
720+
721+    # Set version 2 of a cache key
722+    >>> cache.set('my_key', 'hello world!', version=2)
723+    # Get the default version (assuming version=1)
724+    >>> cache.get('my_key')
725+    None
726+    # Get version 2 of the same key
727+    >>> cache.get('my_key', version=2)
728+    'hello world!'
729+
730+The value of a specific key can be incremented and decremented using
731+the :func:`incr_version()` and :func:`decr_version()` methods. This
732+enables specific keys to be bumped to a new version, leaving other
733+keys unaffected. Continuing our previous example::
734+
735+    # Increment the version of 'my_key'
736+    >>> cache.incr_version('my_key')
737+    # The default version still isn't available
738+    >>> cache.get('my_key')
739+    None
740+    # Version 2 isn't available, either
741+    >>> cache.get('my_key', version=2)
742+    None
743+    # But version 3 *is* availble
744+    >>> cache.get('my_key', version=3)
745+    'hello world!'
746+
747+.. _cache_key_transformation:
748+
749+Cache key transformation
750+------------------------
751+
752+.. versionadded:: 1.3
753+
754+As described in the previous two sections, the cache key provided by a
755+user is not used verbatim -- it is combined with the cache prefix and
756+key version to provide a final cache key. By default, the three parts
757+are joined using colons to produce a final string::
758+
759+    def make_key(key, key_prefix, version):
760+        return ':'.join([key_prefix, str(version), smart_str(key)])
761+
762+If you want to combine the parts in different ways, or apply other
763+processing to the final key (e.g., taking a hash digest of the key
764+parts), you can provide a custom key function.
765+
766+The setting :setting:`CACHE_KEY_FUNCTION` specifies a dotted-path to
767+a function matching the prototype of :func:`make_key()` above. If
768+provided, this custom key function will be used instead of the default
769+key combining function.
770+
771 Cache key warnings
772 ------------------
773 
774diff -r 9aed8047e568 tests/regressiontests/cache/tests.py
775--- a/tests/regressiontests/cache/tests.py      Thu Nov 18 01:53:38 2010 +0000
776+++ b/tests/regressiontests/cache/tests.py      Fri Nov 19 22:34:44 2010 +0800
777@@ -145,11 +145,21 @@
778         "clear does nothing for the dummy cache backend"
779         self.cache.clear()
780 
781+    def test_incr_version(self):
782+        "Dummy cache versions can't be incremented"
783+        self.cache.set('answer', 42)
784+        self.assertRaises(ValueError, self.cache.incr_version, 'answer')
785+        self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist')
786+
787+    def test_decr_version(self):
788+        "Dummy cache versions can't be decremented"
789+        self.cache.set('answer', 42)
790+        self.assertRaises(ValueError, self.cache.decr_version, 'answer')
791+        self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist')
792+
793 
794 class BaseCacheTests(object):
795     # A common set of tests to apply to all cache backends
796-    def tearDown(self):
797-        self.cache.clear()
798 
799     def test_simple(self):
800         # Simple cache set/get works
801@@ -163,6 +173,18 @@
802         self.assertEqual(result, False)
803         self.assertEqual(self.cache.get("addkey1"), "value")
804 
805+    def test_prefix(self):
806+        # Test for same cache key conflicts between shared backend
807+        self.cache.set('somekey', 'value')
808+
809+        # should not be set in the prefixed cache
810+        self.assertFalse(self.prefix_cache.has_key('somekey'))
811+
812+        self.prefix_cache.set('somekey', 'value2')
813+
814+        self.assertEqual(self.cache.get('somekey'), 'value')
815+        self.assertEqual(self.prefix_cache.get('somekey'), 'value2')
816+
817     def test_non_existent(self):
818         # Non-existent cache keys return as None/default
819         # get with non-existent keys
820@@ -376,6 +398,13 @@
821         with more liberal key rules. Refs #6447.
822 
823         """
824+        # mimic custom ``make_key`` method being defined since the default will
825+        # never show the below warnings
826+        def func(key, *args):
827+            return key
828+
829+        old_func = self.cache.key_func
830+        self.cache.key_func = func
831         # On Python 2.6+ we could use the catch_warnings context
832         # manager to test this warning nicely. Since we can't do that
833         # yet, the cleanest option is to temporarily ask for
834@@ -390,6 +419,242 @@
835             self.assertRaises(CacheKeyWarning, self.cache.set, 'a' * 251, 'value')
836         finally:
837             restore_warnings_state(_warnings_state)
838+            self.cache.key_func = old_func
839+
840+    def test_cache_versioning_get_set(self):
841+        # set, using default version = 1
842+        self.cache.set('answer1', 42)
843+        self.assertEqual(self.cache.get('answer1'), 42)
844+        self.assertEqual(self.cache.get('answer1', version=1), 42)
845+        self.assertEqual(self.cache.get('answer1', version=2), None)
846+
847+        self.assertEqual(self.v2_cache.get('answer1'), None)
848+        # print '---'
849+        # print 'c1',self.cache._cache
850+        # print 'v2',self.v2_cache._cache
851+        self.assertEqual(self.v2_cache.get('answer1', version=1), 42)
852+        self.assertEqual(self.v2_cache.get('answer1', version=2), None)
853+
854+        # set, default version = 1, but manually override version = 2
855+        self.cache.set('answer2', 42, version=2)
856+        self.assertEqual(self.cache.get('answer2'), None)
857+        self.assertEqual(self.cache.get('answer2', version=1), None)
858+        self.assertEqual(self.cache.get('answer2', version=2), 42)
859+
860+        self.assertEqual(self.v2_cache.get('answer2'), 42)
861+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
862+        self.assertEqual(self.v2_cache.get('answer2', version=2), 42)
863+
864+        # v2 set, using default version = 2
865+        self.v2_cache.set('answer3', 42)
866+        self.assertEqual(self.cache.get('answer3'), None)
867+        self.assertEqual(self.cache.get('answer3', version=1), None)
868+        self.assertEqual(self.cache.get('answer3', version=2), 42)
869+
870+        self.assertEqual(self.v2_cache.get('answer3'), 42)
871+        self.assertEqual(self.v2_cache.get('answer3', version=1), None)
872+        self.assertEqual(self.v2_cache.get('answer3', version=2), 42)
873+
874+        # v2 set, default version = 2, but manually override version = 1
875+        self.v2_cache.set('answer4', 42, version=1)
876+        self.assertEqual(self.cache.get('answer4'), 42)
877+        self.assertEqual(self.cache.get('answer4', version=1), 42)
878+        self.assertEqual(self.cache.get('answer4', version=2), None)
879+
880+        self.assertEqual(self.v2_cache.get('answer4'), None)
881+        self.assertEqual(self.v2_cache.get('answer4', version=1), 42)
882+        self.assertEqual(self.v2_cache.get('answer4', version=2), None)
883+
884+    def test_cache_versioning_has_key(self):
885+        self.cache.set('answer1', 42)
886+
887+        # has_key
888+        self.assertTrue(self.cache.has_key('answer1'))
889+        self.assertTrue(self.cache.has_key('answer1', version=1))
890+        self.assertFalse(self.cache.has_key('answer1', version=2))
891+
892+        self.assertFalse(self.v2_cache.has_key('answer1'))
893+        self.assertTrue(self.v2_cache.has_key('answer1', version=1))
894+        self.assertFalse(self.v2_cache.has_key('answer1', version=2))
895+
896+    def test_cache_versioning_delete(self):
897+        self.cache.set('answer1', 37, version=1)
898+        self.cache.set('answer1', 42, version=2)
899+        self.cache.delete('answer1')
900+        self.assertEqual(self.cache.get('answer1', version=1), None)
901+        self.assertEqual(self.cache.get('answer1', version=2), 42)
902+
903+        self.cache.set('answer2', 37, version=1)
904+        self.cache.set('answer2', 42, version=2)
905+        self.cache.delete('answer2', version=2)
906+        self.assertEqual(self.cache.get('answer2', version=1), 37)
907+        self.assertEqual(self.cache.get('answer2', version=2), None)
908+
909+        self.cache.set('answer3', 37, version=1)
910+        self.cache.set('answer3', 42, version=2)
911+        self.v2_cache.delete('answer3')
912+        self.assertEqual(self.cache.get('answer3', version=1), 37)
913+        self.assertEqual(self.cache.get('answer3', version=2), None)
914+
915+        self.cache.set('answer4', 37, version=1)
916+        self.cache.set('answer4', 42, version=2)
917+        self.v2_cache.delete('answer4', version=1)
918+        self.assertEqual(self.cache.get('answer4', version=1), None)
919+        self.assertEqual(self.cache.get('answer4', version=2), 42)
920+
921+    def test_cache_versioning_incr_decr(self):
922+        self.cache.set('answer1', 37, version=1)
923+        self.cache.set('answer1', 42, version=2)
924+        self.cache.incr('answer1')
925+        self.assertEqual(self.cache.get('answer1', version=1), 38)
926+        self.assertEqual(self.cache.get('answer1', version=2), 42)
927+        self.cache.decr('answer1')
928+        self.assertEqual(self.cache.get('answer1', version=1), 37)
929+        self.assertEqual(self.cache.get('answer1', version=2), 42)
930+
931+        self.cache.set('answer2', 37, version=1)
932+        self.cache.set('answer2', 42, version=2)
933+        self.cache.incr('answer2', version=2)
934+        self.assertEqual(self.cache.get('answer2', version=1), 37)
935+        self.assertEqual(self.cache.get('answer2', version=2), 43)
936+        self.cache.decr('answer2', version=2)
937+        self.assertEqual(self.cache.get('answer2', version=1), 37)
938+        self.assertEqual(self.cache.get('answer2', version=2), 42)
939+
940+        self.cache.set('answer3', 37, version=1)
941+        self.cache.set('answer3', 42, version=2)
942+        self.v2_cache.incr('answer3')
943+        self.assertEqual(self.cache.get('answer3', version=1), 37)
944+        self.assertEqual(self.cache.get('answer3', version=2), 43)
945+        self.v2_cache.decr('answer3')
946+        self.assertEqual(self.cache.get('answer3', version=1), 37)
947+        self.assertEqual(self.cache.get('answer3', version=2), 42)
948+
949+        self.cache.set('answer4', 37, version=1)
950+        self.cache.set('answer4', 42, version=2)
951+        self.v2_cache.incr('answer4', version=1)
952+        self.assertEqual(self.cache.get('answer4', version=1), 38)
953+        self.assertEqual(self.cache.get('answer4', version=2), 42)
954+        self.v2_cache.decr('answer4', version=1)
955+        self.assertEqual(self.cache.get('answer4', version=1), 37)
956+        self.assertEqual(self.cache.get('answer4', version=2), 42)
957+
958+    def test_cache_versioning_get_set_many(self):
959+        # set, using default version = 1
960+        self.cache.set_many({'ford1': 37, 'arthur1': 42})
961+        self.assertEqual(self.cache.get_many(['ford1','arthur1']),
962+                         {'ford1': 37, 'arthur1': 42})
963+        self.assertEqual(self.cache.get_many(['ford1','arthur1'], version=1),
964+                         {'ford1': 37, 'arthur1': 42})
965+        self.assertEqual(self.cache.get_many(['ford1','arthur1'], version=2), {})
966+
967+        self.assertEqual(self.v2_cache.get_many(['ford1','arthur1']), {})
968+        self.assertEqual(self.v2_cache.get_many(['ford1','arthur1'], version=1),
969+                         {'ford1': 37, 'arthur1': 42})
970+        self.assertEqual(self.v2_cache.get_many(['ford1','arthur1'], version=2), {})
971+
972+        # set, default version = 1, but manually override version = 2
973+        self.cache.set_many({'ford2': 37, 'arthur2': 42}, version=2)
974+        self.assertEqual(self.cache.get_many(['ford2','arthur2']), {})
975+        self.assertEqual(self.cache.get_many(['ford2','arthur2'], version=1), {})
976+        self.assertEqual(self.cache.get_many(['ford2','arthur2'], version=2),
977+                         {'ford2': 37, 'arthur2': 42})
978+
979+        self.assertEqual(self.v2_cache.get_many(['ford2','arthur2']),
980+                         {'ford2': 37, 'arthur2': 42})
981+        self.assertEqual(self.v2_cache.get_many(['ford2','arthur2'], version=1), {})
982+        self.assertEqual(self.v2_cache.get_many(['ford2','arthur2'], version=2),
983+                         {'ford2': 37, 'arthur2': 42})
984+
985+        # v2 set, using default version = 2
986+        self.v2_cache.set_many({'ford3': 37, 'arthur3': 42})
987+        self.assertEqual(self.cache.get_many(['ford3','arthur3']), {})
988+        self.assertEqual(self.cache.get_many(['ford3','arthur3'], version=1), {})
989+        self.assertEqual(self.cache.get_many(['ford3','arthur3'], version=2),
990+                         {'ford3': 37, 'arthur3': 42})
991+
992+        self.assertEqual(self.v2_cache.get_many(['ford3','arthur3']),
993+                         {'ford3': 37, 'arthur3': 42})
994+        self.assertEqual(self.v2_cache.get_many(['ford3','arthur3'], version=1), {})
995+        self.assertEqual(self.v2_cache.get_many(['ford3','arthur3'], version=2),
996+                         {'ford3': 37, 'arthur3': 42})
997+
998+        # v2 set, default version = 2, but manually override version = 1
999+        self.v2_cache.set_many({'ford4': 37, 'arthur4': 42}, version=1)
1000+        self.assertEqual(self.cache.get_many(['ford4','arthur4']),
1001+                         {'ford4': 37, 'arthur4': 42})
1002+        self.assertEqual(self.cache.get_many(['ford4','arthur4'], version=1),
1003+                         {'ford4': 37, 'arthur4': 42})
1004+        self.assertEqual(self.cache.get_many(['ford4','arthur4'], version=2), {})
1005+
1006+        self.assertEqual(self.v2_cache.get_many(['ford4','arthur4']), {})
1007+        self.assertEqual(self.v2_cache.get_many(['ford4','arthur4'], version=1),
1008+                         {'ford4': 37, 'arthur4': 42})
1009+        self.assertEqual(self.v2_cache.get_many(['ford4','arthur4'], version=2), {})
1010+
1011+    def test_incr_version(self):
1012+        self.cache.set('answer', 42, version=2)
1013+        self.assertEqual(self.cache.get('answer'), None)
1014+        self.assertEqual(self.cache.get('answer', version=1), None)
1015+        self.assertEqual(self.cache.get('answer', version=2), 42)
1016+        self.assertEqual(self.cache.get('answer', version=3), None)
1017+
1018+        self.assertEqual(self.cache.incr_version('answer', version=2), 3)
1019+        self.assertEqual(self.cache.get('answer'), None)
1020+        self.assertEqual(self.cache.get('answer', version=1), None)
1021+        self.assertEqual(self.cache.get('answer', version=2), None)
1022+        self.assertEqual(self.cache.get('answer', version=3), 42)
1023+
1024+        self.v2_cache.set('answer2', 42)
1025+        self.assertEqual(self.v2_cache.get('answer2'), 42)
1026+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
1027+        self.assertEqual(self.v2_cache.get('answer2', version=2), 42)
1028+        self.assertEqual(self.v2_cache.get('answer2', version=3), None)
1029+
1030+        self.assertEqual(self.v2_cache.incr_version('answer2'), 3)
1031+        self.assertEqual(self.v2_cache.get('answer2'), None)
1032+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
1033+        self.assertEqual(self.v2_cache.get('answer2', version=2), None)
1034+        self.assertEqual(self.v2_cache.get('answer2', version=3), 42)
1035+
1036+        self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist')
1037+
1038+    def test_decr_version(self):
1039+        self.cache.set('answer', 42, version=2)
1040+        self.assertEqual(self.cache.get('answer'), None)
1041+        self.assertEqual(self.cache.get('answer', version=1), None)
1042+        self.assertEqual(self.cache.get('answer', version=2), 42)
1043+
1044+        self.assertEqual(self.cache.decr_version('answer', version=2), 1)
1045+        self.assertEqual(self.cache.get('answer'), 42)
1046+        self.assertEqual(self.cache.get('answer', version=1), 42)
1047+        self.assertEqual(self.cache.get('answer', version=2), None)
1048+
1049+        self.v2_cache.set('answer2', 42)
1050+        self.assertEqual(self.v2_cache.get('answer2'), 42)
1051+        self.assertEqual(self.v2_cache.get('answer2', version=1), None)
1052+        self.assertEqual(self.v2_cache.get('answer2', version=2), 42)
1053+
1054+        self.assertEqual(self.v2_cache.decr_version('answer2'), 1)
1055+        self.assertEqual(self.v2_cache.get('answer2'), None)
1056+        self.assertEqual(self.v2_cache.get('answer2', version=1), 42)
1057+        self.assertEqual(self.v2_cache.get('answer2', version=2), None)
1058+
1059+        self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist', version=2)
1060+
1061+    def test_custom_key_func(self):
1062+        # Two caches with different key functions aren't visible to each other
1063+        self.cache.set('answer1', 42)
1064+        self.assertEqual(self.cache.get('answer1'), 42)
1065+        self.assertEqual(self.custom_key_cache.get('answer1'), None)
1066+
1067+        self.custom_key_cache.set('answer2', 42)
1068+        self.assertEqual(self.cache.get('answer2'), None)
1069+        self.assertEqual(self.custom_key_cache.get('answer2'), 42)
1070+
1071+def custom_key_func(key, key_prefix, version):
1072+    "A customized cache key function"
1073+    return 'CUSTOM-' + '-'.join([key_prefix, str(version), key])
1074 
1075 class DBCacheTests(unittest.TestCase, BaseCacheTests):
1076     def setUp(self):
1077@@ -397,6 +662,9 @@
1078         self._table_name = 'test cache table'
1079         management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False)
1080         self.cache = get_cache('db://%s?max_entries=30' % self._table_name)
1081+        self.prefix_cache = get_cache('db://%s' % self._table_name, key_prefix='cacheprefix')
1082+        self.v2_cache = get_cache('db://%s' % self._table_name, version=2)
1083+        self.custom_key_cache = get_cache('db://%s' % self._table_name, key_func=custom_key_func)
1084 
1085     def tearDown(self):
1086         from django.db import connection
1087@@ -413,6 +681,20 @@
1088 class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
1089     def setUp(self):
1090         self.cache = get_cache('locmem://?max_entries=30')
1091+        self.prefix_cache = get_cache('locmem://', key_prefix='cacheprefix')
1092+        self.v2_cache = get_cache('locmem://', version=2)
1093+        self.custom_key_cache = get_cache('locmem://?max_entries=30', key_func=custom_key_func)
1094+
1095+        # LocMem requires a hack to make the other caches
1096+        # share a data store with the 'normal' cache.
1097+        self.prefix_cache._cache = self.cache._cache
1098+        self.prefix_cache._expire_info = self.cache._expire_info
1099+
1100+        self.v2_cache._cache = self.cache._cache
1101+        self.v2_cache._expire_info = self.cache._expire_info
1102+
1103+        self.custom_key_cache._cache = self.cache._cache
1104+        self.custom_key_cache._expire_info = self.cache._expire_info
1105 
1106     def test_cull(self):
1107         self.perform_cull_test(50, 29)
1108@@ -428,6 +710,15 @@
1109 class MemcachedCacheTests(unittest.TestCase, BaseCacheTests):
1110     def setUp(self):
1111         self.cache = get_cache(settings.CACHE_BACKEND)
1112+        self.prefix_cache = get_cache(settings.CACHE_BACKEND, key_prefix='cacheprefix')
1113+        self.v2_cache = get_cache(settings.CACHE_BACKEND, version=2)
1114+        self.custom_key_cache = get_cache(settings.CACHE_BACKEND, key_func=custom_key_func)
1115+
1116+    def tearDown(self):
1117+        self.cache.clear()
1118+        self.prefix_cache.clear()
1119+        self.v2_cache.clear()
1120+        self.custom_key_cache.clear()
1121 
1122     def test_invalid_keys(self):
1123         """
1124@@ -439,10 +730,16 @@
1125         that a generic exception of some kind is raised.
1126 
1127         """
1128+        _warnings_state = get_warnings_state()
1129+        warnings.simplefilter("ignore", CacheKeyWarning)
1130+
1131         # memcached does not allow whitespace or control characters in keys
1132         self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value')
1133         # memcached limits key length to 250
1134         self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value')
1135+
1136+        restore_warnings_state(_warnings_state)
1137+
1138 MemcachedCacheTests = unittest.skipUnless(settings.CACHE_BACKEND.startswith('memcached://'), "memcached not available")(MemcachedCacheTests)
1139 
1140 class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
1141@@ -452,11 +749,21 @@
1142     def setUp(self):
1143         self.dirname = tempfile.mkdtemp()
1144         self.cache = get_cache('file://%s?max_entries=30' % self.dirname)
1145+        self.prefix_cache = get_cache('file://%s' % self.dirname, key_prefix='cacheprefix')
1146+        self.v2_cache = get_cache('file://%s' % self.dirname, version=2)
1147+        self.custom_key_cache = get_cache('file://%s' % self.dirname, key_func=custom_key_func)
1148+
1149+    def tearDown(self):
1150+        self.cache.clear()
1151+        self.prefix_cache.clear()
1152+        self.v2_cache.clear()
1153+        self.custom_key_cache.clear()
1154 
1155     def test_hashing(self):
1156         """Test that keys are hashed into subdirectories correctly"""
1157         self.cache.set("foo", "bar")
1158-        keyhash = md5_constructor("foo").hexdigest()
1159+        key = self.cache._make_key("foo")
1160+        keyhash = md5_constructor(key).hexdigest()
1161         keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
1162         self.assert_(os.path.exists(keypath))
1163 
1164@@ -465,7 +772,8 @@
1165         Make sure that the created subdirectories are correctly removed when empty.
1166         """
1167         self.cache.set("foo", "bar")
1168-        keyhash = md5_constructor("foo").hexdigest()
1169+        key = self.cache._make_key("foo")
1170+        keyhash = md5_constructor(key).hexdigest()
1171         keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
1172         self.assert_(os.path.exists(keypath))
1173 
1174@@ -475,7 +783,7 @@
1175         self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath))))
1176 
1177     def test_cull(self):
1178-        self.perform_cull_test(50, 28)
1179+        self.perform_cull_test(50, 29)
1180 
1181 class CustomCacheKeyValidationTests(unittest.TestCase):
1182     """
1183@@ -498,16 +806,16 @@
1184 
1185     def setUp(self):
1186         self.path = '/cache/test/'
1187-        self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
1188-        self.old_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
1189+        self.old_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
1190+        self.old_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
1191         self.orig_use_i18n = settings.USE_I18N
1192         settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix'
1193         settings.CACHE_MIDDLEWARE_SECONDS = 1
1194         settings.USE_I18N = False
1195 
1196     def tearDown(self):
1197-        settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_settings_key_prefix
1198-        settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds
1199+        settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_cache_middleware_key_prefix
1200+        settings.CACHE_MIDDLEWARE_SECONDS = self.old_cache_middleware_seconds
1201         settings.USE_I18N = self.orig_use_i18n
1202 
1203     def _get_request(self, path, method='GET'):
1204@@ -561,6 +869,16 @@
1205         learn_cache_key(request, response)
1206         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.HEAD.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
1207 
1208+class PrefixedCacheUtils(CacheUtils):
1209+    def setUp(self):
1210+        super(PrefixedCacheUtils, self).setUp()
1211+        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
1212+        settings.CACHE_KEY_PREFIX = 'cacheprefix'
1213+
1214+    def tearDown(self):
1215+        super(PrefixedCacheUtils, self).tearDown()
1216+        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
1217+
1218 class CacheHEADTest(unittest.TestCase):
1219 
1220     def setUp(self):
1221@@ -714,5 +1032,15 @@
1222         get_cache_data = FetchFromCacheMiddleware().process_request(request)
1223         self.assertEqual(get_cache_data.content, es_message)
1224 
1225+class PrefixedCacheI18nTest(CacheI18nTest):
1226+    def setUp(self):
1227+        super(PrefixedCacheI18nTest, self).setUp()
1228+        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
1229+        settings.CACHE_KEY_PREFIX = 'cacheprefix'
1230+
1231+    def tearDown(self):
1232+        super(PrefixedCacheI18nTest, self).tearDown()
1233+        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
1234+
1235 if __name__ == '__main__':
1236     unittest.main()