diff --git a/AUTHORS b/AUTHORS index 4b5fc953b8..62dbeb1161 100644 --- a/AUTHORS +++ b/AUTHORS @@ -34,6 +34,7 @@ answer newbie questions, and generally made Django that much better: Marty Alchin Ahmad Alhashemi Ahmad Al-Ibrahim + Antoni Aloy Daniel Alves Barbosa de Oliveira Vaz AgarFu Dagur Páll Ammendrup @@ -301,6 +302,7 @@ answer newbie questions, and generally made Django that much better: Martin Mahner Matt McClanahan Stanislaus Madueke + Yann Malet Frantisek Malina Mike Malone Martin Maney diff --git a/django/utils/cache.py b/django/utils/cache.py index 3c40e17e90..9828556057 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -19,16 +19,13 @@ An example: i18n middleware would need to distinguish caches by the import re import time -try: - set -except NameError: - from sets import Set as set # Python 2.3 fallback from django.conf import settings from django.core.cache import cache from django.utils.encoding import smart_str, iri_to_uri from django.utils.http import http_date from django.utils.hashcompat import md5_constructor +from django.utils import translation from django.http import HttpRequest cc_delim_re = re.compile(r'\s*,\s*') @@ -145,13 +142,20 @@ def _generate_cache_key(request, headerlist, key_prefix): if value is not None: ctx.update(value) path = md5_constructor(iri_to_uri(request.path)) - return 'views.decorators.cache.cache_page.%s.%s.%s' % ( - key_prefix, path.hexdigest(), ctx.hexdigest()) + cache_key = 'views.decorators.cache.cache_page.%s.%s.%s' % ( + key_prefix, path.hexdigest(), ctx.hexdigest()) + if settings.USE_I18N: + cache_key += '.%s' % translation.get_language() + return cache_key def _generate_cache_header_key(key_prefix, request): """Returns a cache key for the header cache.""" path = md5_constructor(iri_to_uri(request.path)) - return 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, path.hexdigest()) + cache_key = 'views.decorators.cache.cache_header.%s.%s' % ( + key_prefix, path.hexdigest()) + if settings.USE_I18N: + cache_key += ".%s" % translation.get_language() + return cache_key def get_cache_key(request, key_prefix=None): """ diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index f8c9870462..dc450ae1fc 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -320,6 +320,13 @@ time, rather than ``CACHE_MIDDLEWARE_SECONDS``. Using the decorators in the ``never_cache`` decorator). See the `using other headers`__ section for more on these decorators. +.. versionadded:: 1.2 + +If :setting:`USE_I18N` is set to ``True`` then the generated cache key will +include the name of the currently active :term:`language`. +This allows you to easily cache multilingual sites without having to create +the cache key yourself. + __ `Controlling cache: Using other headers`_ The per-view cache diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 5d8d039cf5..83422b3a97 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -14,6 +14,8 @@ from django.core import management from django.core.cache import get_cache from django.core.cache.backends.base import InvalidCacheBackendError from django.http import HttpResponse, HttpRequest +from django.middleware.cache import FetchFromCacheMiddleware, UpdateCacheMiddleware +from django.utils import translation from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key from django.utils.hashcompat import md5_constructor from regressiontests.cache.models import Poll, expensive_calculation @@ -401,12 +403,15 @@ class CacheUtils(unittest.TestCase): self.path = '/cache/test/' self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX self.old_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.USE_I18N = self.orig_use_i18n def _get_request(self, path): request = HttpRequest() @@ -458,5 +463,101 @@ class CacheUtils(unittest.TestCase): learn_cache_key(request, response) self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e') +class CacheI18nTest(unittest.TestCase): + + def setUp(self): + self.orig_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS + self.orig_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX + self.orig_cache_backend = settings.CACHE_BACKEND + self.orig_use_i18n = settings.USE_I18N + self.orig_languages = settings.LANGUAGES + settings.LANGUAGES = ( + ('en', 'English'), + ('es', 'Spanish'), + ) + settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix' + settings.CACHE_MIDDLEWARE_SECONDS + self.path = '/cache/test/' + + def tearDown(self): + settings.CACHE_MIDDLEWARE_SECONDS = self.orig_cache_middleware_seconds + settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.orig_cache_middleware_key_prefix + settings.CACHE_BACKEND = self.orig_cache_backend + settings.USE_I18N = self.orig_use_i18n + settings.LANGUAGES = self.orig_languages + translation.deactivate() + + def _get_request(self): + request = HttpRequest() + request.META = { + 'SERVER_NAME': 'testserver', + 'SERVER_PORT': 80, + } + request.path = request.path_info = self.path + return request + + def _get_request_cache(self): + request = HttpRequest() + request.META = { + 'SERVER_NAME': 'testserver', + 'SERVER_PORT': 80, + } + request.path = request.path_info = self.path + request._cache_update_cache = True + request.method = 'GET' + request.session = {} + return request + + def test_cache_key_i18n(self): + settings.USE_I18N = True + request = self._get_request() + lang = translation.get_language() + response = HttpResponse() + key = learn_cache_key(request, response) + self.assertTrue(key.endswith(lang), "Cache keys should include the language name when i18n is active") + key2 = get_cache_key(request) + self.assertEqual(key, key2) + + def test_cache_key_no_i18n (self): + settings.USE_I18N = False + request = self._get_request() + lang = translation.get_language() + response = HttpResponse() + key = learn_cache_key(request, response) + self.assertFalse(key.endswith(lang), "Cache keys shouldn't include the language name when i18n is inactive") + + def test_middleware(self): + def set_cache(request, lang, msg): + translation.activate(lang) + response = HttpResponse() + response.content= msg + return UpdateCacheMiddleware().process_response(request, response) + + settings.CACHE_MIDDLEWARE_SECONDS = 60 + settings.CACHE_MIDDLEWARE_KEY_PREFIX="test" + settings.CACHE_BACKEND='locmem:///' + settings.USE_I18N = True + en_message ="Hello world!" + es_message ="Hola mundo!" + + request = self._get_request_cache() + set_cache(request, 'en', en_message) + get_cache_data = FetchFromCacheMiddleware().process_request(request) + # Check that we can recover the cache + self.assertNotEqual(get_cache_data.content, None) + self.assertEqual(en_message, get_cache_data.content) + # change the session language and set content + request = self._get_request_cache() + set_cache(request, 'es', es_message) + # change again the language + translation.activate('en') + # retrieve the content from cache + get_cache_data = FetchFromCacheMiddleware().process_request(request) + self.assertEqual(get_cache_data.content, en_message) + # change again the language + translation.activate('es') + get_cache_data = FetchFromCacheMiddleware().process_request(request) + self.assertEqual(get_cache_data.content, es_message) + if __name__ == '__main__': unittest.main()