Students make the club page's sign-up form work with Flask, reading the answers with request.form and redirecting home, then refuse blank names, add a year level and build a form that records a win.
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. <form method="post" action="/join"> sends the form to /join when the button is pressed; each box is sent only if it has a name attribute, under that name (the id is for the label; the bug task has an id and no name, and Flask replies 400 Bad Request). A route accepts a sent form with methods=["POST"] and reads it with request.form["name"]. After changing the data it returns redirect("/"), so the browser shows the home page again (and a reload does not send the form twice). The server checks what it gets (a blank name), because anyone can send anything to an address. Members live in a variable, so they vanish on Run: that motivates the next lesson. In the playground the form is passed to the app by the App tab; on a computer the browser sends it to Flask.
The name attribute on form boxes. Ask students to list what the form sends, box by box, before they look at the route.
| What you will see | A question to ask | The fix |
|---|---|---|
| 400 Bad Request when the form is sent. | Does every box the route reads have a name attribute, spelled the same? | name="year" on the box, request.form["year"] in the route. |
| 405 Method Not Allowed. | Does the route accept POST? | @app.route("/join", methods=["POST"]). |
| The error message never shows. | Is error passed to the template, and does the template show it? | error="..." in render_template_string, and {% if error %}{{ error }}{% endif %} in PAGE. |
One possible solution for each task. Other correct programs are fine.
from flask import Flask, render_template_string, request, redirect
app = Flask(__name__)
members = [
{"name": "Ada", "group": "Champions"},
{"name": "Sam", "group": "Players"},
]
PAGE = """<h1>Chess club</h1>
<h2>Members</h2>
<ul>
{% for m in members %}
<li>{{ m["name"] }} ({{ m["group"] }})</li>
{% endfor %}
</ul>
<h2>Join the club</h2>
{% if error %}<p style="color: crimson">{{ error }}</p>{% endif %}
<form method="post" action="/join">
<label for="name">Name</label>
<input id="name" name="name">
<label for="group">Group</label>
<select id="group" name="group">
<option>Beginners</option>
<option>Players</option>
<option>Champions</option>
</select>
<button>Join</button>
</form>"""
@app.route("/")
def home():
return render_template_string(PAGE, members=members)
@app.route("/join", methods=["POST"])
def join():
name = request.form["name"].strip()
group = request.form["group"]
if name == "":
return render_template_string(PAGE, members=members, error="Please type your name.")
members.append({"name": name, "group": group})
return redirect("/")
app.run()
from flask import Flask, render_template_string, request, redirect
app = Flask(__name__)
members = [
{"name": "Ada", "group": "Champions", "year": "9"},
{"name": "Sam", "group": "Players", "year": "8"},
]
PAGE = """<h1>Chess club</h1>
<h2>Members</h2>
<ul>
{% for m in members %}
<li>{{ m["name"] }} ({{ m["group"] }}, Year {{ m["year"] }})</li>
{% endfor %}
</ul>
<h2>Join the club</h2>
<form method="post" action="/join">
<label for="name">Name</label>
<input id="name" name="name">
<label for="group">Group</label>
<select id="group" name="group">
<option>Beginners</option>
<option>Players</option>
<option>Champions</option>
</select>
<label for="year">Year</label>
<input id="year" name="year">
<button>Join</button>
</form>"""
@app.route("/")
def home():
return render_template_string(PAGE, members=members)
@app.route("/join", methods=["POST"])
def join():
name = request.form["name"].strip()
group = request.form["group"]
year = request.form["year"]
members.append({"name": name, "group": group, "year": year})
return redirect("/")
app.run()
from flask import Flask, render_template_string, request, redirect
app = Flask(__name__)
players = [
{"name": "Ada", "wins": 5},
{"name": "Sam", "wins": 4},
{"name": "Mia", "wins": 1},
]
PAGE = """<h1>Results</h1>
<table>
<tr><th>Player</th><th>Wins</th></tr>
{% for p in players %}
<tr><td>{{ p["name"] }}</td><td>{{ p["wins"] }}</td></tr>
{% endfor %}
</table>
<h2>Record a win</h2>
<form method="post" action="/win">
<select name="player">
{% for p in players %}
<option>{{ p["name"] }}</option>
{% endfor %}
</select>
<button>Add a win</button>
</form>"""
@app.route("/")
def home():
return render_template_string(PAGE, players=players)
@app.route("/win", methods=["POST"])
def win():
name = request.form["player"]
for p in players:
if p["name"] == name:
p["wins"] = p["wins"] + 1
return redirect("/")
app.run()
Stretch: Refuse a name that is already a member, with its own message.
Support: Give students the if with the error return, and ask them only to add the error to the template.
A form box has id="name" but no name attribute. What does the server get from it?
Nothing: only boxes with a name attribute are sent, under that name.
Next lesson saves the members to a CSV file so they survive a Run. Later, a JSON route lets other programs read the list.