Fixed #14908 -- Added a 'takes_context' argument to simple_tag. Thanks 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
This commit is contained in:
Russell Keith-Magee 2010-12-19 15:00:50 +00:00
parent 7adffaeaf6
commit 314fabc930
6 changed files with 134 additions and 20 deletions

View File

@ -872,21 +872,40 @@ class Library(object):
self.filters[getattr(func, "_decorated_function", func).__name__] = func self.filters[getattr(func, "_decorated_function", func).__name__] = func
return func return func
def simple_tag(self,func): def simple_tag(self, func=None, takes_context=None):
params, xx, xxx, defaults = getargspec(func) 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): class SimpleNode(Node):
def __init__(self, vars_to_resolve): def __init__(self, vars_to_resolve):
self.vars_to_resolve = map(Variable, vars_to_resolve) self.vars_to_resolve = map(Variable, vars_to_resolve)
def render(self, context): def render(self, context):
resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
return func(*resolved_vars) 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 = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
compile_func.__doc__ = func.__doc__ compile_func.__doc__ = func.__doc__
self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
return 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 inclusion_tag(self, file_name, context_class=Context, takes_context=False):
def dec(func): def dec(func):

View File

@ -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 * If the argument was a template variable, our function is passed the
current value of the variable, not the variable itself. current value of the variable, not the variable itself.
When your template tag does not need access to the current context, writing a .. versionadded:: 1.3
function to work with the input values and using the ``simple_tag`` helper is
the easiest way to create a new tag. 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: .. _howto-custom-template-tags-inclusion-tags:

View File

@ -184,12 +184,16 @@ requests. These include:
* Support for _HTTPOnly cookies. * Support for _HTTPOnly cookies.
* mail_admins() and mail_managers() now support easily attaching * :meth:`mail_admins()` and :meth:`mail_managers()` now support
HTML content to messages. easily attaching HTML content to messages.
* Error emails now include more of the detail and formatting of * Error emails now include more of the detail and formatting of
the debug server error page. 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 .. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly
.. _backwards-incompatible-changes-1.3: .. _backwards-incompatible-changes-1.3:

View File

@ -1,11 +1,54 @@
from django import template from django import template
from django.utils.unittest import TestCase from django.utils.unittest import TestCase
from templatetags import custom
class CustomFilterTests(TestCase):
class CustomTests(TestCase):
def test_filter(self): def test_filter(self):
t = template.Template("{% load custom %}{{ string|trim:5 }}") t = template.Template("{% load custom %}{{ string|trim:5 }}")
self.assertEqual( self.assertEqual(
t.render(template.Context({"string": "abcdefghijklmnopqrstuvwxyz"})), t.render(template.Context({"string": "abcdefghijklmnopqrstuvwxyz"})),
u"abcde" 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 File

@ -9,3 +9,33 @@ trim = stringfilter(trim)
register.filter(trim) 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 File

@ -23,7 +23,7 @@ from django.utils.safestring import mark_safe
from django.utils.tzinfo import LocalTimezone from django.utils.tzinfo import LocalTimezone
from context import ContextTests from context import ContextTests
from custom import CustomTests from custom import CustomTagTests, CustomFilterTests
from parser import ParserTests from parser import ParserTests
from unicode import UnicodeTests from unicode import UnicodeTests
from nodelist import NodelistTest from nodelist import NodelistTest