Forms

Part 1, Chapter 6


This chapter looks at how to create forms for acquiring user input data.

Prerequisites

This chapter looks at how forms are implemented and validated in HTML. If you've never worked with HTML or forms before, there are some great resources available from the Mozilla Developer Network (MDN):

Forms

Forms (also referred to as 'web forms' or 'HTML forms') provide a way for the user to interact with a web app. Typically, forms allow the user to enter data, which is sent to the server to be processed.

Some examples of forms:

  • User registration
  • Contact
  • Feedback

In this chapter, we'll implement a form for submitting the following data for a stock:

  • stock symbol
  • number of shares purchased
  • purchase price

Form for Stock Data

To create a form, we'll add:

  1. The HTML code (in a template) to display the form
  2. A view function to serve and process the form submission

Template (HTML)

Let's start by creating the HTML code for the form by creating a new file called templates/add_stock.html:

{% extends "base.html" %}

{% block content %}
<h2>Add a Stock:</h2>
<form method="post">
  <label for="stockSymbol">Stock Symbol:</label>
  <input type="text" id="stockSymbol" name="stock_symbol" />
  <br>
  <label for="numberOfShares">Number of Shares:</label>
  <input type="text" id="numberOfShares" name="number_of_shares" />
  <br>
  <label for="purchasePrice">Purchase Price ($):</label>
  <input type="text" id="purchasePrice" name="purchase_price" />
  <br>
  <input type="submit">
</form>
{% endblock %}

Form Element

The <form> element is used to define a form, which can contain any number of inputs that the user can provide. There are two key attributes often used with the <form> element:

  • action defines the location (URL) where the form should be sent to when submitted
  • method defines which HTTP method (typically, POST) to send the data with

In the above example, we set the method to POST and since we left off the action attribute, the form submission will be sent to the same URL that's used to serve up the form in the browser.

Input Elements

In HTML, the typical approach for defining an input is to create a <label> element to define a description of what to enter and then to have a separate <input> element for collecting the input:

  <label for="stockSymbol">Stock Symbol:</label>
  <input type="text" id="stockSymbol" name="stock_symbol" />

These two elements are linked together via the stockSymbol identifier. The <label> element provides the description for the <input> element with the matching id of stockSymbol.

Make sure to include the name attribute in each <input> element, as this is used by Flask to parse the input data from the request object. We'll look at this in detail shortly.

The type of an input element defines the expected type of data:

  • button
  • checkbox
  • date
  • email
  • file
  • number
  • password
  • radio
  • submit
  • text (default)
  • url

In this form, we defined three <input> elements with type="text".

The final <input> element, from the example, has a type of submit, which will be rendered as an HTML button. When clicked, the form data will be sent to the server-side via a POST request.

Flask Route

To serve up this form, add a new route and view function to app.py:

@app.route('/add_stock')
def add_stock():
    return render_template('add_stock.html')

Now, when the '/add_stock' URL is accessed, the templates/add_stock.html template will be rendered for the user to see:

Add Stock Page - Display Only

While we're able to see the form, we haven't set up a route to handle the form submission. In fact, if you click the 'Submit' button, you will get a 404 (Method Not Allowed) error:

<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>

Processing Form Data

Currently, the add_stock() function only handles GET requests:

@app.route('/add_stock')
def add_stock():
    return render_template('add_stock.html')

Do you remember how to allow additional HTTP methods?

@app.route('/add_stock', methods=['GET', 'POST'])
def add_stock():
    return render_template('add_stock.html')

Next, in order to perform a different operation when the HTTP method is a POST, make the following changes:

from flask import request

@app.route('/add_stock', methods=['GET', 'POST'])
def add_stock():
    if request.method == 'POST':
        # Print the form data to the console
        for key, value in request.form.items():
            print(f'{key}: {value}')

    return render_template('add_stock.html')

This diagram provides an illustration of what happens when the form is submitted:

Flask Server Request for POST HTTP Method

Request

The user provided data is available via the keys on the form property from the Flask request object:

# Print the form data to the console
for key, value in request.form.items():
    print(f'{key}: {value}')

To see this in action, navigate to the '/add_stock' URL and enter the following data:

  • Stock Symbol: AAPL
  • Number of Shares: 15
  • Purchase Price ($): 302.17

You should see the inputs printed in the terminal output from the Flask development server:

127.0.0.1 - - [12/Apr/2020 22:14:28] "GET /add_stock HTTP/1.1" 200 -

stock_symbol: AAPL
number_of_shares: 15
purchase_price: 302.17

The values printed to the console are the key/value pairs from the request.form property. The keys should look familiar, as they are the same as the values assigned to the name attributes from the <input> elements in our form:

<form method="post">
  <label for="stockSymbol">Stock Symbol:</label>
  <input type="text" id="stockSymbol" name="stock_symbol" />  <!-- Flask: request.form['stock_symbol'] -->
  <br>
  <label for="numberOfShares">Number of Shares:</label>
  <input type="text" id="numberOfShares" name="number_of_shares" />  <!-- Flask: request.form['number_of_shares'] -->
  <br>
  <label for="purchasePrice">Purchase Price ($):</label>
  <input type="text" id="purchasePrice" name="purchase_price" />  <!-- Flask: request.form['purchase_price'] -->
  <br>
  <input type="submit">
</form>

Form Validation

Form validation is a set of checks to constrain the data input by the user. The goal of form validation is to ensure that the data is in the correct format.

You've probably seen examples of form validation in practice on different websites:

  • "This field is required"
  • "Please enter a valid email address"
  • "Your password needs to be between 8 and 30 characters long and contain one uppercase letter, one symbol, and two numbers."

Client-Side vs. Server-Side Form Validation

User inputted data can be processed on both the client-side (web browser) or the server-side (Flask application).

While both approaches are valid, I've come to prefer client-side form validation for two reasons:

  1. There's no need to waste time sending data to the server if it's invalid to begin with
  2. HTML form validation has significantly improved over the past few years

Here are some key validation checks that can be defined in the HTML code for a form:

  • required specifies that the field needs to be filled in
  • minlength and maxlength specifies the minimum and maximum length of text input (strings)
  • pattern uses a regular expression to define a pattern that data needs to adhere to

Additionally, the different type attributes have their own, built-in data validation.

So, when the form is submitted, the web browser checks that the data is in the correct format based on the specified constraints. If the data passes the validation checks, it will be sent to the server for processing.

Invalid Data Input

To illustrate how valuable form validation can be, let's attempt to send some invalid data. Navigate to the form in your browser and enter the following data:

Field Value Description
Stock Symbol 15 Invalid stock symbol!
Number of Shares Empty field!
Purchase Price ($) 302.17 Valid

Since there are no validation checks on the input fields, this data is passed directly to our view function and the following output is logged to the console:

127.0.0.1 - - [13/Apr/2020 08:49:46] "GET /add_stock HTTP/1.1" 200 -
stockSymbol: 15
numberOfShares:
purchasePrice: 302.17

Fortunately, this is a fairly easy fix.

Updated Form

While we will use a number of different form validators in this course, let's start by using the required and pattern validators:

{% extends "base.html" %}

{% block content %}
<h2>Add a Stock:</h2>
<form method="post">
  <label for="stockSymbol">Stock Symbol (<i>required</i>):</label>
  <input type="text" id="stockSymbol" name="stock_symbol" required pattern="[A-Z]{1,5}" />
  <br>
  <label for="numberOfShares">Number of Shares (<i>required</i>):</label>
  <input type="text" id="numberOfShares" name="number_of_shares" required />
  <br>
  <label for="purchasePrice">Purchase Price (<i>required</i>) ($):</label>
  <input type="text" id="purchasePrice" name="purchase_price" required />
  <br>
  <input type="submit">
</form>
{% endblock %}

The form no longer allows an empty <input> field due to the required attribute. Additionally, there's a regex pattern used to check the format of the stock symbol. The regex pattern is defined as pattern="[A-Z]{1,5}", which means that there needs to be between 1 and 5 ({1,5}) capital letters ([A-Z]).

Some examples:

  • AAPL - valid
  • appl - invalid
  • Patrick - invalid
  • T123 - invalid
  • T - valid

Since each input is required, we should inform the user of this so that they are not caught off guard by an error message when they submit the form:

<label for="stockSymbol">Stock Symbol (<i>required</i>):</label>

Try submitting the form without filling in the stock symbol field:

Add Stock Form - Empty Field Error Message

The web browser performs the form validation each time the 'Submit' button is clicked. Since the stock symbol field was not filled in, the web browser reports an error back to us and the HTTP POST request to the server is NOT sent since the form is invalid.

Next, try submitting the form with an invalid entry in the stock symbol field:

Add Stock Form - Invalid Stock Symbol

Since the stock symbol of '12' does not match the regex pattern for the stock symbol, the web browser reports an error back to us.

While there are lots of options available for form validation in HTML, you can expand this even further by using JavaScript to extend the browser's built-in validators to make a more elegant experience for the end user. Refer to the Form Validation guide from the Mozilla Developer Network for more details.

Conclusion

In this chapter we looked at how to create and process forms. You should now be able to:

  1. Explain how forms are processed on a web app
  2. Define a form in an HTML template
  3. Change the allowable HTTP methods for a Flask view function
  4. Parse form data in a view function using the request object
  5. Implement basic form validation in HTML

Please note that this chapter does not discuss a common attack known as Cross-Site Request Forgery (CSRF). We'll cover how to prevent this attack in an upcoming chapter using the Flask-WTForms module.




Mark as Completed