Home > Net >  better way to loop a dict in Jinja2
better way to loop a dict in Jinja2

Time:09-02

I am working on a flask app that gets data from a database and should render this in on a page template. It is working okish, but there is the request to display data differently. I am now looking how I can do this best.

The data is an example, my data set has up to 20 columns and more than 1000 rows

Assume the SQL table looks like this:

State City Population
Alabama Huntsville 220k
Alabama Montgomery 199k
Alabama Birmingham 198k
Alabama Mobile 185k
Alaska Anchorage 288k
Alaska Fairbanks 33k
Alaska Juneau 32k
Arizona Phoenix 1,625k
Arizona Tucson 543k
Arizona Mesa 509k

The data is going directly to a dict which is passed to jinja2

cursor.execute("SELECT State , City , Population from table")
result = cursor.fetchall()
return render_template('template.html', results = results)

in Jinja2 it looks like this:

    {% for result in results %}
        <tr>
            <td>{{result['State']}}</td>     
            <td>{{result['City']}}</td>
            <td>{{result['Population']}}</td>
        </tr>        
    {% endfor %}

Now the request is to deliver the output in div with a structure like this:

Alabama 
 -- Huntsville 220k
 -- Montgomery 199k
 -- Birmingham 198k
Alaska
 -- Anchorage 228k
 -- Fairbanks 33k
 -- Juneau 32k

The jinja loop could be something link this

    {% for states in results %}
        <div>
            <div>{{result['State']}}</div>     
            <div>
              {% for cities in results %}
{% if states['State'] == cities ['State'] %}
                <div>--</div>
                <div>{{result['City']}}</div>
                <div>{{result['Population']}}</div>
{% endif %}
              {% endfor %}
            </div>
        </div>        
    {% endfor %}

what is the best way to loop this dictionary again? Would it be better if the data would be prepared differently before putting into the template?

I am not looking for the exact code, but some input what could be a great approach.

CodePudding user response:

You don't need that nested loop, you can use the loop.nextitem and loop.previtem special variable to asses if you need to display the state or not:

{% for result in results | sort(attribute="State") %}
  {% if not loop.previtem or loop.previtem.State != result.State %}
    <div>
      <div>{{ result.State }}</div>     
      <div>
  {% endif %}
        <div>--</div>
        <div>{{ result.City }}</div>
        <div>{{ result.Population }}</div>
  {% if not loop.nextitem or loop.nextitem.State != result.State %}
      </div>
    </div>   
  {% endif %}     
{% endfor %}

Another way would be to, first, select the unique states, with the unique filter, and then, only select the elements having those state, with the selectattr filter:

{% for state in results | unique(attribute="State") %}
<div>
  <div>{{ state.State }}</div>     
  <div>
    {% for city in results | selectattr('State', '==', state.State) %}
      <div>--</div>
      <div>{{ city.City }}</div>
      <div>{{ city.Population }}</div>
    {% endfor %}
  </div>
</div>      
{% endfor %}

CodePudding user response:

I think it would be simpler first group data and lated send it to template.

You may use itertools.groupby(data, key) to group it

groups = itertools.groupby(results, lambda row:row['State'])

Minimale working example

results = [
    {'State': 'Alabama', 'City': 'Huntsville', 'Population': '220k'},
    {'State': 'Alabama', 'City': 'Montgomery', 'Population': '199k'},
    {'State': 'Alabama', 'City': 'Birmingham', 'Population': '198k'},
    {'State': 'Alabama', 'City': 'Mobile', 'Population': '185k'},
    {'State': 'Alaska', 'City': 'Anchorage', 'Population': '288k'},
    {'State': 'Alaska', 'City': 'Fairbanks', 'Population': '33k'},
    {'State': 'Alaska', 'City': 'Juneau', 'Population': '32k'},
    {'State': 'Arizona', 'City': 'Phoenix', 'Population': '1,625k'},
    {'State': 'Arizona', 'City': 'Tucson', 'Population': '543k'},
    {'State': 'Arizona', 'City': 'Mesa', 'Population': '509k'}
]
    
#print(results)

import itertools

groups = itertools.groupby(results, lambda row:row['State'])

# conver only to display it in one line but `for`-loop doesn't need it
#groups = [(state, list(data)) for state, data in groups]
#print(groups)

# run similar loops in template
for state, data in groups:
    print(state)
    for item in data:
        print('---', item['City'], item['Population'])

Results:

Alabama
--- Huntsville 220k
--- Montgomery 199k
--- Birmingham 198k
--- Mobile 185k
Alaska
--- Anchorage 288k
--- Fairbanks 33k
--- Juneau 32k
Arizona
--- Phoenix 1,625k
--- Tucson 543k
--- Mesa 509k
  • Related