From 6fbf653aa5aa15867e03a5c60bda599d5c99123c Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sun, 21 Oct 2007 15:48:40 +0000 Subject: [PATCH] Fixed #1065 -- Added a "cache" template tag. Thanks, Ian Maurer and, particularly, Nick Lane. git-svn-id: http://code.djangoproject.com/svn/django/trunk@6580 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/templatetags/cache.py | 57 ++++++++++++++++++++++++ docs/cache.txt | 30 +++++++++++++ tests/regressiontests/templates/tests.py | 14 ++++++ 3 files changed, 101 insertions(+) create mode 100644 django/templatetags/cache.py diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py new file mode 100644 index 0000000000..845be36cd8 --- /dev/null +++ b/django/templatetags/cache.py @@ -0,0 +1,57 @@ +from django.template import Library, Node, TemplateSyntaxError +from django.template import resolve_variable +from django.core.cache import cache +from django.utils.encoding import force_unicode + +register = Library() + +class CacheNode(Node): + def __init__(self, nodelist, expire_time, fragment_name, vary_on): + self.nodelist = nodelist + self.expire_time = expire_time + self.fragment_name = fragment_name + self.vary_on = vary_on + + def render(self, context): + # Build a unicode key for this fragment and all vary-on's. + cache_key = u':'.join([self.fragment_name] + \ + [force_unicode(resolve_variable(var, context)) for var in self.vary_on]) + value = cache.get(cache_key) + if value is None: + value = self.nodelist.render(context) + cache.set(cache_key, value, self.expire_time) + return value + +def do_cache(parser, token): + """ + This will cache the contents of a template fragment for a given amount + of time. + + Usage:: + + {% load cache %} + {% cache [expire_time] [fragment_name] %} + .. some expensive processing .. + {% endcache %} + + This tag also supports varying by a list of arguments:: + + {% load cache %} + {% cache [expire_time] [fragment_name] [var1] [var2] .. %} + .. some expensive processing .. + {% endcache %} + + Each unique set of arguments will result in a unique cache entry. + """ + nodelist = parser.parse(('endcache',)) + parser.delete_first_token() + tokens = token.contents.split() + if len(tokens) < 3: + raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) + try: + expire_time = int(tokens[1]) + except ValueError: + raise TemplateSyntaxError(u"First argument to '%r' must be an integer (got '%s')." % (tokens[0], tokens[1])) + return CacheNode(nodelist, expire_time, tokens[2], tokens[3:]) + +register.tag('cache', do_cache) diff --git a/docs/cache.txt b/docs/cache.txt index 20c59bfe97..3b53260c91 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -288,6 +288,36 @@ Or, using Python 2.4's decorator syntax:: above example, the result of the ``slashdot_this()`` view will be cached for 15 minutes. +Template fragment caching +========================= + +If you're after even more control, you can also cache template fragments using +the ``cache`` template tag. To give your template access to this tag, put ``{% +load cache %}`` near the top of your template. + +The ``{% cache %}`` template tag caches the contents of the block for a given +amount of time. It takes at least two arguments: the cache timeout, in +seconds, and the name to give the cache fragment. For example:: + + {% load cache %} + {% cache 500 sidebar %} + .. sidebar .. + {% endcache %} + +Sometimes you might want to cache multiple copies of a fragment depending on +some dynamic data that appears inside the fragment. For example you may want a +separate cached copy of the sidebar used in the previous example for every user +of your site. This can be easily achieved by passing additional arguments to +the ``{% cache %}`` template tag to uniquely identify the cache fragment:: + + {% load cache %} + {% cache 500 sidebar request.user.username %} + .. sidebar for logged in user .. + {% endcache %} + +If you need more than one argument to identify the fragment that's fine, simply +pass as many arguments to ``{% cache %}`` as you need! + The low-level cache API ======================= diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index a011248210..b0d4957fbb 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -805,6 +805,20 @@ class Templates(unittest.TestCase): 'url-fail01' : ('{% url %}', {}, template.TemplateSyntaxError), 'url-fail02' : ('{% url no_such_view %}', {}, ''), 'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''), + + ### CACHE TAG ###################################################### + 'cache01' : ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'), + 'cache02' : ('{% load cache %}{% cache -1 test %}cache02{% endcache %}', {}, 'cache02'), + 'cache03' : ('{% load cache %}{% cache 2 test %}cache03{% endcache %}', {}, 'cache03'), + 'cache04' : ('{% load cache %}{% cache 2 test %}cache04{% endcache %}', {}, 'cache03'), + 'cache05' : ('{% load cache %}{% cache 2 test foo %}cache05{% endcache %}', {'foo': 1}, 'cache05'), + 'cache06' : ('{% load cache %}{% cache 2 test foo %}cache06{% endcache %}', {'foo': 2}, 'cache06'), + 'cache07' : ('{% load cache %}{% cache 2 test foo %}cache06{% endcache %}', {'foo': 1}, 'cache05'), + + # Raise exception if we dont have at least 2 args, first one integer. + 'cache08' : ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError), + 'cache09' : ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError), + 'cache10' : ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError), } # Register our custom template loader.