Flask Sequence

Reading Form Data

When a user submits a form, Flask collects all the field values into an object called request.form. It works like a dictionary: the keys are the name attributes from your HTML inputs, and the values are whatever the user typed.

The mapping

<!-- input.html -->
<input type="text" name="title" id="title">
<textarea name="description" id="description"></textarea>
<select name="status" id="status">...</select>
# app.py — inside the POST block
title       = request.form["title"]
description = request.form["description"]
status      = request.form["status"]

The name= attribute in the HTML and the key in request.form["..."] must match exactly — same spelling, same capitalisation. name="title"request.form["title"]. Not "Title", not "task_title". Exactly "title".

The silent failure

This is the most frustrating mistake in this unit:

<!-- name= is missing -->
<input type="text" id="title">
title = request.form["title"]  # KeyError — or worse, silently empty

Flask doesn't warn you that the name is missing. The field just isn't there in request.form. You'll either get a KeyError that crashes the POST handler, or — if you use .get() instead of [] — you'll silently save an empty string. Either way, the cause is a missing name= in the HTML.

If your form submits but the data in the CSV is blank or the app crashes on POST, the first thing to check is whether every <input>, <textarea>, and <select> has a name= attribute.

Putting it together

Here's the full round-trip: HTML form field → submitted via POST → read in Flask:

name="project_title" in <input>
    ↓ browser sends form data
request.form["project_title"] in Python
    ↓ you assign it
project_title = request.form["project_title"]
    ↓ you save it
writer.writerow({"project_title": project_title, ...})

Every field follows this exact pattern. Read all five fields the same way, then pass them all to the CSV writer together.

request.form vs request.args

If you built a greet route in Week 1 or Week 2, you may have already used request.args — possibly without fully knowing what it was. These two objects look the same but carry data from different places.

<!-- Form with no method (defaults to GET) -->
<form action="/greet">
    <input type="text" name="name">
    <button type="submit">Go</button>
</form>
# URL becomes: /greet?name=Alice
@app.route("/greet")
def greet():
    name = request.args.get("name")

The data travels in the URL as query string parameters (?name=Alice). You can see it in the address bar. request.args reads it.

<!-- Form with method="POST" -->
<form action="/input" method="POST">
    <input type="text" name="title">
    <button type="submit">Save</button>
</form>
# URL stays: /input — data is in the request body, not the URL
@app.route("/input", methods=["GET", "POST"])
def input_task():
    if request.method == "POST":
        title = request.form["title"]

The data travels in the request body, invisible in the URL. request.form reads it.

The practical difference for this week:

request.args request.form
Form method GET (or no method) POST
Data visible in URL? Yes — ?key=value No
How to read request.args.get("key") request.form["key"]
Good for Short lookups, filters, searches Saving data, sensitive input

request.args is fine for a greet page where the name is in the URL. It's wrong for saving tasks — you don't want form data appearing in the address bar, and GET requests aren't meant to modify data. Use request.form with method="POST" for anything that writes.

Importing request

request comes from Flask. Make sure your import line includes it:

from flask import Flask, render_template, request, redirect, url_for

If request isn't imported, Python will throw a NameError the first time a form is submitted.

Create Shareable URL

Select Sections

© Copyright 2026 by Mr. Carle