diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py index 85ce67f1cc..86555af45b 100644 --- a/django/templatetags/cache.py +++ b/django/templatetags/cache.py @@ -1,17 +1,24 @@ from __future__ import unicode_literals +from django.core.cache import get_cache, InvalidCacheBackendError from django.core.cache.utils import make_template_fragment_key from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist -from django.core.cache import cache register = Library() +try: + default_cache = get_cache('template_fragments') +except InvalidCacheBackendError: + from django.core.cache import cache as default_cache + + class CacheNode(Node): - def __init__(self, nodelist, expire_time_var, fragment_name, vary_on): + def __init__(self, nodelist, expire_time_var, fragment_name, vary_on, cache_name): self.nodelist = nodelist self.expire_time_var = expire_time_var self.fragment_name = fragment_name self.vary_on = vary_on + self.cache_name = cache_name def render(self, context): try: @@ -22,6 +29,17 @@ class CacheNode(Node): expire_time = int(expire_time) except (ValueError, TypeError): raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) + if self.cache_name: + try: + cache_name = self.cache_name.resolve(context) + except VariableDoesNotExist: + raise TemplateSyntaxError('"cache" tag got an unknown variable: %r' % self.cache_name.var) + try: + cache = get_cache(cache_name) + except InvalidCacheBackendError: + raise TemplateSyntaxError('Invalid cache name specified for cache tag: %r' % cache_name) + else: + cache = default_cache vary_on = [var.resolve(context) for var in self.vary_on] cache_key = make_template_fragment_key(self.fragment_name, vary_on) value = cache.get(cache_key) @@ -50,6 +68,9 @@ def do_cache(parser, token): .. some expensive processing .. {% endcache %} + Optionally the cache to use may be specified thus:: + + {% cache .... using="cachename" %} Each unique set of arguments will result in a unique cache entry. """ nodelist = parser.parse(('endcache',)) @@ -57,7 +78,14 @@ def do_cache(parser, token): tokens = token.split_contents() if len(tokens) < 3: raise TemplateSyntaxError("'%r' tag requires at least 2 arguments." % tokens[0]) + if len(tokens) > 3 and tokens[-1].startswith('using='): + cache_name = parser.compile_filter(tokens[-1][len('using='):]) + tokens = tokens[:-1] + else: + cache_name = None return CacheNode(nodelist, parser.compile_filter(tokens[1]), tokens[2], # fragment_name can't be a variable. - [parser.compile_filter(t) for t in tokens[3:]]) + [parser.compile_filter(t) for t in tokens[3:]], + cache_name, + ) diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index e71fc5b65c..7ce13066bf 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -396,6 +396,11 @@ Templates ` ``datetime`` instances performing the expected rendering. +* The :ttag:`cache` tag will now try to use the cache called + "template_fragments" if it exists and fall back to using the default cache + otherwise. It also now accepts an optional ``using`` keyword argument to + control which cache it uses. + Requests ^^^^^^^^ diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 4e37eeb3f4..f61e3ffbd3 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -652,6 +652,19 @@ equivalent: This feature is useful in avoiding repetition in templates. You can set the timeout in a variable, in one place, and just reuse that value. +.. versionadded:: 1.7 + + By default, the cache tag will try to use the cache called + "template_fragments". If no such cache exists, it will fall back to using + the default cache. You may select an alternate cache backend to use with + the ``using`` keyword argument, which must be the last argument to the tag. + +.. code-block:: html+django + + {% cache 300 local-thing ... using="localcache" %} + +It is considered an error to specify a cache name that is not configured. + .. function:: django.core.cache.utils.make_template_fragment_key(fragment_name, vary_on=None) If you want to obtain the cache key used for a cached fragment, you can use diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py index 8c52052811..a7990d0163 100644 --- a/tests/template_tests/tests.py +++ b/tests/template_tests/tests.py @@ -478,6 +478,42 @@ class TemplateRegressionTests(TestCase): cachenode = t.nodelist[1] self.assertEqual(cachenode.fragment_name, 'regression_20130') + @override_settings(CACHES={ + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'default', + }, + 'template_fragments': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'fragments', + }, + }) + def test_cache_fragment_cache(self): + """ + When a cache called "template_fragments" is present, the cache tag + will use it in preference to 'default' + """ + t1 = Template('{% load cache %}{% cache 1 fragment %}foo{% endcache %}') + t2 = Template('{% load cache %}{% cache 1 fragment using="default" %}bar{% endcache %}') + + ctx = Context() + o1 = t1.render(ctx) + o2 = t2.render(ctx) + + self.assertEqual(o1, 'foo') + self.assertNotEqual(o1, o2) + + def test_cache_missing_backend(self): + """ + When a cache that doesn't exist is specified, the cache tag will + raise a TemplateSyntaxError + '""" + t = Template('{% load cache %}{% cache 1 backend using="unknown" %}bar{% endcache %}') + + ctx = Context() + with self.assertRaises(TemplateSyntaxError): + t.render(ctx) + def test_ifchanged_render_once(self): """ Test for ticket #19890. The content of ifchanged template tag was rendered twice.""" @@ -1736,7 +1772,6 @@ class TemplateTests(TransRealMixin, TestCase): # Test whitespace in filter arguments 'cache18': ('{% load cache custom %}{% cache 2|noop:"x y" cache18 %}cache18{% endcache %}', {}, 'cache18'), - ### AUTOESCAPE TAG ############################################## 'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"), 'autoescape-tag02': ("{% autoescape off %}{{ first }}{% endautoescape %}", {"first": "hello"}, "hello"),