diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 2714bfb..ac06000 100644
a
|
b
|
SESSION_FILE_PATH = None # Directory to store ses
|
428 | 428 | # The cache backend to use. See the docstring in django.core.cache for the |
429 | 429 | # possible values. |
430 | 430 | CACHE_BACKEND = 'locmem://' |
| 431 | CACHE_KEY_PREFIX = '' |
431 | 432 | CACHE_MIDDLEWARE_KEY_PREFIX = '' |
432 | 433 | CACHE_MIDDLEWARE_SECONDS = 600 |
433 | 434 | |
diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py
index e49590b..62d5b35 100644
a
|
b
|
def parse_backend_uri(backend_uri):
|
56 | 56 | |
57 | 57 | return scheme, host, params |
58 | 58 | |
59 | | def get_cache(backend_uri): |
| 59 | def get_cache(backend_uri, key_prefix=''): |
| 60 | if not key_prefix: |
| 61 | key_prefix = settings.CACHE_KEY_PREFIX |
60 | 62 | scheme, host, params = parse_backend_uri(backend_uri) |
61 | 63 | if scheme in BACKENDS: |
62 | 64 | name = 'django.core.cache.backends.%s' % BACKENDS[scheme] |
63 | 65 | else: |
64 | 66 | name = scheme |
65 | 67 | module = importlib.import_module(name) |
66 | | return module.CacheClass(host, params) |
| 68 | return module.CacheClass(host, params, key_prefix) |
67 | 69 | |
68 | 70 | cache = get_cache(settings.CACHE_BACKEND) |
69 | 71 | |
70 | | # Some caches -- pythont-memcached in particular -- need to do a cleanup at the |
| 72 | # Some caches -- python-memcached in particular -- need to do a cleanup at the |
71 | 73 | # end of a request cycle. If the cache provides a close() method, wire it up |
72 | 74 | # here. |
73 | 75 | if hasattr(cache, 'close'): |
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index 83dd461..c911ce2 100644
a
|
b
|
|
3 | 3 | import warnings |
4 | 4 | |
5 | 5 | from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning |
| 6 | from django.utils.encoding import smart_str |
6 | 7 | |
7 | 8 | class InvalidCacheBackendError(ImproperlyConfigured): |
8 | 9 | pass |
… |
… |
class CacheKeyWarning(DjangoRuntimeWarning):
|
14 | 15 | MEMCACHE_MAX_KEY_LENGTH = 250 |
15 | 16 | |
16 | 17 | class BaseCache(object): |
17 | | def __init__(self, params): |
| 18 | def __init__(self, params, key_prefix=''): |
18 | 19 | timeout = params.get('timeout', 300) |
19 | 20 | try: |
20 | 21 | timeout = int(timeout) |
21 | 22 | except (ValueError, TypeError): |
22 | 23 | timeout = 300 |
23 | 24 | self.default_timeout = timeout |
| 25 | self.key_prefix = smart_str(key_prefix) |
| 26 | |
| 27 | def make_key(self, key): |
| 28 | """Constructs the key used by all other methods. By default it prepends |
| 29 | the `key_prefix'. In cache backend subclasses this can be overriden to |
| 30 | provide custom key making behavior. |
| 31 | """ |
| 32 | return self.key_prefix + smart_str(key) |
24 | 33 | |
25 | 34 | def add(self, key, value, timeout=None): |
26 | 35 | """ |
diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
index c4429c8..3731df8 100644
a
|
b
|
class Options(object):
|
26 | 26 | self.proxy = False |
27 | 27 | |
28 | 28 | class CacheClass(BaseCache): |
29 | | def __init__(self, table, params): |
30 | | BaseCache.__init__(self, params) |
| 29 | def __init__(self, table, params, key_prefix=''): |
| 30 | BaseCache.__init__(self, params, key_prefix) |
31 | 31 | self._table = table |
32 | 32 | |
33 | 33 | class CacheEntry(object): |
… |
… |
class CacheClass(BaseCache):
|
46 | 46 | self._cull_frequency = 3 |
47 | 47 | |
48 | 48 | def get(self, key, default=None): |
| 49 | key = self.make_key(key) |
49 | 50 | self.validate_key(key) |
50 | 51 | db = router.db_for_read(self.cache_model_class) |
51 | 52 | table = connections[db].ops.quote_name(self._table) |
… |
… |
class CacheClass(BaseCache):
|
66 | 67 | return pickle.loads(base64.decodestring(value)) |
67 | 68 | |
68 | 69 | def set(self, key, value, timeout=None): |
| 70 | key = self.make_key(key) |
69 | 71 | self.validate_key(key) |
70 | 72 | self._base_set('set', key, value, timeout) |
71 | 73 | |
72 | 74 | def add(self, key, value, timeout=None): |
| 75 | key = self.make_key(key) |
73 | 76 | self.validate_key(key) |
74 | 77 | return self._base_set('add', key, value, timeout) |
75 | 78 | |
… |
… |
class CacheClass(BaseCache):
|
106 | 109 | return True |
107 | 110 | |
108 | 111 | def delete(self, key): |
| 112 | key = self.make_key(key) |
109 | 113 | self.validate_key(key) |
110 | 114 | db = router.db_for_write(self.cache_model_class) |
111 | 115 | table = connections[db].ops.quote_name(self._table) |
… |
… |
class CacheClass(BaseCache):
|
115 | 119 | transaction.commit_unless_managed(using=db) |
116 | 120 | |
117 | 121 | def has_key(self, key): |
| 122 | key = self.make_key(key) |
118 | 123 | self.validate_key(key) |
119 | 124 | db = router.db_for_read(self.cache_model_class) |
120 | 125 | table = connections[db].ops.quote_name(self._table) |
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index 46e69f3..322143c 100644
a
|
b
|
from django.core.cache.backends.base import BaseCache
|
12 | 12 | from django.utils.hashcompat import md5_constructor |
13 | 13 | |
14 | 14 | class CacheClass(BaseCache): |
15 | | def __init__(self, dir, params): |
16 | | BaseCache.__init__(self, params) |
| 15 | def __init__(self, dir, params, key_prefix=''): |
| 16 | BaseCache.__init__(self, params, key_prefix) |
17 | 17 | |
18 | 18 | max_entries = params.get('max_entries', 300) |
19 | 19 | try: |
… |
… |
class CacheClass(BaseCache):
|
32 | 32 | self._createdir() |
33 | 33 | |
34 | 34 | def add(self, key, value, timeout=None): |
| 35 | key = self.make_key(key) |
35 | 36 | self.validate_key(key) |
36 | 37 | if self.has_key(key): |
37 | 38 | return False |
… |
… |
class CacheClass(BaseCache):
|
40 | 41 | return True |
41 | 42 | |
42 | 43 | def get(self, key, default=None): |
| 44 | key = self.make_key(key) |
43 | 45 | self.validate_key(key) |
44 | 46 | fname = self._key_to_file(key) |
45 | 47 | try: |
… |
… |
class CacheClass(BaseCache):
|
58 | 60 | return default |
59 | 61 | |
60 | 62 | def set(self, key, value, timeout=None): |
| 63 | key = self.make_key(key) |
61 | 64 | self.validate_key(key) |
62 | 65 | fname = self._key_to_file(key) |
63 | 66 | dirname = os.path.dirname(fname) |
… |
… |
class CacheClass(BaseCache):
|
82 | 85 | pass |
83 | 86 | |
84 | 87 | def delete(self, key): |
| 88 | key = self.make_key(key) |
85 | 89 | self.validate_key(key) |
86 | 90 | try: |
87 | 91 | self._delete(self._key_to_file(key)) |
… |
… |
class CacheClass(BaseCache):
|
99 | 103 | pass |
100 | 104 | |
101 | 105 | def has_key(self, key): |
| 106 | key = self.make_key(key) |
102 | 107 | self.validate_key(key) |
103 | 108 | fname = self._key_to_file(key) |
104 | 109 | try: |
… |
… |
class CacheClass(BaseCache):
|
153 | 158 | Thus, a cache key of "foo" gets turnned into a file named |
154 | 159 | ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``. |
155 | 160 | """ |
156 | | path = md5_constructor(key.encode('utf-8')).hexdigest() |
| 161 | key = self.make_key(key) |
| 162 | path = md5_constructor(key).hexdigest() |
157 | 163 | path = os.path.join(path[:2], path[2:4], path[4:]) |
158 | 164 | return os.path.join(self._dir, path) |
159 | 165 | |
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
index fe33d33..2f7933a 100644
a
|
b
|
from django.core.cache.backends.base import BaseCache
|
10 | 10 | from django.utils.synch import RWLock |
11 | 11 | |
12 | 12 | class CacheClass(BaseCache): |
13 | | def __init__(self, _, params): |
14 | | BaseCache.__init__(self, params) |
| 13 | def __init__(self, _, params, key_prefix=''): |
| 14 | BaseCache.__init__(self, params, key_prefix) |
15 | 15 | self._cache = {} |
16 | 16 | self._expire_info = {} |
17 | 17 | |
… |
… |
class CacheClass(BaseCache):
|
30 | 30 | self._lock = RWLock() |
31 | 31 | |
32 | 32 | def add(self, key, value, timeout=None): |
| 33 | key = self.make_key(key) |
33 | 34 | self.validate_key(key) |
34 | 35 | self._lock.writer_enters() |
35 | 36 | try: |
… |
… |
class CacheClass(BaseCache):
|
45 | 46 | self._lock.writer_leaves() |
46 | 47 | |
47 | 48 | def get(self, key, default=None): |
| 49 | key = self.make_key(key) |
48 | 50 | self.validate_key(key) |
49 | 51 | self._lock.reader_enters() |
50 | 52 | try: |
… |
… |
class CacheClass(BaseCache):
|
78 | 80 | self._expire_info[key] = time.time() + timeout |
79 | 81 | |
80 | 82 | def set(self, key, value, timeout=None): |
| 83 | key = self.make_key(key) |
81 | 84 | self.validate_key(key) |
82 | 85 | self._lock.writer_enters() |
83 | 86 | # Python 2.4 doesn't allow combined try-except-finally blocks. |
… |
… |
class CacheClass(BaseCache):
|
90 | 93 | self._lock.writer_leaves() |
91 | 94 | |
92 | 95 | def has_key(self, key): |
| 96 | key = self.make_key(key) |
93 | 97 | self.validate_key(key) |
94 | 98 | self._lock.reader_enters() |
95 | 99 | try: |
… |
… |
class CacheClass(BaseCache):
|
131 | 135 | pass |
132 | 136 | |
133 | 137 | def delete(self, key): |
| 138 | key = self.make_key(key) |
134 | 139 | self.validate_key(key) |
135 | 140 | self._lock.writer_enters() |
136 | 141 | try: |
diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py
index 7d6b5b3..e321193 100644
a
|
b
|
|
3 | 3 | import time |
4 | 4 | |
5 | 5 | from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError |
6 | | from django.utils.encoding import smart_unicode, smart_str |
7 | 6 | |
8 | 7 | try: |
9 | 8 | import cmemcache as memcache |
… |
… |
except ImportError:
|
19 | 18 | raise InvalidCacheBackendError("Memcached cache backend requires either the 'memcache' or 'cmemcache' library") |
20 | 19 | |
21 | 20 | class CacheClass(BaseCache): |
22 | | def __init__(self, server, params): |
23 | | BaseCache.__init__(self, params) |
| 21 | def __init__(self, server, params, key_prefix): |
| 22 | BaseCache.__init__(self, params, key_prefix) |
24 | 23 | self._cache = memcache.Client(server.split(';')) |
25 | 24 | |
26 | 25 | def _get_memcache_timeout(self, timeout): |
… |
… |
class CacheClass(BaseCache):
|
40 | 39 | return timeout |
41 | 40 | |
42 | 41 | def add(self, key, value, timeout=0): |
| 42 | key = self.make_key(key) |
43 | 43 | if isinstance(value, unicode): |
44 | 44 | value = value.encode('utf-8') |
45 | | return self._cache.add(smart_str(key), value, self._get_memcache_timeout(timeout)) |
| 45 | return self._cache.add(key, value, self._get_memcache_timeout(timeout)) |
46 | 46 | |
47 | 47 | def get(self, key, default=None): |
48 | | val = self._cache.get(smart_str(key)) |
| 48 | key = self.make_key(key) |
| 49 | val = self._cache.get(key) |
49 | 50 | if val is None: |
50 | 51 | return default |
51 | 52 | return val |
52 | 53 | |
53 | 54 | def set(self, key, value, timeout=0): |
54 | | self._cache.set(smart_str(key), value, self._get_memcache_timeout(timeout)) |
| 55 | key = self.make_key(key) |
| 56 | self._cache.set(key, value, self._get_memcache_timeout(timeout)) |
55 | 57 | |
56 | 58 | def delete(self, key): |
57 | | self._cache.delete(smart_str(key)) |
| 59 | key = self.make_key(key) |
| 60 | self._cache.delete(key) |
58 | 61 | |
59 | 62 | def get_many(self, keys): |
60 | | return self._cache.get_multi(map(smart_str,keys)) |
| 63 | return self._cache.get_multi(map(self.make_key,keys)) |
61 | 64 | |
62 | 65 | def close(self, **kwargs): |
63 | 66 | self._cache.disconnect_all() |
64 | 67 | |
65 | 68 | def incr(self, key, delta=1): |
| 69 | key = self.make_key(key) |
66 | 70 | try: |
67 | 71 | val = self._cache.incr(key, delta) |
68 | 72 | |
… |
… |
class CacheClass(BaseCache):
|
77 | 81 | return val |
78 | 82 | |
79 | 83 | def decr(self, key, delta=1): |
| 84 | key = self.make_key(key) |
80 | 85 | try: |
81 | 86 | val = self._cache.decr(key, delta) |
82 | 87 | |
… |
… |
class CacheClass(BaseCache):
|
92 | 97 | def set_many(self, data, timeout=0): |
93 | 98 | safe_data = {} |
94 | 99 | for key, value in data.items(): |
| 100 | key = self.make_key(key) |
95 | 101 | if isinstance(value, unicode): |
96 | 102 | value = value.encode('utf-8') |
97 | | safe_data[smart_str(key)] = value |
| 103 | safe_data[key] = value |
98 | 104 | self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout)) |
99 | 105 | |
100 | 106 | def delete_many(self, keys): |
101 | | self._cache.delete_multi(map(smart_str, keys)) |
| 107 | self._cache.delete_multi(map(self.make_key, keys)) |
102 | 108 | |
103 | 109 | def clear(self): |
104 | 110 | self._cache.flush_all() |
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index 5797199..7fe9fec 100644
a
|
b
|
instance, to do this for the ``locmem`` backend, put this code in a module::
|
680 | 680 | ...and use the dotted Python path to this module as the scheme portion of your |
681 | 681 | :setting:`CACHE_BACKEND`. |
682 | 682 | |
| 683 | CACHE_KEY_PREFIX |
| 684 | ---------------- |
| 685 | |
| 686 | It is a common occurence to have a shared cache instance running on your development |
| 687 | or production server for use across multiple projects, i.e. all sites are pointing |
| 688 | to the memcached instance running on port 11211. As a result, cache key conflicts |
| 689 | may arise which could result in data from one site being used by another. |
| 690 | |
| 691 | To alleviate this, CACHE_KEY_PREFIX can be set. This will be prepended to all keys |
| 692 | that are used with the cache backend, transparently:: |
| 693 | |
| 694 | # in settings, CACHE_KEY_PREFIX = 'myproject_' |
| 695 | |
| 696 | >>> cache.set('my_key', 'hello world!') # set with key 'myproject_my_key' |
| 697 | >>> cache.get('my_key') # retrieved with key 'myproject_my_key' |
| 698 | 'hello world!' |
| 699 | |
| 700 | Of course, for sites that _do_ share content, simply set the CACHE_KEY_PREFIX for |
| 701 | both sites to the same value. The default value for CACHE_KEY_PREFIX is the empty |
| 702 | string ``''``. |
| 703 | |
| 704 | .. note:: |
| 705 | |
| 706 | This does *not* conflict with the CACHE_MIDDLEWARE_KEY_PREFIX and can be used |
| 707 | in conjunction with it. CACHE_KEY_PREFIX acts as a global prefix for a |
| 708 | particular cache instance, therefore it will be prepended to the |
| 709 | CACHE_MIDDLEWARE_KEY_PREFIX transparently. |
| 710 | |
683 | 711 | Upstream caches |
684 | 712 | =============== |
685 | 713 | |
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index 1e0a404..5647acc 100644
a
|
b
|
class DummyCacheTests(unittest.TestCase):
|
144 | 144 | "clear does nothing for the dummy cache backend" |
145 | 145 | self.cache.clear() |
146 | 146 | |
147 | | |
148 | 147 | class BaseCacheTests(object): |
149 | 148 | # A common set of tests to apply to all cache backends |
150 | 149 | def tearDown(self): |
… |
… |
class BaseCacheTests(object):
|
162 | 161 | self.assertEqual(result, False) |
163 | 162 | self.assertEqual(self.cache.get("addkey1"), "value") |
164 | 163 | |
| 164 | def test_prefix(self): |
| 165 | # Test for same cache key conflicts between shared backend |
| 166 | self.cache.set('somekey', 'value') |
| 167 | |
| 168 | # should not be set in the prefixed cache |
| 169 | self.assertFalse(self.pfx_cache.has_key('somekey')) |
| 170 | |
| 171 | self.pfx_cache.set('somekey', 'value2') |
| 172 | |
| 173 | self.assertEqual(self.cache.get('somekey'), 'value') |
| 174 | self.assertEqual(self.pfx_cache.get('somekey'), 'value2') |
| 175 | |
165 | 176 | def test_non_existent(self): |
166 | 177 | # Non-existent cache keys return as None/default |
167 | 178 | # get with non-existent keys |
… |
… |
class DBCacheTests(unittest.TestCase, BaseCacheTests):
|
400 | 411 | self._table_name = 'test cache table' |
401 | 412 | management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False) |
402 | 413 | self.cache = get_cache('db://%s?max_entries=30' % self._table_name) |
| 414 | self.pfx_cache = get_cache('db://%s' % self._table_name, 'cacheprefix') |
403 | 415 | |
404 | 416 | def tearDown(self): |
405 | 417 | from django.db import connection |
… |
… |
class DBCacheTests(unittest.TestCase, BaseCacheTests):
|
412 | 424 | class LocMemCacheTests(unittest.TestCase, BaseCacheTests): |
413 | 425 | def setUp(self): |
414 | 426 | self.cache = get_cache('locmem://?max_entries=30') |
| 427 | self.pfx_cache = get_cache('locmem://', 'cacheprefix') |
415 | 428 | |
416 | 429 | def test_cull(self): |
417 | 430 | self.perform_cull_test(50, 29) |
… |
… |
if settings.CACHE_BACKEND.startswith('memcached://'):
|
424 | 437 | class MemcachedCacheTests(unittest.TestCase, BaseCacheTests): |
425 | 438 | def setUp(self): |
426 | 439 | self.cache = get_cache(settings.CACHE_BACKEND) |
| 440 | self.pfx_cache = get_cache(settings.CACHE_BACKEND, 'cacheprefix') |
427 | 441 | |
428 | 442 | def test_invalid_keys(self): |
429 | 443 | """ |
… |
… |
class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
|
448 | 462 | def setUp(self): |
449 | 463 | self.dirname = tempfile.mkdtemp() |
450 | 464 | self.cache = get_cache('file://%s?max_entries=30' % self.dirname) |
| 465 | self.pfx_cache = get_cache('file://%s' % self.dirname, 'cacheprefix') |
451 | 466 | |
452 | 467 | def test_hashing(self): |
453 | 468 | """Test that keys are hashed into subdirectories correctly""" |
… |
… |
class CacheUtils(unittest.TestCase):
|
494 | 509 | |
495 | 510 | def setUp(self): |
496 | 511 | self.path = '/cache/test/' |
497 | | self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX |
498 | | self.old_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS |
| 512 | self.old_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX |
| 513 | self.old_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS |
499 | 514 | self.orig_use_i18n = settings.USE_I18N |
500 | 515 | settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix' |
501 | 516 | settings.CACHE_MIDDLEWARE_SECONDS = 1 |
502 | 517 | settings.USE_I18N = False |
503 | 518 | |
504 | 519 | def tearDown(self): |
505 | | settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_settings_key_prefix |
506 | | settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds |
| 520 | settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_cache_middleware_key_prefix |
| 521 | settings.CACHE_MIDDLEWARE_SECONDS = self.old_cache_middleware_seconds |
507 | 522 | settings.USE_I18N = self.orig_use_i18n |
508 | 523 | |
509 | 524 | def _get_request(self, path): |
… |
… |
class CacheUtils(unittest.TestCase):
|
556 | 571 | learn_cache_key(request, response) |
557 | 572 | self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e') |
558 | 573 | |
| 574 | class PrefixedCacheUtils(CacheUtils): |
| 575 | def setUp(self): |
| 576 | super(PrefixedCacheUtils, self).setUp() |
| 577 | self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX |
| 578 | settings.CACHE_KEY_PREFIX = 'cacheprefix' |
| 579 | |
| 580 | def tearDown(self): |
| 581 | super(PrefixedCacheUtils, self).tearDown() |
| 582 | settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix |
| 583 | |
559 | 584 | class CacheI18nTest(unittest.TestCase): |
560 | 585 | |
561 | 586 | def setUp(self): |
… |
… |
class CacheI18nTest(unittest.TestCase):
|
651 | 676 | get_cache_data = FetchFromCacheMiddleware().process_request(request) |
652 | 677 | self.assertEqual(get_cache_data.content, es_message) |
653 | 678 | |
| 679 | class PrefixedCacheI18nTest(CacheI18nTest): |
| 680 | def setUp(self): |
| 681 | super(PrefixedCacheI18nTest, self).setUp() |
| 682 | self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX |
| 683 | settings.CACHE_KEY_PREFIX = 'cacheprefix' |
| 684 | |
| 685 | def tearDown(self): |
| 686 | super(PrefixedCacheI18nTest, self).tearDown() |
| 687 | settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix |
| 688 | |
654 | 689 | if __name__ == '__main__': |
655 | 690 | unittest.main() |