0

Supposing a list0 with elements ['a','b','c','d'], I need to get the nth element depending on a template variable like forloop.counter0 or any other integer available from the template.

So far the better way I found was to create a custom tag as explained here or here and in the django doc.
Once defined you can write something like :

{% for elm in list0 %}
{{ elm }} {{ list1|index:forloop.counter0 }}
{% endfor %}

The example assumes there's another list list1, with elements ['A','B','C','D'].
index is the name of the custom filter, it uses the forloop counter as an index to get each list1 element : list1[0], list1[1], list1[2], list1[3]

So this generates :

a A
b B
c C
d D

But what if you only want to use builtin filters ?
(Or have spare time for recreative stuff ?)
After some research and tests the only way I found is this odd thing :

{% for elm in list0 %}

{% with sl0=forloop.counter0|make_list sl1=forloop.counter|make_list %}
{% with header_id=sl0.0|add:":"|add:sl1.0 %}

{{ elm }} {{ list1|slice:header_id|join:"" }}

{% endwith %}
{% endwith %}

{% endfor %}

what it does :

It uses the slice builtin filter. slice needs a string representing a python list part like '[2:3]' which is generated by the second with and the two add. So one need to slice one member at a time using the forloop counters : '[0:1]', '[1:2]', '[2:3]'...But forloop.counter0 and forloop.counter are integers, and add does not generate a string in this case. It tries to get an integer as a result. So it needs to be converted to a string. This is why make_list is used in the first with, as it's the only way I found so far to change an integer into a string..finally slice returns a list with one element so it uses a join:"" to convert it to a string.

I hope I missed something in the documentation because the code above is.. well.. terrible - but funny.

How could I address this need in an efficient way using django template builtins ?
I agree it should be adressed when building the context in the view and not in the template, but let's say it cannot be done.

jerome
  • 183
  • 2
  • 14
  • In the view `zip_obj = zip(list0, list1)`, in the template `{% for elm0, elm1 in zip_obj %}`, why do all this indexing and what not in the template? Also you didn't miss anything in the documentation as it is a deliberate design decision to separate the template from the logic. – Abdul Aziz Barkat Apr 30 '21 at 10:29
  • @AbdulAzizBarkat: yes, that is exacly to prevent what happens here, that people write complex code, that will typically require a lot of cycles. – Willem Van Onsem Apr 30 '21 at 10:32
  • thanks. in my question, please note "I agree it should be adressed when building the context in the view and not in the template, but let's say it cannot be done.". obviously I know about zip(). Also since there's a slice template filter, why not an index one ? – jerome Apr 30 '21 at 11:09

1 Answers1

0

You should not do this in a template, a template is for rendering logic, it i up to the view to provide the correct structure. We can do this with zip(…):

from django.shortcuts import render

def my_view(request):
    list0 = ['a', 'b', 'c', 'd']
    list1 = ['A', 'B', 'C', 'D']
    list01 = zip(list0, list1)
    return render(request, 'some_template.html', {'list01': list01})

and then render this with:

{% for elm0, elm1 in list01 %}

{{ elm0 }} {{ elm1 }}

{% endfor %}
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555