{% for thing in mylist %}
{% if thing.test %}
{{thing}}
{% endif %}
{% endfor %}
Now if I want to comma-separate the list, how do I do it? The natural
thing to my mind is an "iffirst" tag:
{% for thing in mylist %}
{% if thing.test %}
{% iffirst %}Things: {% else %}, {% endiffirst %}
{{thing}}
{% endif %}
{% endfor %}
{% iffirst %} is true the first time it is evaluated in each loop.
Because of the "if thing.test", it could be evaluated first on the third
time through the loop, for example. That's why forloop.first isn't
sufficient.
I've tried creating such a tag, but can't find the contextual
information I need to know when the for loop has been restarted.
(Also: the whitespace will be messed up even if I do have this tag: the
comma will have space before it).
So, two questions: 1) How would I create a tag like this, and 2) is
there some simpler way to achieve my purpose?
--Ned.
--
Ned Batchelder, http://nedbatchelder.com
the for loop has a forloop.first, see:
http://www.djangoproject.com/documentation/templates/#for
Creating your own tags is covered in
http://www.djangoproject.com/documentation/templates_python/
I would look at the join filter though.
http://www.djangoproject.com/documentation/templates/#join
Michael Trier
blog.michaeltrier.com
Is there a compelling reason not to do the filtering in the view
before you even hit the template?
Todd
In the tag code, test to see if context['forloop']['first'] is True or
not. That's the Python equivalent of {% if forloop.first %} in a
template.
But, seriously, use {% if forloop.first %}. It even works for nested
for-loops:
{% for x1 in some_list %}
{% for x2 in some_other_list %}
{% if forloop.parentloop.first %}
x1 is still on it's first iteration.
{% endif %}
{% endfor %}
{% endfor %}
"parentloop" always points to the parent loop or is {} when there's no
nesting above the current loop.
(And, yes, the wheels will come fall off if you ever create your own
context variable called "forloop", so don't do that.)
Regards,
Malcolm
--
Tolkien is hobbit-forming.
http://www.pointy-stick.com/blog/
{% for thing in mylist %} {% if thing.test %} {% iffirst %}Things: {% else %}, {% endiffirst %} {{thing}} {% endif %} {% endfor %}--Ned.
-- Ned Batchelder, http://nedbatchelder.com
Presumably, it's even more subtle than that. Don't you want to know if
it's the first time in the "if" clause for this particular execution of
the for-loop?
In any case, change the syntax slightly. The "iffirst" tag should take a
variable name, which is what will be set in the context to indicate if
it's been executed before. So the render code for
{% iffirst foo %}
will check to see if context['foo'] is equal to id(context['forloop']).
If not, set it to that value and return the "true" branch. Otherwise,
return the "false" branch. Untested code follows:
def render(self, context):
if context[self.varname] == id(context['forloop']):
return self.nodelist_false.render(context)
context[self.varname] = id(context['forloop'])
return self.nodelist_true.render(context0
Here. nodelist_true and nodelist_false could be similar to the same
variables in django.template.defaulttags.IfNode (and do the setup the
same way). I've been awake all night, so my brain is completely straight
at the moment ... there might be some subtlety that means you need to
use Variable.resolve() or something instead of just
context[self.varname], but it should be clear with a bit of
experimentation. You certainly won't need anything as complicated as
what the IfNode class does, since you aren't testing a user-supplied
condition.
The only reason to use the variable name is so that you can have
multiple "iffirst" calls in a single template without collisions.
Clearly extending to allow another parameter that says which for-loop
level of nesting ("parent", etc) would be another extension and not too
difficult.
Regards,
Malcolm
--
I just got lost in thought. It was unfamiliar territory.
http://www.pointy-stick.com/blog/
On Sat, 2007-12-29 at 08:27 +1100, Malcolm Tredinnick wrote:
[...]
> def render(self, context):
> if context[self.varname] == id(context['forloop']):
> return self.nodelist_false.render(context)
> context[self.varname] = id(context['forloop'])
> return self.nodelist_true.render(context)
This guess is rubbish. The problem is that the context['forloop']
dictionary is a new instance for every iteration of the for-loop, so
there's nothing constant about that outer structure we can use.
After a bit of sleep, some food and some time spent thinking about this
whilst doing other stuff today, there's a better solution (a.k.a one
that works) attached to this mail. I've even tested this one.
During any for-loop, the only thing that remains constant is the
"parentloop" dictionary. That dictionary is created anew every time the
*parent* loop steps forwards, but it is referred to by reference for
each iteration of the inner loop. So we can stick a marker into the
parent loop's data structure (even when there's no parent loop, that
data structure is an empty dictionary).
So, this implementation does depend a bit on the internals of the
ForNode, but that isn't a complete crime. Plus it's been hard to come up
with an alternative. Every time I thought of some tricky data structure
that seemed to do the trick, I was able to break it. A template fragment
such as
{% for x in data %}
{% if x %}
{% iffirst %} yes {% else %} no {% endfirst %}
{% else %} fail {% endif %}
{% endfor %}
given data = (0, 1, 1, 0) twice in succession (without recompiling), or
giving data = (0, 1, 1, 0) and data = (0, 0, 1, 0) on successive runs
(again, without recompiling) was enough to break most of my attempts.
It's also not too hard to extend this to be check whether this is the
first time around the parent loop using a similar method, although
nested "iffirst" tags seems hard.
Best wishes,
-- Ned Batchelder, http://nedbatchelder.com