This video is available to students only

Building (HTML) Forms

Handling Forms

Forms are often the main way end users will interact with our web application, so it's important to make usable through the use of styling and error messages. A lot of the complexity implementing forms comes from data validations, adding CSRF protection, and the tediousness of writing HTML for each for field. In this chapter, we will be adding more forms to Yumroad and using a library called WTForms to make the implementation easier.

Writing HTML Forms

At this point we have already seen everything we need to render a form and process the input, so we will start with a plain HTML implementation and then switch to use a library that makes it easier.

To start with, we will create a template with a form with two fields that sends a POST request to the current URL.

yumroad-app/yumroad/templates/products/new_plain_form.html
{% extends "base_layout.html" %}

{% block title %} New Product {% endblock %}

{% block content %}
  <div class="container">
    <form method="POST">
      <label for="name">Name</label>
      <input type="text" class="form-control" name="name" />

      <label for="description">Description</label>
      <input type="textarea" class="form-control" name="description" />

      <br />
      <input type="submit" class="btn btn-primary"/>
    </form>
  </div>
{% endblock %}

To render the form, we will create a route that will handle GET requests to render the form.

@products.route('/create')
def create():
    return render_template('products/create.html')

To make the same route also handle the POST requests that will be sent when the form is submitted, we need to tell Flask that this route supports POST requests. To indicate that this route can support POST requests, we can specify the methods argument of the @products.route decorator and specify it to be a list of the two supported HTTP request methods (GET and POST).

To extract information about the current HTTP request, Flask provides a request object that you can import that will provide the details of the current HTTP request that Flask is handling (including form values and HTTP metadata).

If the request is a POST request (which we can check from request.method), we want to extract the data from the form (available in request.form) and create a new product. Once we have created the record, we can then redirect users using the redirect function from Flask to send them to the associated product page for the record that was just created. If the request is a GET, we can render the form like before.

from flask import render_template, redirect, request, ...

@products.route('/create', methods=['GET', 'POST'])
def create():
    # Using plain old request handling
    if request.method == 'POST':
        product = Product(name=request.form['name'], description=request.form['description'])
        db.session.add(product)
        db.session.commit()
        return redirect(url_for('product.details', product_id=product.id))
    return render_template('products/create.html')

At this point, we can submit the form and create new records for products that have a name of at least three characters. As soon as we submit an invalid input, like a product with no input whatsoever, we get a ValueError exception.

Value Error
Value Error

Our forms should ideally display the errors inline and perform validations themselves. For simple validations like requiring an input, you might be satisfied with relying on HTML5 input validations like required, but as the validations grow complex or involve server side logic, you will want to rely on WTForms. In this next section we will look at what it will take to convert our manually created form to use WTForms.

No discussions yet