"""
In the Django template system. There is a small caveat that you need to
recognize when developing your own template tags.
When Django parses the Node tree it creates a template.Node instance for
each template tag in the template. The node tree is just like our beloved
HTML DOM.
So for instance take the following::
That turns into something like this::
The indention shows that the li's are child nodes of the ul tag. Each li in a
different instance of an HTMLElement, each with their own state.
So for a Django Template take the following::
{% block content %}
{% firstof var1 var2 var3 %}
{% firstof var1 var2 var3 %}
{% firstof var1 var2 var3 %}
{% firstof var1 var2 var3 %}
{% endblock %}
The node tree would look like this::
So we have a block node with four FirstOfNode instances
In python this would translate roughly to::
f1 = FirstOfNode(...)
f2 = FirstOfNode(...)
f3 = FirstOfNode(...)
f4 = FirstOfNode(...)
f1.render()
f2.render()
f3.render()
f4.render()
Everything looks fine here. Render is called once per node instance.
Now, check this out::
{% block content %}
{% for i in value_list %}
{% repr obj %}
{% endfor %}
{% endblock %}
The node tree that django builds would look something like this::
Now each node in that tree is it's own object instance with it's own
state. This can be an issue if you don't realize one thing, each node
is persistent while the template is rendering.
Let me write out how the template rendering of the a loop would look
in python with the ReprNode::
repr_node = ReprNode("obj")
for i in range(10):
repr_node.render(context)
Can you tell what the problem? We're calling the render method
on the same instance of the Node.
Let's peek under the covers and look at ReprNode's definition::
class ReprNode(template.Node):
def __init__(self, obj)
self.obj = obj
def render(self, context):
self.obj = template.resolve_variable(self.obj, context)
return str(self.obj)
def do_repr(parser, token):
tag_name, obj = token.split_contents()
return ReprNode(obj)
Now look at what's going on. self.obj is assumed to be the name of the
variable in the context. That's fine when render is called once. When render
called a second time, self.obj is now the actual value of obj from the context.
What happens when you try to resolve that? You get a VariableDoesNotExist
error, uh oh!
I made the assumption that render() is only called once. You don't realize
that inside a for tag, the render method is called repeatedly. This can cause
tons of issues. If it wasn't a huge design change, Template Nodes should
probably be Static classes.
I don't know if this is technically a bug in the Django templating system,
bad design, bad documentation, or just simply poor assumptions on my part, but
I came across this issue today and it took stepping through PDB to find the
problem with my custom template tag (the ReprNode was completely made up for
the example)
Example code
"""
from django import template
register = template.Library()
class ReprNode(template.Node):
def __init__(self, obj):
self.obj = obj
def render(self, context):
self.obj = template.resolve_variable(self.obj, context)
return str(self.obj)
@register.tag
def repr(parser, token):
# Get the tag_name and the variable name of the start variable
tag_name, start_exp = token.split_contents()
return ReprNode(start_exp)
# This allows me to do everything in one file
template.add_to_builtins(__name__)
def test_reprtag():
t = template.Template("""
{% for i in counter %}
mystr: {% repr mystr %}
{% endfor %}
""")
print t.render(template.Context({"mystr": "foo",
"counter": range(10)}))
if __name__ == '__main__':
test_reprtag()