Skip to content
Phil Kirlin edited this page Mar 24, 2022 · 12 revisions

Using forms with Flask

So far we have no way to get any input from the user. We can't use Python's input() function because that is tied to the keyboard or standard input. What we will do is use HTML forms to display fields that the user can type into.

There are a number of complications that will come into play here. The first one deals with the way web browsers and web servers exchange information. This is done through what are called "requests" or "transactions." When a web browser connects to a web server, it makes a "request" of that server by saying something like "Give me the webpage at URL /time". Then the web server sends the HTML code of that webpage back to the browser (or HTML code for an error message) and the browser displays it. If the browser reloads the page, or visits another page, that will generate a second request and a second response.

Normally, when we use HTML forms, we will need two complete rounds of request-response to generate something useful.
The reason is that the first request the web browser makes will be for the server to send back the HTML code with the (blank) form. Then the user fills out the form, presses the Submit button, and then a second request is made to the server, including the contents of the form, and a second response is generated, this time usually by processing the form and sending back some kind of webpage indicating the result.

This is analogous to going to Rhodes Express and filling out paperwork there. First you walk up to the desk and ask for a specific form (Request #1). The form is handed back to you, blank (Response #1). You then fill out the form and hand it in (Request #2). The person behind the desk then response in an appropriate way, for instance, by saying "We will process this form ASAP," or "This form is filled out incorrectly" (Response #2).

The second complication is that HTTP (Hypertext Transfer Protocol), the set of standards that web browsers and web servers use to talk to each other, is stateless. That means that by default, when your web browser makes multiple requests of a web server, there is no way to maintain a set of variables automatically between the first and second request-response cycles. It's like having a conversation with a person where you have to constantly remind them the subject that you are talking about.

There are a number of technologies that have been developed to get around this problem (cookies is the best known one), and we will see how to use them later. For now, just remember that every time you want to send information to the web server, it will have forgotten any previous information you sent.

Our first form

We will see how to create a form that lets the user generate random numbers between 1 and a number they choose.

First we will create the HTML template. Make a new HTML template or edit your old one for random.html:

<html>
  <head>
    <title>Random</title>
  </head>
<body>
  <h1>Random numbers are fun!</h1>
  {% if number is not defined %}
    <form action="{{ url_for('pick_number') }}" method="post">
    Pick an upper limit: <input type="text" name="upperlim">
    <input type="submit" value="Generate Number">
    </form>
  {% else %}
    Your random number is: {{ number }}
  {% endif %}
</body>
</html>

Edit your Python code. First change the Flask import line to

from flask import Flask, render_template, request

Next, change the random route:

@app.route("/random", methods=['get', 'post'])
def pick_number():
    if "upperlim" not in request.form:
        return render_template("random.html")
    else:
        upper = int(request.form['upperlim'])
        n = random.randint(1, upper)
        return render_template("random.html", number=n)

Visit http://blahblah.repl.co/random and give it a try!

How does this work?

Before we talk about that, we need to talk about...

GET vs POST

When a browser makes a request to a web server, this is usually done via either a "GET" request or a "POST" request. The original reasons for choosing these words are not as relevant as they used to be. The two methods differ in how the browser sends information in an HTML form to a web server (if no HTML form information is being sent from the browser to the server, the distinction is irrelevant).

In a GET request, the HTML form information is encoded as part of the URL. If you have ever glanced at a URL and seen something like http://www.server.com/page.html?name=Snoopy&type=dog&friend=CharlieBrown, that happens when the user submits a form via GET with three HTML form variables, name, type, and friend.

GET requests can become problematic in a number of ways. Some web servers limit the length that a URL can be, so it can be dangerous to submit the results of a long HTML form via GET, because the URL might get truncated. Furthermore, because all data in the form is passed via the URL without any encryption or encoding, this is a very poor method for submitting any kind of sensitive information, like passwords or credit card numbers.

In a POST request, the information in the HTML form is submitted separately from the URL. The information is still part of the same request as the URL, but arrives separately. This has advantages and disadvantages from the GET method. The advantages are that there is typically no limit on the length of data submitted through POST, so no truncating will ever happen if there is a lot of data to submit (for example, uploading a file). Furthermore, the data isn't part of the URL, so sensitive information isn't shown in the browser's address bar (it is still not encrypted, however, so it's not that much more secure). The disadvantage of POST is that the results of a POST request can't be bookmarked in a browser, because a bookmark only stores a specific URL.

All you should remember for this tutorial is that the default HTTP method is GET, but we will use POST to submit forms.

Breaking down the order of events

Let's break this down in the order that the browser and server talk to each other.

  • When you visit http://blahblah.repl.co/random, Python runs pick_number(), because that's the function connected to the /random route.
  • [Python:] Inside pick_number(), if a form has been submitted, then there is a special variable called request.form that holds all the information the user typed into the form. However, the form hasn't been submitted yet, so this variable is not defined, so the random.html template is rendered with no additional variables.
  • [HTML:] Now the template starts generating. Notice the template now has an if statement! Yes, this is possible! The syntax is similar to Python, but slightly different.
  • Since no additional information was sent to the template, there is no variable called number, so the if statement is true, so the first part of the if is generated, and the second part is skipped.
  • The action parameter for the HTML form specifies the URL that should receive the information in the form. In this case, we use the special function url_for to automatically generate the URL connected to the pick_number function, which in this case, is /random. You should always use url_for to generate these URLs, this way you can easily change the route to a different name if desired later.
  • The HTML from the template contains a form that is presented to the user. The user fills in the value for the field and clicks Submit.
  • A second request to the server begins, again with the route /random. However, in this second request, we are submitting via POST, and we are submitting form data. Specifically, each item in the form is turned into a variable/value pair that will appear in the request.form variable in Python. Notice that in the HTML form, the input field has a specifies name="upperlim". That means, request.form (a Python dictionary) will contain an entry form the key upperlim.
  • [Python:] We run pick_random() again. This time, however, there is a key called upperlim in the request.form dictionary, so we skip to the else part of the code. We create a Python variable called upper (again, there is no rule that Python variables have to have the same names as the form variables), and cast it to an int. Notice this is just like saying int(input(...)) in Python when we want the user to type something in and it must be an integer.
  • [Python:] We generate a random integer n in Python and call render_template, passing the value of n as the value of the keyword argument number.
  • [HTML:] The random.html template starts generating again. This time the if statement is false, because number is defined. So we run the else part and print out our random number.

Whew! This was a lot of work to follow. But hopefully it makes sense.

Errors in forms

Flask does not do input validation, just as Python doesn't (at least not automatically). Try submitting the form with a blank entry, or with text instead of numbers, or a decimal point. You will get the same ValueError: invalid literal for int() with base 10 error you probably remember from CS141.

You try it

Edit your HTML template and the Python code so the user can pick the upper and lower bounds on the random number. If you get that, try making it so the page that prints the random number also prints the lower and upper bounds the user put into the form.

Check your answer below when you're done.

.

.

.

.

.

.

.

. .

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

. .

.

.

.

.

.

.

.

<html>
  <head>
    <title>Random</title>
  </head>
<body>
  <h1>Random numbers are fun!</h1>
  {% if number is not defined %}
    <form action="{{ url_for('pick_number') }}" method="post">
    Pick a lower limit: <input type="text" name="lowerlim"><br>
    Pick an upper limit: <input type="text" name="upperlim"><br>
    <input type="submit" value="Generate Number">
    </form>
  {% else %}
    Your random number is: {{ number }}.<br>
    As a reminder, your desired range was between {{ low }} and {{ high }}.
  {% endif %}
</body>
</html>
@app.route("/random", methods=['get', 'post'])
def pick_number():
    if "upperlim" not in request.form:
        return render_template("random.html")
    else:
        upper = int(request.form['upperlim'])
        lower = int(request.form['lowerlim'])
        n = random.randint(lower, upper)
        return render_template("random.html", number=n, low=lower, high=upper)

Use the main wiki page to navigate, not the list of pages directly above, because those are out of order.

Clone this wiki locally