Permalink
Browse files

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.
  • Loading branch information...
1 parent b9cc610 commit 99edbe0e279166db82caaf545ef92d5446a6a07e @oinopion oinopion committed with HonzaKral Feb 24, 2013
Showing with 60 additions and 7 deletions.
  1. +2 −0 AUTHORS
  2. +15 −0 django/core/cache/utils.py
  3. +3 −7 django/templatetags/cache.py
  4. +17 −0 docs/topics/cache.txt
  5. +23 −0 tests/regressiontests/cache/tests.py
View
@@ -382,6 +382,7 @@ answer newbie questions, and generally made Django that much better:
Paul McLanahan <paul@mclanahan.net>
Tobias McNulty <http://www.caktusgroup.com/blog>
Andrews Medina <andrewsmedina@gmail.com>
+ Christoph Mędrela <chris.medrela@gmail.com>
Zain Memon
Christian Metts
michal@plovarna.cz
@@ -418,6 +419,7 @@ answer newbie questions, and generally made Django that much better:
Christian Oudard <christian.oudard@gmail.com>
oggie rob <oz.robharvey@gmail.com>
oggy <ognjen.maric@gmail.com>
+ Tomek Paczkowski <tomek@hauru.eu>
Jens Page
Jay Parlar <parlar@gmail.com>
Carlos Eduardo de Paula <carlosedp@gmail.com>
View
@@ -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())
@@ -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 @@ def render(self, context):
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)
View
@@ -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
=======================
@@ -20,6 +20,7 @@
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 @@ def test_admin(self):
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')
+

2 comments on commit 99edbe0

Owner

jezdez commented on 99edbe0 Dec 28, 2014

I don't understand why this wasn't put into django.utils.cache. Mind elaborating?

Member

HonzaKral replied Dec 28, 2014

To me it made sense at the time because django.utils.cache only contains utils connected to request/response cycle where this seems unrelated. Also the existing module contains mostly internal APIs whereas this was meant explicitly for public consumption.

I can see the point you are raising though and don't feel strongly either way.

Please sign in to comment.