Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #14908 -- Added a 'takes_context' argument to simple_tag. Thank…

…s to Julien Phalip for driving the issue and providing the final patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14987 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 314fabc930a1bb361ca06e9c948bb726ad8df99a 1 parent 7adffae
@freakboy3742 freakboy3742 authored
View
43 django/template/base.py
@@ -872,21 +872,40 @@ def filter_function(self, func):
self.filters[getattr(func, "_decorated_function", func).__name__] = func
return func
- def simple_tag(self,func):
- params, xx, xxx, defaults = getargspec(func)
+ def simple_tag(self, func=None, takes_context=None):
+ def dec(func):
+ params, xx, xxx, defaults = getargspec(func)
+ if takes_context:
+ if params[0] == 'context':
+ params = params[1:]
+ else:
+ raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
- class SimpleNode(Node):
- def __init__(self, vars_to_resolve):
- self.vars_to_resolve = map(Variable, vars_to_resolve)
+ class SimpleNode(Node):
+ def __init__(self, vars_to_resolve):
+ self.vars_to_resolve = map(Variable, vars_to_resolve)
- def render(self, context):
- resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
- return func(*resolved_vars)
+ def render(self, context):
+ resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
+ if takes_context:
+ func_args = [context] + resolved_vars
+ else:
+ func_args = resolved_vars
+ return func(*func_args)
- compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
- compile_func.__doc__ = func.__doc__
- self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
- return func
+ compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
+ compile_func.__doc__ = func.__doc__
+ self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
+ return func
+
+ if func is None:
+ # @register.simple_tag(...)
+ return dec
+ elif callable(func):
+ # @register.simple_tag
+ return dec(func)
+ else:
+ raise TemplateSyntaxError("Invalid arguments provided to simple_tag")
def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
def dec(func):
View
24 docs/howto/custom-template-tags.txt
@@ -669,9 +669,27 @@ A couple of things to note about the ``simple_tag`` helper function:
* If the argument was a template variable, our function is passed the
current value of the variable, not the variable itself.
-When your template tag does not need access to the current context, writing a
-function to work with the input values and using the ``simple_tag`` helper is
-the easiest way to create a new tag.
+.. versionadded:: 1.3
+
+If your template tag needs to access the current context, you can use the
+``takes_context`` argument when registering your tag::
+
+ # The first argument *must* be called "context" here.
+ def current_time(context, format_string):
+ timezone = context['timezone']
+ return your_get_current_time_method(timezone)
+
+ register.simple_tag(takes_context=True)(current_time)
+
+Or, using decorator syntax::
+
+ @register.simple_tag(takes_context=True)
+ def current_time(context, format_string):
+ timezone = context['timezone']
+ return your_get_current_time_method(timezone)
+
+For more information on how the ``takes_context`` option works, see the section
+on `inclusion tags`_.
.. _howto-custom-template-tags-inclusion-tags:
View
8 docs/releases/1.3.txt
@@ -184,12 +184,16 @@ requests. These include:
* Support for _HTTPOnly cookies.
- * mail_admins() and mail_managers() now support easily attaching
- HTML content to messages.
+ * :meth:`mail_admins()` and :meth:`mail_managers()` now support
+ easily attaching HTML content to messages.
* Error emails now include more of the detail and formatting of
the debug server error page.
+ * :meth:`simple_tag` now accepts a :attr:`takes_context` argument,
+ making it easier to write simple template tags that require
+ access to template context.
+
.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly
.. _backwards-incompatible-changes-1.3:
View
47 tests/regressiontests/templates/custom.py
@@ -1,11 +1,54 @@
from django import template
from django.utils.unittest import TestCase
+from templatetags import custom
-
-class CustomTests(TestCase):
+class CustomFilterTests(TestCase):
def test_filter(self):
t = template.Template("{% load custom %}{{ string|trim:5 }}")
self.assertEqual(
t.render(template.Context({"string": "abcdefghijklmnopqrstuvwxyz"})),
u"abcde"
)
+
+
+class CustomTagTests(TestCase):
+ def verify_tag(self, tag, name):
+ self.assertEquals(tag.__name__, name)
+ self.assertEquals(tag.__doc__, 'Expected %s __doc__' % name)
+ self.assertEquals(tag.__dict__['anything'], 'Expected %s __dict__' % name)
+
+ def test_simple_tags(self):
+ c = template.Context({'value': 42})
+
+ t = template.Template('{% load custom %}{% no_params %}')
+ self.assertEquals(t.render(c), u'no_params - Expected result')
+
+ t = template.Template('{% load custom %}{% one_param 37 %}')
+ self.assertEquals(t.render(c), u'one_param - Expected result: 37')
+
+ t = template.Template('{% load custom %}{% explicit_no_context 37 %}')
+ self.assertEquals(t.render(c), u'explicit_no_context - Expected result: 37')
+
+ t = template.Template('{% load custom %}{% no_params_with_context %}')
+ self.assertEquals(t.render(c), u'no_params_with_context - Expected result (context value: 42)')
+
+ t = template.Template('{% load custom %}{% params_and_context 37 %}')
+ self.assertEquals(t.render(c), u'params_and_context - Expected result (context value: 42): 37')
+
+ def test_simple_tag_registration(self):
+ # Test that the decorators preserve the decorated function's docstring, name and attributes.
+ self.verify_tag(custom.no_params, 'no_params')
+ self.verify_tag(custom.one_param, 'one_param')
+ self.verify_tag(custom.explicit_no_context, 'explicit_no_context')
+ self.verify_tag(custom.no_params_with_context, 'no_params_with_context')
+ self.verify_tag(custom.params_and_context, 'params_and_context')
+
+ def test_simple_tag_missing_context(self):
+ # That the 'context' parameter must be present when takes_context is True
+ def a_simple_tag_without_parameters(arg):
+ """Expected __doc__"""
+ return "Expected result"
+
+ register = template.Library()
+ decorator = register.simple_tag(takes_context=True)
+ self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
View
30 tests/regressiontests/templates/templatetags/custom.py
@@ -9,3 +9,33 @@ def trim(value, num):
register.filter(trim)
+@register.simple_tag
+def no_params():
+ """Expected no_params __doc__"""
+ return "no_params - Expected result"
+no_params.anything = "Expected no_params __dict__"
+
+@register.simple_tag
+def one_param(arg):
+ """Expected one_param __doc__"""
+ return "one_param - Expected result: %s" % arg
+one_param.anything = "Expected one_param __dict__"
+
+@register.simple_tag(takes_context=False)
+def explicit_no_context(arg):
+ """Expected explicit_no_context __doc__"""
+ return "explicit_no_context - Expected result: %s" % arg
+explicit_no_context.anything = "Expected explicit_no_context __dict__"
+
+@register.simple_tag(takes_context=True)
+def no_params_with_context(context):
+ """Expected no_params_with_context __doc__"""
+ return "no_params_with_context - Expected result (context value: %s)" % context['value']
+no_params_with_context.anything = "Expected no_params_with_context __dict__"
+
+@register.simple_tag(takes_context=True)
+def params_and_context(context, arg):
+ """Expected params_and_context __doc__"""
+ return "params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
+params_and_context.anything = "Expected params_and_context __dict__"
+
View
2  tests/regressiontests/templates/tests.py
@@ -23,7 +23,7 @@
from django.utils.tzinfo import LocalTimezone
from context import ContextTests
-from custom import CustomTests
+from custom import CustomTagTests, CustomFilterTests
from parser import ParserTests
from unicode import UnicodeTests
from nodelist import NodelistTest
Please sign in to comment.
Something went wrong with that request. Please try again.