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:
- The HTML code (in a template) to display the form
- 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 %} <h1>Add a Stock</h1> <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 submittedmethod
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 therequest
object. We'll look at this in detail shortly.
The type
of an input element defines the expected type of data:
- button
- checkbox
- date
- 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:
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:
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:
- There's no need to waste time sending data to the server if it's invalid to begin with
- HTML form validation has significantly improved over the past few years
Client-side validation is great for providing (near) instant feedback to the user when they input invalid or unexpected data; this approach provides a nice user experience.
However, server-side validation is still a best practice (even with client-side validation). Client-side validation could be bypassed and invalid/malicious data could be sent to your server. Check out the Mozilla Developer Network (MDN) guide on client-side validation for more details: https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation
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 inminlength
andmaxlength
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 <em>(required)</em></label> <input type="text" id="stockSymbol" name="stock_symbol" required pattern="[A-Z]{1,5}" /> <br> <label for="numberOfShares">Number of Shares <em>(required)</em></label> <input type="text" id="numberOfShares" name="number_of_shares" required /> <br> <label for="purchasePrice">Purchase Price ($) <em>(required)</em> </label> <input type="text" id="purchasePrice" name="purchase_price" placeholder="$300.00" 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 <em>(required)</em></label>
Try submitting the form without filling in the stock symbol field:
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:
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:
- Explain how forms are processed on a web app
- Define a form in an HTML template
- Change the allowable HTTP methods for a Flask view function
- Parse form data in a view function using the
request
object - 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