8. Forms and request data

Every page so far has answered the same plain visit: the browser asks for a URL and Flask hands back a page. Real apps also take input: a search term in the URL, or a form the user fills in and submits. Flask exposes both through one object, request, and the only thing you need to know first is which HTTP method carried the data.

GET versus POST

Two HTTP methods cover almost everything you will do in this chapter:

  • GET asks for a page and can carry small values in the URL's query string (/search?q=radiohead). GET requests should not change anything on the server, which is why a browser will happily repeat them on refresh.
  • POST submits data in the request body, out of the URL. Use it for anything that changes state: creating, updating, or deleting. Browsers warn before resubmitting a POST, because repeating it would repeat the change.

Reading query strings with request.args

Query-string values live in request.args, a dictionary-like object. Use .get() with a default so a missing value never raises:

app.py
from flask import Flask, request, render_template

app = Flask(__name__)


@app.route("/search")
def search():
    # /search?q=radiohead  ->  query == "radiohead"
    # /search              ->  query == ""
    query = request.args.get("q", "")
    return render_template("search.html", query=query)

The matching template renders a form that submits with GET, so the search term ends up back in the URL. That makes the result shareable and bookmarkable, which is exactly what you want for a search:

templates/search.html
{% if query %}

You searched for: {{ query }}

{% endif %}

The name="q" on the input is what becomes ?q=... in the URL, which is the same q the route reads with request.args.get("q"). The name is the contract between the form and the route.

Handling submissions with request.form

When a form changes something, switch it to POST and read the submitted fields from request.form. A single route can serve both methods: GET shows the empty form, POST processes the submission. Declare the methods the route accepts, then branch on request.method:

app.py
from flask import Flask, request, render_template, redirect, url_for

app = Flask(__name__)


@app.route("/note", methods=["GET", "POST"])
def note():
    if request.method == "POST":
        text = request.form.get("text", "").strip()
        if text:
            save_note(text)            # your own logic
        return redirect(url_for("note"))   # see Post/Redirect/Get below
    return render_template("note.html")
templates/note.html

The Post/Redirect/Get pattern

Notice the route does not render a page after a successful POST; it returns a redirect(). This is the Post/Redirect/Get pattern, and it exists to solve a real annoyance: if you render the result directly, the browser's address bar still holds the POST, so a refresh resubmits the form and saves the note twice. Redirecting to a plain GET page means a refresh just reloads that page harmlessly. You will use redirect() properly on the next page, alongside sessions and flash messages.