﻿id,summary,reporter,owner,description,type,status,component,version,severity,resolution,keywords,cc,stage,has_patch,needs_docs,needs_tests,needs_better_patch,easy,ui_ux 1400,[magic-removal] [patch] proposed update to template system,django@…,Russell Keith-Magee,"Django's template tags do not always resolve arguments consistently and as more people write custom template tags there is a risk that there will be a proliferation of arbitrary argument syntaxes, each with its own quirks. Currently, arguments are sometimes resolved as context variables ('if' tag and 'ifequal' in a slightly different way), sometimes as pure tag-specific syntax ('as', 'in'), sometimes as Python primitives (strings and numbers in 'simple_tag' tags but not strings containing whitespace), sometimes variables take filters, sometimes not... For people writing custom template tags it would be nice if there was one (simple) way of doing things. Some related tickets: #365, #566, #959, #1338 Robert's 'simple_tag' decorator introduced the nice idea of mapping template tags directly to Python callables. The existing implementation is a bit convoluted in order to allow the first argument to be either an alternative name or the decorated function. This seems a bit ""magic"": {{{ register.tag(tag_function) register.tag('another_name', tag_function) }}} How come the first argument changed its meaning? {{{ register.inclusion_tag('mytemplate')(tag_function) }}} Why does inclusion_tag have a double-call syntax (if not used as a decorator)? Proposed solution This patch to the magic-removal branch supersedes #1105 and provides a more regular mapping from 'simple_tag' to python callables. Argument resolution handles quoted strings with whitespace, boolean literals, and optional named ""keyword"" arguments. Also fixes #1338. This works as expected: {{{ @register.simple_tag def chant(message): return '


' % message }}} {{{ {% chant ""Django beats Chuck Norris."" %} }}} outputs: {{{

Django beats Chuck Norris.

}}} Optional arguments are useful for letting template authors customise output: {{{ @register.simple_tag(takes_context=True, takes_block=True) def chant(context, block, color='red', bold=False, class_='chant'): attributes = { 'style':'color:%s; font-weight:%s;' % (color, bold and 'bold' or 'normal'), 'class':class_, } return '%s

' % ( ''.join(' %s=""%s""' % (name, value) for name, value in attributes.items()), block.render(context), ) }}} {{{ {% chant bold=True class=""chuck-hidden chant"" %}Django beats Chuck Norris.{% endchant %} }}} outputs: {{{

Django beats Chuck Norris.

}}} For a real-world use case, I've got a tag that dynamically renders an arbitrary block using a truetype font with the Python Imaging Library. Optional keyword arguments control color, font, and size. See http://www.kieranholland.com/code/django/typeset-tag/ Patch summary: 1. Adds regular expression parser for template tag arguments that permits single or double quoted strings with whitespace and named 'keyword' arguments. If a keyword argument name is a Python keyword an underscore is appended. 2. Resolves literal True and False arguments to Python booleans. 3. TEMPLATE_STRING_IF_INVALID is not returned from resolve_variable - only at render time. 4. simple_tag decorator allows takes_context, and takes_block arguments (per #1105) and can either return a string to be inserted in the template or None. 5. The tag registration class has been simplified with the benefit that the inclusion_tag and simple_tag syntax is more consistent: the tag function always comes first and optional alternate names - the uncommon case - are provided by the 'name' keyword argument. Summary of backwards-incompatible changes: 1. Alternative names for tag registration come after the tag function. 2. resolve_variable raises VariableDoesNotExist exception for any unresolved variable so the caller is responsible for handling this. I have fixed all backwards-incompatible code that I could find in the magic-removal branch, but there may be a few bits and pieces remaining. All existing unit tests pass(*) and I've added a few for the tag argument tokenizer and made some minor doc fixes. * Except one I think should fail: In 'resolve_variable' if a variable is resolved to a callable and a TypeError is thrown because the callable expected arguments why should the exception be masked? Doesn't it indicate a templating error or am I missing something? Thoughts? Kieran ",enhancement,closed,Template system,,normal,wontfix,,django@…,Unreviewed,1,0,0,0,0,0