- Author:
- ericmoritz
- Posted:
- June 17, 2008
- Language:
- Python
- Version:
- .96
- Score:
- 2 (after 2 ratings)
This describes an issue with the template system that people may come across.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | """
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::
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
That turns into something like this::
<ul:Element>
<li:HTMLElement>
<li:HTMLElement>
<li:HTMLElement>
<li:HTMLElement>
<li:HTMLElement>
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::
<BlockNode>
<FirstOfNode>
<FirstOfNode>
<FirstOfNode>
<FirstOfNode>
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::
<BlockNode>
<ForNode>
<ReprNode>
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 <http://www.djangosnippets.org/snippets/811/>
"""
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()
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
My vote would be for incomplete documentation. Using nodes like that seems optimal to me, compiling the tag once, then rendering it multiple times. But you're right, it's a bit counter-intuitive if you're used to common object-oriented behavior, and I can definitely see how it could trip you up.
#
Please login first before commenting.