Numeric for loop in Django templates
Asked Answered
P

20

342

How do I write a numeric for loop in a Django template? I mean something like

for i = 1 to n
Papillon answered 10/7, 2009 at 4:48 Comment(0)
C
486

I've used a simple technique that works nicely for small cases with no special tags and no additional context. Sometimes this comes in handy

{% for i in '0123456789'|make_list %}
    {{ forloop.counter }}
{% endfor %}
Coverley answered 22/4, 2011 at 19:21 Comment(7)
FWIW, 012 == 12, so it will only loop over 1 and 2.Baguio
{% for i in '0123456789'|make_list %} to iterate over all 10, not skipping 0.Nicolettenicoli
very clever. what about a list comprehension though? Kind of takes the hackishness out but uses the same idea x = 5, y=[i for i in range(x)] context={'iterateover':y}Selfinduced
@Selfinduced context={'iterateover': range(x)} would work just as wellCarpospore
This solution doesn't work when the string length is variable, I mean when you don't know in advance its lenght.Burkhardt
Generate a string of arbitrary length with 'rjust' {% for i in "x"|rjust:"100" %}Antiquate
Under 4 forcycle is not work. Filter return string of minimal length.Defeat
A
175
{% with ''|center:n as range %}
{% for _ in range %}
    {{ forloop.counter }}
{% endfor %}
{% endwith %}
Attune answered 17/1, 2013 at 22:12 Comment(1)
Great answer. Works because center creates a string of n spaces that are then looped over. Each space char is then ignored, but the current value in range can be found from forloop.counter (or forloop.counter0). See docs.djangoproject.com/en/dev/ref/templates/builtins/#centerCensurable
C
125

Unfortunately, that's not supported in the Django template language. There are a couple of suggestions, but they seem a little complex. I would just put a variable in the context:

...
render_to_response('foo.html', {..., 'range': range(10), ...}, ...)
...

and in the template:

{% for i in range %}
     ...
{% endfor %}
Coprophilia answered 10/7, 2009 at 5:0 Comment(3)
The motivations the Django authors had for disallowing plain python in templates seem pointless and inconsequential compared to the pain and lost time involved in working around not having it, not to mention the need to invent an entirely new langauge when a perfectly awesome one (python!) is already right there!Encephalo
@Encephalo If that's what you want, just use Jinja2: docs.djangoproject.com/en/1.9/topics/templates/…Coprophilia
@Encephalo Because obviously nothing bad happens, when you can call clear_full_page_cache() in a template (seen in php/magento store, took considerable time to debug). Finally, it's not very sensible to let a server render this, when client side this can be done just as easy and cheaper, given that no data is associated with it (else you just iterate the data).Gratulation
L
120

My take on this issue, i think is the most pythonic. Create a my_filters.py in your apps templatetags directory.

@register.filter(name='times') 
def times(number):
    return range(number)

Usage in your template:

{% load my_filters %}
{% for i in 15|times %}
    <li>Item</li>
{% endfor %}
Lactometer answered 9/10, 2012 at 10:12 Comment(5)
I think this is right solution. Do range(1, 16) to get numbers starting from 1, not 0.Platus
Also create an empty file _ init _.py in templatetags directory. Also add these line to top of my_filters.py from django.template import Library;register = Library()Unhair
Add a second filter parameter and you get the full range function built into python. @register.filter(name='range') def filter_range(start, end): return range(start, end) Then gets used as {% for i in 1|range:6 %}{% endfor %}. See full answer below....Gipps
I altered this slightly (excuse formatting): try: return range(number) except: return []. That way it never raises an error and returns an empty array (similar to how most template functions work).Gramnegative
In order to have a python solution, this seemed to me the best oneIneslta
G
55

You can pass a binding of

{'n' : range(n) }

to the template, then do

{% for i in n %}
...
{% endfor %}

Note that you'll get 0-based behavior (0, 1, ... n-1).

(Updated for Python3 compatibility)

Glace answered 10/7, 2009 at 4:57 Comment(3)
Use range(n) in python 3, if I remember it correctly, xrange was deprecated on itQuotha
Indeed yes. And that was one of two lines of code I had to chance in transitioning an app to Python3.Glace
Working in 2021! Voted up!Side
F
53

Maybe like this?

{% for i in "x"|rjust:"100" %}
...
{% endfor %}
Flagellate answered 16/5, 2013 at 17:16 Comment(1)
Works but not easy to read for another dev that comes along and sees it.Hilten
G
22

I'm just taking the popular answer a bit further and making it more robust. This lets you specify any start point, so 0 or 1 for example. It also uses python's range feature where the end is one less so it can be used directly with list lengths for example.

@register.filter(name='range')
def filter_range(start, end):
    return range(start, end)

Then in your template just include the above template tag file and use the following:

{% load myapp_filters %}

{% for c in 1|range:6 %}
    {{ c }}
{% endfor %}

Now you can do 1-6 instead of just 0-6 or hard coding it. Adding a step would require a template tag, this should cover more uses cases so it's a step forward.

Gipps answered 5/9, 2016 at 7:20 Comment(1)
This is an extension of @guillermo-siliceo-trueba answer.Gipps
D
14

I tried very hard on this question, and I find the best answer here: (from how to loop 7 times in the django templates)

You can even access the idx!

views.py:

context['loop_times'] = range(1, 8)

html:

{% for i in loop_times %}
        <option value={{ i }}>{{ i }}</option>
{% endfor %}
Darceydarci answered 7/7, 2016 at 19:16 Comment(1)
Be more specific. How should we write context['loop_times'] = range(1, 8). If possible give the entire codeShingles
W
11

You don't pass n itself, but rather range(n) [the list of integers from 0 to n-1 included], from your view to your template, and in the latter you do {% for i in therange %} (if you absolutely insist on 1-based rather than the normal 0-based index you can use forloop.counter in the loop's body;-).

Woodenware answered 10/7, 2009 at 4:55 Comment(0)
I
10

Just incase anyone else comes across this question… I've created a template tag which lets you create a range(...): http://www.djangosnippets.org/snippets/1926/

Accepts the same arguments as the 'range' builtin and creates a list containing
the result of 'range'.

Syntax:
    {% mkrange [start,] stop[, step] as context_name %}

For example:
    {% mkrange 5 10 2 as some_range %}
    {% for i in some_range %}
      {{ i }}: Something I want to repeat\n
    {% endfor %}

Produces:
    5: Something I want to repeat 
    7: Something I want to repeat 
    9: Something I want to repeat

Islander answered 14/2, 2010 at 22:50 Comment(1)
-1 in favour of Alex Pi's snippet which adds support of variable arguments.Fizgig
M
10

You should use "slice" in template, a example like this:

in views.py

contexts = {
    'ALL_STORES': Store.objects.all(),
}

return render_to_response('store_list.html', contexts, RequestContext(request, processors=[custom_processor]))

in store_list.html:

<ul>
{% for store in ALL_STORES|slice:":10" %}
    <li class="store_item">{{ store.name }}</li>
{% endfor %}
</ul>
Moua answered 15/8, 2012 at 3:30 Comment(1)
Not sure if this is what the OP was looking for, but it's exactly what I was looking for. =)Phasis
R
10

This method supports all the functionality of the standard range([start,] stop[, step]) function

<app>/templatetags/range.py

from django import template

register = template.Library()


@register.filter(name='range')
def _range(_min, args=None):
    _max, _step = None, None
    if args:
        if not isinstance(args, int):
            _max, _step = map(int, args.split(','))
        else:
            _max = args
    args = filter(None, (_min, _max, _step))
    return range(*args)

Usage:

{% load range %}

<p>stop 5
{% for value in 5|range %}
{{ value }}
{% endfor %}
</p>

<p>start 5 stop 10
{% for value in 5|range:10 %}
{{ value }}
{% endfor %}
</p>

<p>start 5 stop 10 step 2
{% for value in 5|range:"10,2" %}
{{ value }}
{% endfor %}
</p>

Output

<p>stop 5
0 1 2 3 4
</p>

<p>start 5 stop 10
5 6 7 8 9
</p>

<p>start 5 stop 10 step 2
5 7 9
</p>
Rica answered 23/2, 2016 at 4:41 Comment(2)
your solution does not work on for value in 0|range:"10,2". You have to change your code as follow: args = filter(lambda x: isinstance(x, int) and x >= 0, (_min, _max, _step))Cuomo
@Cuomo this code mimics the standard python range. even it doesn't support negative ranges without an explicit step parameter. >>> list(range(10,2)) [] >>> list(range(10,2,-1)) [10, 9, 8, 7, 6, 5, 4, 3]Rica
K
5

This essentially requires a range function. A Django feature ticket was raised (https://code.djangoproject.com/ticket/13088) for this but closed as "won't fix" with the following comment.

My impression of this idea is that it is trying to lead to programming in the template. If you have a list of options that need to be rendered, they should be computed in the view, not in the template. If that's as simple as a range of values, then so be it.

They have a good point - Templates are supposed to be very simple representations of the view. You should create the limited required data in the view and pass to the template in the context.

Kowalewski answered 27/2, 2017 at 16:27 Comment(2)
The view should be for data, the template should be for presentation. The view should not require knowledge of the contents of the template, specifically ranges. Django's reason for ignoring these feature requests is utter rubbish.Rica
that's actually hard to tell if it's a good point. Templates aren't supposed to "host programmation" but they do today, with for loops and such... And actually, the solution today is to add tags, providing syntax support to allows method like calls in templates and default tags isn't gonna change muchKnifeedged
P
4
{% for _ in ''|center:13 %}
    {{ forloop.counter }}
{% endfor %}
Patton answered 4/2, 2021 at 23:41 Comment(2)
While this code may provide a solution to the question, it's better to add context as to why/how it works. This can help future users learn and eventually apply that knowledge to their own code. You are also likely to have positive-feedback/upvotes from users, when the code is explained.Thomasson
@AmitVerma I forgot what I said in the last two replies to your comment that someone deleted. Sorry about that.Patton
S
3

If the number is coming from a model, I found this to be a nice patch to the model:

def iterableQuantity(self):
    return range(self.quantity)
Skin answered 10/3, 2011 at 18:45 Comment(3)
Not sure why you're getting down voted, it's a valid answer. I don't like this solution compared to implementing a proper filter as I've provided above. DB models should be kept lean. But it's still better than the majority accepted answer.Rica
I don't even know…Skin
I am 9 years too late but I upvoted you fam, don't even worry about it.Hauck
S
3

For those who are looking to simple answer, just needing to display an amount of values, let say 3 from 100 posts for example just add {% for post in posts|slice:"3" %} and loop it normally and only 3 posts will be added.

Sporades answered 14/3, 2020 at 3:6 Comment(0)
Z
3

This shows 1 to 20 numbers:

{% for i in "x"|rjust:"20"|make_list %}
 {{ forloop.counter }}
{% endfor %}

also this can help you: (count_all_slider_objects come from views)

{% for i in "x"|rjust:count_all_slider_objects %}
  {{ forloop.counter }}
{% endfor %}

or

  {% with counter=count_all_slider_objects %}
    {% if list_all_slider_objects %}
      {%  for slide in list_all_slider_objects %}
        {{forloop.counter|add:"-1"}}
        {% endfor%}
      {% endif %}
    {% endwith %}
Zeena answered 27/8, 2020 at 22:30 Comment(0)
W
1

If you work with queryset you can cap the number of elements with if. Assume that you need to list 5 items:

{% for  item in queryset %}
  {% if forloop.counter < 6 %}
   {{ forloop.counter}}.{{item}}
  {% endif %}
{% endfor %}

If you need just number range, I think its better to create a list or tuple on view part and then push it to the template.

On view:

iteration = [item for item in range(10)]
context= {'iteration':iteration}

On Template:

{% for  item in iteration %}
  {{ forloop.counter}}.{{item}}
{% endfor %}
Whereof answered 22/8, 2023 at 7:50 Comment(0)
D
0

You can pass range(n) instead of n in the context in views.py. This will give you an iterable list.

context['range']= range(n)

Then you can iterate in your template this way:

{% for i in range %}
   <!-- your code -->
{% endfor %}
Dallis answered 7/8, 2021 at 21:4 Comment(0)
J
-13
{% for i in range(10) %}
   {{ i }}

{% endfor %}
Jannette answered 14/11, 2019 at 15:18 Comment(3)
While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value.Caloyer
Could not parse the remainder: (10) from range(10)Shingles
You can't use range() function in django template.But you can use filter to overcome this problemElusion

© 2022 - 2024 — McMap. All rights reserved.