﻿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 '<p>%s</p>' % message
}}}

{{{
{% chant ""Django beats Chuck Norris."" %}
}}}

outputs:

{{{
<p>Django beats Chuck Norris.</p>
}}}

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 '<p%s>%s</p>' % (
        ''.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:

{{{
<p style=""color:red; font-weight:bold;"" class=""chuck-hidden chant"">Django beats Chuck Norris.</p>
}}}

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
