From 99edbe0e279166db82caaf545ef92d5446a6a07e Mon Sep 17 00:00:00 2001 From: Tomek Paczkowski Date: Sun, 24 Feb 2013 12:58:02 +0100 Subject: [PATCH] Fixed #19253 -- Extracted template cache key building logic Introduced a public function django.core.cache.utils.make_template_fragment_key Thanks @chrismedrela for fruitful cooperation. --- AUTHORS | 2 ++ django/core/cache/utils.py | 15 +++++++++++++++ django/templatetags/cache.py | 10 +++------- docs/topics/cache.txt | 17 +++++++++++++++++ tests/regressiontests/cache/tests.py | 23 +++++++++++++++++++++++ 5 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 django/core/cache/utils.py diff --git a/AUTHORS b/AUTHORS index 9089f7db90..5f9cb980c6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -382,6 +382,7 @@ answer newbie questions, and generally made Django that much better: Paul McLanahan Tobias McNulty Andrews Medina + Christoph Mędrela Zain Memon Christian Metts michal@plovarna.cz @@ -418,6 +419,7 @@ answer newbie questions, and generally made Django that much better: Christian Oudard oggie rob oggy + Tomek Paczkowski Jens Page Jay Parlar Carlos Eduardo de Paula diff --git a/django/core/cache/utils.py b/django/core/cache/utils.py new file mode 100644 index 0000000000..4310825ad4 --- /dev/null +++ b/django/core/cache/utils.py @@ -0,0 +1,15 @@ +from __future__ import absolute_import, unicode_literals + +import hashlib +from django.utils.encoding import force_bytes +from django.utils.http import urlquote + +TEMPLATE_FRAGMENT_KEY_TEMPLATE = 'template.cache.%s.%s' + + +def make_template_fragment_key(fragment_name, vary_on=None): + if vary_on is None: + vary_on = () + key = ':'.join([urlquote(var) for var in vary_on]) + args = hashlib.md5(force_bytes(key)) + return TEMPLATE_FRAGMENT_KEY_TEMPLATE % (fragment_name, args.hexdigest()) diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py index bb21b91c6a..8c061ca4c6 100644 --- a/django/templatetags/cache.py +++ b/django/templatetags/cache.py @@ -1,10 +1,8 @@ from __future__ import unicode_literals -import hashlib +from django.core.cache.utils import make_template_fragment_key from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist from django.core.cache import cache -from django.utils.encoding import force_bytes -from django.utils.http import urlquote register = Library() @@ -24,10 +22,8 @@ class CacheNode(Node): expire_time = int(expire_time) except (ValueError, TypeError): raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) - # Build a key for this fragment and all vary-on's. - key = ':'.join([urlquote(var.resolve(context)) for var in self.vary_on]) - args = hashlib.md5(force_bytes(key)) - cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest()) + 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) if value is None: value = self.nodelist.render(context) diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index e345b89dcd..6b6d57511a 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -639,6 +639,23 @@ 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. +.. 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 +``make_template_fragment_key``. ``fragment_name`` is the same as second argument +to the ``cache`` template tag; ``vary_on`` is a list of all additional arguments +passed to the tag. This function can be useful for invalidating or overwriting +a cached item, for example: + +.. code-block:: python + + >>> from django.core.cache import cache + >>> from django.core.cache.utils import make_template_fragment_key + # cache key for {% cache 500 sidebar username %} + >>> key = make_template_fragment_key('sidebar', [username]) + >>> cache.delete(key) # invalidates cached template fragment + + The low-level cache API ======================= diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 17d17f7fdd..d5d538319a 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -20,6 +20,7 @@ from django.core.cache import get_cache from django.core.cache.backends.base import (CacheKeyWarning, InvalidCacheBackendError) from django.db import router, transaction +from django.core.cache.utils import make_template_fragment_key from django.http import (HttpResponse, HttpRequest, StreamingHttpResponse, QueryDict) from django.middleware.cache import (FetchFromCacheMiddleware, @@ -1809,3 +1810,25 @@ class TestEtagWithAdmin(TestCase): response = self.client.get('/test_admin/admin/') self.assertEqual(response.status_code, 200) self.assertTrue(response.has_header('ETag')) + + +class TestMakeTemplateFragmentKey(TestCase): + def test_without_vary_on(self): + key = make_template_fragment_key('a.fragment') + self.assertEqual(key, 'template.cache.a.fragment.d41d8cd98f00b204e9800998ecf8427e') + + def test_with_one_vary_on(self): + key = make_template_fragment_key('foo', ['abc']) + self.assertEqual(key, + 'template.cache.foo.900150983cd24fb0d6963f7d28e17f72') + + def test_with_many_vary_on(self): + key = make_template_fragment_key('bar', ['abc', 'def']) + self.assertEqual(key, + 'template.cache.bar.4b35f12ab03cec09beec4c21b2d2fa88') + + def test_proper_escaping(self): + key = make_template_fragment_key('spam', ['abc:def%']) + self.assertEqual(key, + 'template.cache.spam.f27688177baec990cdf3fbd9d9c3f469') +