Students build pages from a list of dictionaries with a template (Jinja), add a page for each player with an address part that changes, then add a wins column, group pages and a leaders page.
Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.
Predict and Run 10 min · Investigate 10 min · Modify 20 min · Make 10 min
Read the unit primer first. A template is HTML with gaps, in Flask's template language (Jinja): {{ value }} shows a value and {% for p in players %} ... {% endfor %} repeats a part. render_template_string(PAGE, players=players) fills it in; the template sees only what is passed by name (the bug task forgets to pass it, and the list comes out empty, with no error). In a route, <name> matches any word in that place of the address and is passed to the function. A route can return a page and a status: return "...", 404. On a computer templates usually live in templates/ files with render_template; the playground keeps them in strings so everything is in one file.
Passing data into a template by name. Ask what the template can see, and let them find the missing argument themselves.
| What you will see | A question to ask | The fix |
|---|---|---|
| The table or list is empty. | Is the list passed to render_template_string by name? | render_template_string(PAGE, players=players). |
| jinja2.exceptions.TemplateSyntaxError. | Does every {% for %} have an {% endfor %}, and every {{ a }}? | Close each one; check the brackets. |
| /group/players finds nobody. | Does the address match the data exactly, capitals included? | Use /group/Players, or compare with .lower() on both sides. |
One possible solution for each task. Other correct programs are fine.
from flask import Flask, render_template_string
app = Flask(__name__)
players = [
{"name": "Ada", "group": "Champions", "wins": 5},
{"name": "Sam", "group": "Players", "wins": 4},
{"name": "Mia", "group": "Beginners", "wins": 1},
{"name": "Leo", "group": "Players", "wins": 3},
]
PAGE = """<h1>Chess club</h1>
<table>
<tr><th>Player</th><th>Group</th><th>Wins</th></tr>
{% for p in players %}
<tr><td><a href="/player/{{ p['name'] }}">{{ p["name"] }}</a></td><td>{{ p["group"] }}</td><td>{{ p["wins"] }}</td></tr>
{% endfor %}
</table>"""
PLAYER = """<h1>{{ p["name"] }}</h1>
<p>Group: {{ p["group"] }}</p>
<p><a href="/">All players</a></p>"""
@app.route("/")
def home():
return render_template_string(PAGE, players=players)
@app.route("/player/<name>")
def player(name):
for p in players:
if p["name"] == name:
return render_template_string(PLAYER, p=p)
return "<h1>No player called " + name + "</h1>", 404
app.run()
from flask import Flask, render_template_string
app = Flask(__name__)
players = [
{"name": "Ada", "group": "Champions", "wins": 5},
{"name": "Sam", "group": "Players", "wins": 4},
{"name": "Mia", "group": "Beginners", "wins": 1},
{"name": "Leo", "group": "Players", "wins": 3},
]
PAGE = """<h1>Chess club</h1>
<table>
<tr><th>Player</th><th>Group</th></tr>
{% for p in players %}
<tr><td><a href="/player/{{ p['name'] }}">{{ p["name"] }}</a></td><td>{{ p["group"] }}</td></tr>
{% endfor %}
</table>"""
PLAYER = """<h1>{{ p["name"] }}</h1>
<p>Group: <a href="/group/{{ p['group'] }}">{{ p["group"] }}</a></p>
<p><a href="/">All players</a></p>"""
@app.route("/")
def home():
return render_template_string(PAGE, players=players)
@app.route("/player/<name>")
def player(name):
for p in players:
if p["name"] == name:
return render_template_string(PLAYER, p=p)
return "<h1>No player called " + name + "</h1>", 404
@app.route("/group/<group>")
def group_page(group):
members = []
for p in players:
if p["group"] == group:
members.append(p)
return render_template_string(PAGE, players=members)
app.run()
from flask import Flask, render_template_string
app = Flask(__name__)
players = [
{"name": "Ada", "group": "Champions", "wins": 5},
{"name": "Sam", "group": "Players", "wins": 4},
{"name": "Mia", "group": "Beginners", "wins": 1},
{"name": "Leo", "group": "Players", "wins": 3},
]
LEADERS = """<h1>Leaders</h1>
<ol>
{% for p in leaders %}
<li>{{ p["name"] }}: {{ p["wins"] }} wins</li>
{% endfor %}
</ol>"""
@app.route("/leaders")
def leaders_page():
leaders = []
for p in players:
if p["wins"] >= 3:
leaders.append(p)
return render_template_string(LEADERS, leaders=leaders)
app.run()
Stretch: Sort the leaders by wins, highest first (sorted(leaders, key=lambda p: p["wins"], reverse=True)), or show the number of players in each group on the home page.
Support: Give students the group route's loop, and ask them only to write the route line and the return.
What does {% for p in players %} do in a template?
Repeats the part up to {% endfor %} once for each item in players, with p as that item.
Next lesson adds a form that adds players, then saving them to a file. Data detective's tables and this template idea meet again in any web dashboard.