Keep the club's players in a list, and let a template build the pages from it: one table for all, and a page for each player.
A sports site does not have a hand-written page for every player. It has one template, filled in from data.
Writing every page by hand stops working when there are many of them. A template is HTML with gaps: {{ p }} shows a value, and {% for p in players %} ... {% endfor %} repeats a part for each item in a list. render_template_string(PAGE, players=players) fills in the template with the values you pass by name. Each player here is a dictionary, which looks a value up by its name (its key): p["name"].
Make your prediction first. Help opens once you've locked it in.
from flask import Flask, render_template_string
app = Flask(__name__)
players = ["Ada", "Sam", "Mia"]
PAGE = """<h1>Players</h1>
<ul>
{% for p in players %}
<li>{{ p }}</li>
{% endfor %}
</ul>"""
@app.route("/")
def home():
return render_template_string(PAGE, players=players)
app.run()
The li line is written once in the template. What does {% for %} do with it?
Run it, then click a player's name. The address /player/<name> has a part that changes: whatever is in that place of the address is passed to the function as name. Try /player/Zoe in the address bar. Then add a fifth player to the list and run again.
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: {{ 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: {{ 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()
What the function receives.
A page and a status code.
A template with nothing to show.
The page shows an empty list, though players has two names. Click the line that is wrong.
Show each player's wins in a third column.
Add a Wins heading cell, and a cell with {{ p["wins"] }} in the row.
Add /group/<group>, a page listing only that group's players (try /group/Players), and link to it from each player's page.
A new route with a part that changes, like the player route, and a function that keeps only the players in that group.
@app.route("/group/<group>"), then def group_page(group): with a loop that appends the matching players to a list.
Reuse PAGE: render_template_string(PAGE, players=members). Link to it from the player page.
Show each player's wins in a third column of the table.
Run your program. This ticks when your code does all of the above.
Add a page for each group, /group/<group>, listing only its players.
Run your program. This ticks when your code does all of the above.
Add a /leaders page: the players with 3 or more wins, as a numbered list, made with a template of your own.
Add a /leaders page listing the players with 3 or more wins, built with a template.
Run your program. This ticks when your code does all of the above.
On a computer, templates usually live in their own files in a templates folder, loaded with render_template("club.html", ...); the {{ }} and {% %} work exactly the same. Next: a form that adds players.
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. About 50 minutes.
Open the teacher guide: answers, solutions, slides and a worksheet.
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.
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.
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.