user3151858
user3151858

Reputation: 820

Converting Python 3.x code into jinja2 template using groupby and itemgetter()

I have sorted list of cities called sorted_cities=[Atlanta, Berlin, Bern, Calgary] that I am sorting in alpha order and would like to display on the web with ability to click on the city to get detailed information as well as select city to get notifications via e-mail. I want the output on the web look as follows:

A

 Atlanta

B  
 Berlin

 Bern

C

 Calgary

I have the following code in Python 3.x that works fine:

sorted_names=sorted(names,key=str.lower)

    print(sorted_names)

   for letter, words in groupby(sorted_names,key=itemgetter(0)):

       print(letter)
       for word in words:

           print(word)

I created this code in Jinja2 but it does not work:

{% block content %}
<div>
    <form action="cities" method="post">
    {% for group in sorted_names|groupby("letter") %}
        <li><b>{{ group.grouper }} </b></li>
        {% for word in group.list %}
            <input type="checkbox" name="city" value="{{ word }}"> <a href="/{{ word }}">{{ word }}</a><br>
        {% endfor %}
        {% endfor %}

    <input type="submit" value="Submit">
    </form>
</div>
{% endblock %}

Upvotes: 0

Views: 1721

Answers (2)

lord63. j
lord63. j

Reputation: 4670

The default filter groupby in jinja2 need the attribute to group your items. Howerver, you give the 'letter' attribute but jinja2 can't understand it since your names list don't have such attribute.

groupby(value, attribute)

Group a sequence of objects by a common attribute.


To reuse your python code, you can register a filter to jinja2 via [template_filter](http://flask.pocoo.org/docs/dev/api/#flask.Flt’s now possible to use dotted notatask.template_filter). So, you can try this way:

from itertools import groupby
from operator import itemgetter

from flask import Flask, render_template


app = Flask(__name__)


@app.template_filter()
def groupby_letter(iterable):
    return groupby(sorted(iterable), key=itemgetter(0))


@app.route('/')
def index():
    names = ['Atlanta','Calgary','Berlin','Austin']
    return render_template('index.html', names=names)


if __name__ == '__main__':
    app.run(debug=True)

and use the filter in your template like this:

{% for key, group in names|groupby_letter %}
    <li><b>{{ key }}</b></li>
    {% for word in group %}
        <p>{{ word }}</p>
    {% endfor %}
{% endfor %}

Yet another way is that you need do something to your names if you still want to use the groupby filter. We need to build a list dicts and each dict contains a 'first_letter' key, we will use it as the group attribute when using groupby filter.

from flask import Flask, render_template


app = Flask(__name__)


@app.route('/')
def index():
    names = ['Atlanta','Calgary','Berlin','Austin']
    name_dicts = [{'first_letter': name[0], 'name': name} for name in names]
    return render_template('index.html', name_dicts=name_dicts)


if __name__ == '__main__':
    app.run(debug=True)

and use the groupby filter in your template like this:

{% for group in name_dicts|groupby('first_letter') %}
    <li><b>{{ group.grouper }}</b></li>
    {% for item in group.list %}
        <p>{{ item.name }}</p>
    {% endfor %}
{% endfor %}

Upvotes: 1

wgwz
wgwz

Reputation: 2769

Here's a quick test that gets the basic functionality you are looking for. I piggy backed off of @1844144's comment. But basically just append the groupby elements to an array on the python side and pass it through to jinja. Then in jinja I do a not so clever conditional thing with a jinja filter (Length of string in Jinja/Flask)

app.py:

from flask import Flask, render_template
from itertools import groupby
from operator import itemgetter

app = Flask(__name__)

@app.route('/')
def main():
    names = ['Atlanta','Calgary','Berlin','Austin']
    sorted_names = sorted(names, key=str.lower)
    grouped = []
    for letter, words in groupby(sorted_names,key=itemgetter(0)):
        grouped.append(letter)
        for word in words:
            grouped.append(word)
    return render_template('index.html', grouped=grouped)

if __name__ == '__main__':
    app.run(debug=True)

templates/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% for name in grouped %}
    {% if name|length == 1 %}
        {{name}}<br>
    {% else %}
        <a href="/"> {{name}} </a><br>
    {%endif%}
{% endfor %}
</body>
</html>

Here's how it looks in the browser:

enter image description here

Upvotes: 1

Related Questions