336

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

for i = 1 to n
Lev
  • 6,487
  • 6
  • 28
  • 29

20 Answers20

479

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 %}
alias51
  • 8,178
  • 22
  • 94
  • 166
Udi
  • 4,762
  • 2
  • 17
  • 11
168
{% with ''|center:n as range %}
{% for _ in range %}
    {{ forloop.counter }}
{% endfor %}
{% endwith %}
Tomasz Żyźniewski
  • 1,847
  • 1
  • 11
  • 10
  • 13
    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 https://docs.djangoproject.com/en/dev/ref/templates/builtins/#center – isedwards May 22 '16 at 16:50
123

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 %}
tghw
  • 25,208
  • 13
  • 70
  • 96
  • 20
    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! – Bogatyr Feb 11 '16 at 16:40
  • 2
    @Bogatyr If that's what you want, just use Jinja2: https://docs.djangoproject.com/en/1.9/topics/templates/#django.template.backends.jinja2.Jinja2 – tghw Feb 11 '16 at 21:02
  • @Bogatyr 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). –  Mar 24 '21 at 19:13
116

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 %}
radtek
  • 34,210
  • 11
  • 144
  • 111
Guillermo Siliceo Trueba
  • 4,251
  • 5
  • 34
  • 47
  • 2
    I think this is right solution. Do `range(1, 16)` to get numbers starting from 1, not 0. – chhantyal Feb 16 '15 at 10:30
  • 3
    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()` – Ajeeb.K.P Mar 23 '15 at 05:42
  • 2
    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.... – Paul Kenjora Sep 05 '16 at 07:13
  • 2
    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). – Tim Tisdall Sep 28 '17 at 16:14
  • In order to have a python solution, this seemed to me the best one – artu-hnrq Jul 07 '20 at 18:51
53

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)

Dave W. Smith
  • 24,318
  • 4
  • 40
  • 46
51

Maybe like this?

{% for i in "x"|rjust:"100" %}
...
{% endfor %}
CruelSilence
  • 619
  • 5
  • 2
20

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.

ajinzrathod
  • 925
  • 10
  • 28
Paul Kenjora
  • 1,914
  • 18
  • 20
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 %}
Community
  • 1
  • 1
KKlalala
  • 965
  • 1
  • 13
  • 23
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;-).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
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>
Vinta
  • 387
  • 4
  • 10
  • 2
    Not sure if this is what the OP was looking for, but it's exactly what I was looking for. =) – GChorn Mar 21 '14 at 17:49
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

David Wolever
  • 148,955
  • 89
  • 346
  • 502
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>
Rebs
  • 4,169
  • 2
  • 30
  • 34
  • 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))` – Bedilbek Feb 28 '20 at 03:05
  • @Bedilbek 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] – Rebs Feb 28 '20 at 08:31
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.

Alastair McCormack
  • 26,573
  • 8
  • 77
  • 100
  • 8
    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. – Rebs May 17 '17 at 05:40
  • 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 much – malmiteria Jun 01 '22 at 09:04
4
{% for _ in ''|center:13 %}
    {{ forloop.counter }}
{% endfor %}
Jan Kyu Peblik
  • 1,435
  • 14
  • 20
  • 1
    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. – Amit Verma Feb 05 '21 at 11:27
  • 1
    @AmitVerma I forgot what I said in the last two replies to your comment that someone deleted. Sorry about that. – Jan Kyu Peblik Sep 08 '21 at 22:37
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)
Alper
  • 3,424
  • 4
  • 39
  • 45
  • 2
    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. – Rebs May 17 '17 at 05:42
  • I don't even know… – Alper May 18 '17 at 20:18
  • 1
    I am 9 years too late but I upvoted you fam, don't even worry about it. – Sahil Jun 08 '19 at 04:21
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.

Elias Prado
  • 1,518
  • 2
  • 17
  • 32
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 %}
Darwin
  • 1,695
  • 1
  • 19
  • 29
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 %}
Srishti Ahuja
  • 181
  • 1
  • 7
0

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 %}
Alkindus
  • 2,064
  • 2
  • 16
  • 16
-13
{% for i in range(10) %}
   {{ i }}

{% endfor %}
  • 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. – xiawi Nov 14 '19 at 15:37
  • 1
    Could not parse the remainder: `(10)` from `range(10)` – ajinzrathod Sep 14 '21 at 08:13
  • You can't use range() function in django template.But you can use filter to overcome this problem – lava Feb 10 '22 at 12:49