17👍
I found a better solution with itertools
. (Better than my previous answer)
You can set current state of the loop to the itertools variable sent to the view context.
This time i tried on a dummy Django project and it works like a charm.
views.py:
from django.shortcuts import render_to_response
import itertools
def home(request):
iterator=itertools.count()
base_parts = [
{'important item': 43},
{'lesser item1': 22, 'lesser item2': 3, 'lesser item3': 45},
{'most important item': 55}
]
return render_to_response('index.html',
{'base_parts': base_parts, 'iterator':iterator})
index.html:
{% for base_part in base_parts %}
{% for k, v in base_part.items %}
{{ iterator.next }} - {{ v }}<br/>
{% endfor %}
{% endfor %}
HTML Output:
0 - 43
1 - 22
2 - 45
3 - 3
4 - 55
Sorted values:
(This part is not an answer to the actual question. It’s more like I’m playing around)
You can use Django’s SortedDict instead of Python’s built-in dictionary to keep items order.
views.py
from django.shortcuts import render_to_response
import itertools
from django.utils.datastructures import SortedDict
def home(request):
iterator=itertools.count()
base_parts = [
SortedDict([('important item', 43)]),
SortedDict([('lesser item1', 22),
('lesser item2', 3),
('lesser item3', 45)]),
SortedDict([('most important item', 55)])
]
print base_parts[1]
return render_to_response('index.html',
{'base_parts': base_parts, 'iterator':iterator})
HTML Output:
0 - 43
1 - 22
2 - 3
3 - 45
4 - 55
Edit 2014-May-25
You can also use collections.OrderedDict instead of Django’s SortedDict.
Edit 2016-June-28
Calling iterator.next
doesn’t work in Python 3. You can create your own iterator class, inheriting from itertools.count:
import itertools
class TemplateIterator(itertools.count):
def next(self):
return next(self)
4👍
In an ideal world, you should avoid putting this kind of logic in the template. If you are not preserving the hierarchy in your output (eg displaying these items as a list of lists) flatten the list and use a simple for loop and the loop counter.
However, the ideal solution isn’t always an option. In theory, I believe the following could/should work
{% for base_part in base_parts %}
{% with outerCounter = forloop.parentloop.counter0 %}
{% for k, v in base_part.items %}
...
{% with innerCounter = forloop.counter %}
{{ outerCounter|add:innerCounter }}
{% endfor %}
{% endfor %}
- How to execute code on post_migrate signal in Django?
- Django DecimalField generating "quantize result has too many digits for current context" error on save
1👍
UPDATE: This is not a correct answer. I am just keeping it here to display what doesn’t work.
I have to admit that i haven’t tried this one but you can use with and add statements.
{% with total=0 %}
{% for base_part in base_parts %}
{% for k, v in base_part.items %}
{# ...do stuff #}
{# I try to get a running total of items to use as an ID #}
totalId: {{ total|add:"1" }} <br/>
{% endfor %}
{% endfor %}
{% endwith %}
This would probably work on template level but i think a better approach is calculating it on the view level and passing a dictionary to the template which includes calculated values.
0👍
It would be quite and efficient to do it at the view code…
#Calculate the count
count = 0
for item in base_parts:
count+=len(item.keys())
run_range = range(count)
return to the template
#template
{% for x in run_range|slice:'1' %}
{{x}}
{% endfor %}
- Admin inline with no ForeignKey relation
- Pycharm (Python IDE) doesn't auto complete Django modules
- How to add attributes to option tags?
- Django rest framework api_view vs normal view
0👍
So I actually just appended the dictionary items to a list, ensuring that one I wanted was first:
base_parts = []
for k, v in rails_dic.items():
base_parts.append({k: v})
for k, v in accessories_dic.items():
base_parts.append({k: v})
This may not be the best way to do this, and I am open to a more pythonic way as the correct answer.
I think that taking care of this in the template is both tricky and not appropriate. This is why I my not accepting those answers. I would like that future visitors can see that the solution was sought for the overall problem in the more correct way, not just how to hack it in the templates.
- Django admin dropdown of 1000s of users
- Django & TastyPie: request.POST is empty
- Field Level Permission Django
0👍
A lot of good answers already, but I didn’t see a memoizing tag yet:
@register.simple_tag
def total_count(initial=None, _count=[0]): # noqa
if initial is not None:
# reset counter and make sure nothing is printed
_count[0] = initial
return ''
# increment counter
_count[0] += 1
return _count[0]
This is actually a generic counter that is incremented every time it is called. It uses a mutable default _count
as its "memory."
The counter can be reset by specifying an initial
value. For example, {% total_count 0 %}
resets the "memory" to 0
, so the next count will be 1
. Similarly, {% total_count -1 %}
will start the count at 0
.
Here’s how to use it in a template:
{% total_count 0 %}
{% for ... %}
{% for ... %}
{% total_count %}
{% endfor %}
{% endfor %}
Here’s another example that resets the counter on every iteration of the outermost loop:
{% for ... %}
{% total_count 0 %}
{% for ... %}
{% for ... %}
{% total_count %}
{% endfor %}
{% endfor %}
{% endfor %}
I really like the itertools.count() solution as well, but it requires us to adapt both the view and the template, and it cannot be easily reset from inside the template.
0👍
I was looking for the same thing but stubbornly wanted to do this in the template itself. I came up with this solution:
cart = {'fruit': ['apples', 'oranges'], 'veggies': ['sprouts', 'lettuce']}
In the template:
{% for category, products in cart.items %}
{% with products_length=products|length %}
{% for product in products %}
{% widthratio forloop.parentloop.counter0 1 products_length as counter %}
Counter: {{ counter|add:forloop.counter0 }}
{% endfor %}
{% endwith %}
{% endfor %}
Result:
Counter: 0
Counter: 1
Counter: 2
Counter: 3
Hat tip to @R. for the widthratio hack.
- How to combine django plus gevent the basics?
- Django reverse error: NoReverseMatch
- What's equivalent to Django's auto_now, auto_now_add in SQLAlchemy?
- ValueError: Field 'id' expected a number but got 'Processing'