简体   繁体   中英

How to correctly structure a for loop in Django templating language

I have a template file in a Django app that is using data from the corresponding view using template variables. In the context of the view (which is returned as the page is rendered) I have a variable called len_values that stores the value of the end range of the loop. I currently have the for loop structured with this syntax:

 <ul class= 'list-group'>
     {% for i in range(0,len_values) %}
         <li  class = 'list-group-item' >
             Artist: {{form_artistSelect}}
             Location: {{venues.i.city}}, {{venues.i.region}}
             Venue: {{venues.i.name}}
             Date: {{dates.i}}
             tickets status: {{ticket_statuses.i}}<br>
             <a href = {{ticket_urls.i}}> ticket link </a>
             {% if user.is_authenticated %}
                 <button id = 'invite' type='button' class= 'btn btn-info btn-lg' data-toggle = 'modal' data-target='#myModal' venue={{venues.i}} date={{dates.i}} ticket_url={{ticket_urls.i}} artist = {{form_artistSelect}}> Invite a friend </button>
                 <button id = 'save' type = 'button' class = 'btn btn-primary-outline'> Save Concert </button>
             {% endif %}
         </li>
     {% endfor %}
 </ul>

When I tried this code, I was getting the error: Could not parse the remainder: '(0,len_values)' from 'range(0,len_values)'

I've seen this old SO post which seems to be solving a similar issue but there isn't a conclusive answer based on the upvotes and the answers provided are old.

What would be the proper way to structure this for loop in the Django templating format

You simply can't do this in Django's template language, as the documentation clearly shows. Even if you got the range bit working, your variable lookups themselves still wouldn't work: the i in venues.i.city is always interpreted as the literal name "i".

But you don't want to do it anyway: you shouldn't do it even in raw Python. You would never iterate over range(len(something)); always iterate over the thing itself. It's just as true here: in your view, you should create a data structure - say, a list of dicts - containing the cities, regions, dates, statuses etc grouped per item, and then iterate through that in your template:

data = [
    {'city': "City 1", "region": "Region 1", "date": date_1, ....},
    ...
]

template:

{% for item in data %}
    Location: {{ item.city }}, {{ item.region }}
    ...
{% endfor %}

Django templates were designed so it's syntax is as simple as possible. The idea is that the templates were implemented by designers more than by programmers.

In your case, the problem is that you can't simply add Python code like range(0, len_values) inside the for tag. The for tag expects an iterator, which may be processed by a filter, but not an arbitrary Python expression.

You have different options to to iterate a given number of times:

  • The easiest is to change your view, so instead of the len of the array, you pass the array itself to the template (you have the range(len_values) in the context, instead of just the len_values).
  • You can create your own template tag, which could be similar to the builtin for, but expecting the len, instead of an iterator.
  • You can use Jinja2, which is very similar in syntax to Django templates, but you are not so limited in the template syntax (not sure if in this case your code would work).

But in your case, I think the problem is another one, in the view side. Instead of having different iterators for venues, dates, ticket_statuses, etc. all with the same length, you should have just one with all the information.

So, instead of having a view like:

def my_view(request):
    context = {'venues': ['venue 1', 'venue 2', 'venue 3'],
               'dates': ['date 1', 'date 2', 'date 2'],
               'ticket_statuses': ['status 1', 'status 2', 'status 3']}
    render(request, 'my_template.html', context)

you should have something like:

def my_view(request):
    context = {'tickets': [
                  {'venue': 'venue 1', 'date': 'date 1', 'status': 'status 1'},
                  {'venue': 'venue 2', 'date': 'date 2', 'status': 'status 2'},
                  {'venue': 'venue 3', 'date': 'date 3', 'status': 'status 3'}]}
    render(request, 'my_template.html', context)

Then, your template will be as simple as:

<ul class= 'list-group'>
     {% for ticket in tickets  %}
         <li  class = 'list-group-item' >
             Artist: {{form_artistSelect}}
             Location: {{ticket.venue.city}}, {{ticket.venue.region}}
             Venue: {{ticket.venue.name}}
             Date: {{ticket.date}}
             tickets status: {{ticket.status}}<br>
             <a href = {{ticket.url}}> ticket link </a>
             {% if user.is_authenticated %}
                 <button id = 'invite' type='button' class= 'btn btn-info btn-lg' data-toggle = 'modal' data-target='#myModal' venue={{venues.i}} date={{dates.i}} ticket_url={{ticket_urls.i}} artist = {{form_artistSelect}}> Invite a friend </button>
                 <button id = 'save' type = 'button' class = 'btn btn-primary-outline'> Save Concert </button>
             {% endif %}
         </li>
     {% endfor %}
 </ul>

As you can see in the official docs the syntax is

{% for var in iterable %}

So in the view pass range(len_values) in the context as the iterable.

Mind you: you would be much better off passing the list of values, without calculating its length.

In case you need the index of each item, in the template loop you can access it with forloop.counter (use forloop.counter0 if starting from zero), eg

{% for item in data %}
    This is item number {{ forloop.counter }}
    {{ item.city }}
    {{ item.region }}
{% endfor %}

Django template tags are not python code. You can't just use functions here. But you can, as in python, iterate through an iterable using for loop.

Second thing that won't work is:

Location: {{venues.i.city}}, {{venues.i.region}}

I won't be treated as variable here, but as name of field in venues.

Proper solution will be:

 <ul class= 'list-group'>
     {% for venue in venues %}
         <li  class = 'list-group-item' >
             Artist: {{form_artistSelect}}
             Location: {{venue.city}}, {{venue.region}}
             Venue: {{venue.name}}
             Date: {{dates.i}}
            ... etc ...
         </li>
     {% endfor %}
 </ul>

But there is problem: you're trying to use that variable in more than one list. Django templates won't allow this by default, so you have 3 options here...

1. join lists into one list that you will iterate through.

This is preferred solution, because preparing your data should go into your view.

2. create your own template tag(s) or filter(s)

You can extract data from other lists using some custom template tag(s) or filter(s). Then simply iterate through one of lists and pass forloop.counter or forloop.counter0 to your custom tag to extract particular data.

3. use dirty hack

There is one way to go around this limitation of django templates, but it is really dirty and you shouldn't use it. Here is example:

<ul class= 'list-group'>
     {% for venue in venues %}{% with index=forloop.counter0|stringformat:"i"|add:":" %}{% with date=dates|slice:index ticket_status=ticket_statuses|slice:index ticket_url=ticket_urls|slice:index %}
         <li  class = 'list-group-item' >
             Artist: {{form_artistSelect}}
             Location: {{venue.city}}, {{venue.region}}
             Venue: {{venue.name}}
             Date: {{date.0}}
             tickets status: {{ticket_status.0}}<br>
             <a href = {{ticket_url.0}}> ticket link </a>
             {% if user.is_authenticated %}
                 <button id = 'invite' type='button' class= 'btn btn-info btn-lg' data-toggle = 'modal' data-target='#myModal' venue={{venue}} date={{date.0}} ticket_url={{ticket_url.0}} artist = {{form_artistSelect}}> Invite a friend </button>
                 <button id = 'save' type = 'button' class = 'btn btn-primary-outline'> Save Concert </button>
             {% endif %}
         </li>
     {% endwith %}{% endwith %}{% endfor %}
 </ul>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM