Lesson 3A

Web Forms & Databases

Share Tweet

Contents

  1. Resources
  2. Part 1: Web Forms
    1. Flask-WTF Configuration
    2. Form Class
    3. HTML Rendering of Forms
    4. Form Handling in View Functions
  3. Part 2: Databases
    1. Database Management with Flask-SQLAlchemy
    2. Model Definition
    3. Database Operations
    4. Database Use in View Functions
  4. Flashing Announcement Banner
  5. Exercises
  6. Further Reading
  7. Troubleshooting

Resources

  • Textbook: "Miguel Grinberg. Flask Web Development, Second Edition. O'Reilly Media, Inc., 2018." Some up-to-date modifications of the following chapters will be also presented on this page.
    • Chapter 4 - Web Forms
    • Chapter 5 - Databases
    • Lesson 3A - Sample Code
  • Python IDE:

Part 1: Web Forms

In the previous lesson, we discussed a basic Flask application that the server renders information and responses to the users (clients). We can see that this sort of application is unidirectional, since the information flows from the server to the client. In reality, most applications need to have information flowing in both directions, in particular, users provide information and the server receives and processes the submitted information.

A popular way to get information from users is through a web form. In HTML, we can easily create a web form by using the <form> element that contains different types of input elements, such as text fields, checkboxes, radio buttons, submit buttons, etc. In Flask, to obtain the information submitted by users via web form in the form of a POST request, we can employ the request object which exposes all the submitted information.

The Flask-WTF extension makes working with web forms a much more pleasant experience. This extension is a Flask integration wrapper of the WTForms package. Flask-WTF and its dependencies can be installed with pip:

$ pip install flask-wtf

Flask-WTF Configuration

Flask-WTF expects the application to have a SECRET_KEY configured. A secret key is a string with any random and unique content that is used as an encryption or signing key to protect a form against cross-site request forgery (CSRF) attacks. Flask uses this key to protect the contents of the user session against tampering. You should pick a strong secret key in your application and make sure that this string is not known by anyone. To configure a secret key for an application, as shown in Fig. 1, you can insert these two lines on top of your application configuration, e.g., hello.py.

Fig 1. Flask-WTF with SECRET_KEY configuration
Fig 1. Flask-WTF with SECRET_KEY configuration
Form Class

When using Flask-WTF, each web form is represented in the server by a class that inherits from the class FlaskForm. The class defines the list of fields in the form, each represented by an object. Each field object can have one or more validators attached. A validator is a function that checks whether the data submitted by the user is valid. Fig. 2 shows a simple web form that has a text field and a submit button. The optional validators argument included in the StringField constructor defines a list of checkers that will be applied to the data submitted by the user before it is accepted. The DataRequired() validator ensures that the field must be submitted. We note that the InputRequired() also validates the submitted field is empty or not, but it behaves differently from the DataRequired() in that InputRequired() looks that form-input data was provided, and DataRequired() looks at the post-coercion data.

Fig 2. hello.py: Implement a form class
Fig 2. hello.py: Implement a form class

Flask-WTF supports a variety of HTML fields such as BooleanField, DateField, FileField, PasswordField, RadioField. Flask-WTF also has many useful built-in validators such as DataRequired, Email, URL, as presented in the figure below.

Fig 3. WTForms validators.
Fig 3. WTForms validators [2]
HTML Rendering of Forms

(This section is different from the one presented in the textbook, since we use Bootstrap-Flask instead of the out-dated Flask-Bootstrap)

The Bootstrap-Flask extension provides a high-level helper function that renders an entire Flask-WTF form using Bootstrap's predefined form styles, all with a single call as {% from 'bootstrap4/form.html' import render_form %}. The import directive works in the same way as regular Python scripts do and allows template elements to be imported and used in many templates. And then, to render the form defined from back-end to front-end, we just simply pass the form object the render_form() function, as follows {{ render_form(form) }}. In Fig. 4, we show how to modify the index.html template to include the form and render it on the web page. You should note that the way we use conditional statements to dynamically render the data on the web page, i.e., the name variable.

Fig 4. index.html: Rendering form on a web page
Fig 4. index.html: Rendering form on a web page
Form Handling in View Functions

To render the form on the web page, we will update the index() function in hello.py. First, we declare the HTTP methods in the app.route() decorator to register this function as a handler for GET and POST requests. By default, if you do not explicitly specify the HTTP methods, Flask will register the view function to handle GET requests only. Second, we define how the function renders the form and handles the form data. The form.validate_on_submit() statement will only return True when the submitted information is valid, and vice versa. This statement also indicates to Flask if the form has been submitted or not. The render_template() call in the last line renders the template. This time, we will pass the form object and the name variable. You should note that this variable will be None if the form is not submitted.

Fig 5. hello.py: index() function handles a web form with GET and POST request methods
Fig 5. hello.py: index() function handles a web form with GET and POST request methods

Fig. 6 shows how the form is rendered on a web page when a user initially visits the site. When the user submits a name, the application responds with a personalized greeting, as shown in Fig. 7.

Fig 6. An example of a Flask-WTF web form
Fig 6. An example of a Flask-WTF web form
Fig 7. The result after submission
Fig 7. The result after submission
Recap: Web forms with Flask-WTF

It is a 3-step process to implement a web form with Flask-WTF on Flask, including (1) define a class to represent a web form by inheriting FlaskForm class, (2) defining the procedure to handle and process data submitted by users together with a corresponding URI in @app.route('/path') decorator, and (3) implementing an HTML file (template) to render the form on a web page and mapping to view function via render_template('path/to/html', form=form) function.


Part 2: Databases

Python has a variety of packages for most database engines, both open source and commercial such as MySQL, Postgres, SQLite, Redis, MongoDB, CouchDB, or DynamoDB. There are a number of factors to evaluate when choosing a database framework, i.e., ease of use, performance, portability, Flask integration. Based on these goals, we decide to choose Flask-SQLAlchemy for demonstration. Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application. It aims to simplify using SQLAlchemy with Flask by providing useful defaults and extra helpers that make it easier to accomplish common tasks.

Fig 8. Database configuration
Fig 8. Database configuration
Database Management with Flask-SQLAlchemy

Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application. It aims to simplify using SQLAlchemy with Flask by providing useful defaults and extra helpers that make it easier to accomplish common tasks. Flask-SQLAlchemy is incredibly easy for basic applications, and readily extends for larger applications. Flask-SQLAlchemy can be installed and updated using pip:

$ pip install -U Flask-SQLAlchemy

For the common case of having one Flask application all you have to do is to create your Flask application, load the configuration of choice and then create the SQLAlchemy object by passing it the application. Once created, that object then contains all the functions and helpers from both sqlalchemy and sqlalchemy.orm. Furthermore it provides a class called Model that is a declarative base which can be used to declare models. Fig. 8 shows how to initialize and configure a simple SQLite database. The db object instantiated from the class SQLAlchemy represents the database and provides access to all the functionality of Flask-SQLAlchemy.

Fig 9. Database configuration
Fig 9. Database configuration
Model Definition

The term model is used when referring to the persistent entities used by the application. In the context of an ORM (Object Relational Mapping), a model is typically a Python class with attributes that match the columns of a corresponding database table. The database instance from Flask-SQLAlchemy provides a base class for models as well as a set of helper classes and functions that are used to define their structure Fig. 9 defines the concept level of roles and users tables, and their implementations are shown in Fig. 10.

Fig 10. An entity relationship diagram (ERD) of roles and users entities [2]
Fig 10. An entity relationship diagram (ERD) of roles and users entities [2]
Fig 11. hello.py: Implement database models and relationships
Fig 11. hello.py: Implement database models and relationships

The __tablename__ class variable defines the name of the table in the database. We should explicitly define the table's name, since Flask-SQLAlchemy will assign a default name if __tablename__ is omitted. The db.Column() defines the attributes of the model where the first argument is the type of the database column such as Integer, BigInteger, Float, UnicodeText, DateTime. The second argument of db.Column() is column options such as primary_key, unique, index, nullable, default. The __repr__() returns a readable string representation of the model that can be used for debugging and testing purposes.

Relationships — Fig. 9 presents a one-to-many relationship from roles to users, it means that one role can belong to many users, but each user can have only one role. As shown in Fig. 10 (line 26), the role_id column added to the User model is defined as a foreign key, and that establishes the relationship. The 'roles.id' argument to db.ForeignKey() specifies that the column should be interpreted as having id values from rows in the roles table.

Database Operations

After implementing database models, we can now perform database operations, and the best way to learn how to work with database operations on models is in a Python shell with the flask shell command. Section Database Operations in Chapter 5 [2] presentes some common database operations such as (1) create tables, (2) insert rows, (3) modify rows, (4) delete rows, and (5) query rows.

In order to run this lesson demo program, we need to create the tables (User and Role), and then insert some records into them. To do so, you should execute the following commands:

$ flask shell
>>> from hello import db
>>> db.create_all()
>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role)
>>> user_susan = User(username='susan', role=user_role)
>>> user_david = User(username='david', role=mod_role)
>>> db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])
>>> db.session.commit()
>>> print(user_david) (for testing purposes)
<User 'david'>
>>> quit()

Database Use in View Functions

The following figures show an update to the root route that records information submitted by users in the database. In this modified version of the application, each time an information is submitted, the application checks for it in the database using the filter_by() query filter. A known variable is written to the user session so that the information can be automatically sent to the template, where it is used to customize the greeting.

Fig 12. hello.py: database use in view functions
Fig 12. hello.py: database use in view functions
Fig 13. templates/index.html: customized greeting in template
Fig 13. templates/index.html: customized greeting in template

Flashing Announcement Banner

In some cases, your application should display/flash an announcement banner when users perform an action. For example, if there is a mistake when users submit information on a web form, the application should display a banner to inform users where and how to correct the error. In Fig. 14 and 15, we show the implementation of how to render a message from a view function to its corresponding template. In the sample code, the 'Looks like you have changed your name!' message will be displayed on the homepage where the NameForm is rendered when users enter the second name which is different from the first one.

Fig 14. hello.py: Implement message flashing in a view function
Fig 14. hello.py: Implement message flashing from a view function
Fig 15. base.html: Implement message flashing in the base template
Fig 15. base.html: Implement message flashing in the base template

Exercises

  1. The hello.py presented in Part 1 - Web Forms section does have a usability problem. If you enter your name and submit it, and then click the refresh button in your browser, you will likely get an obscure warning that asks for confirmation before submitting the form again, as shown in below figure. By taking advantage of user session, you can get rid of this issue since user session is a private storage of each connected client and also able to "remember" things from one request to the next ones. The function index() presented in Chapter 4 should be modified by employing Redirects and User Sessions techniques. You can refer to section Redirects and User Sessions in Chapter 4 in [2].
  2. Fig 16. A warning when resubmit a web form
    Fig 16. A warning when resubmit a web form
  3. Implementing a web form in order to allow a user register a forum account. The information of an account should consist the basic information such as name, gender, email, password, date of birth, nationality, and a checkbox to require a client to read-and-accept the terms of services. The input information must be validate first, and if they are valid, the information will be stored in a database. In this exercise, you also need to configure a small database (SQLite, MySQL, ...) to support the web app.
  4. Sometimes it is useful to give the user a status update after a request is completed. This could be a confirmation message, a warning, or an error. A typical example is when you submit a login form to a website with a mistake and the server responds by rendering the login form again with a message above it that informs you that your username or password is invalid. Continue from Exercise 2, you need to update the web form to make it more responsive, for example, the web form will render an alert when an account is successfully created, or the user account did exist in the database. You can refer to section Message Flashing in Chapter 4.
  5. Continue from Exercise 2, you need to implement Recaptcha mechanism to protect your web form from spam and abuse. To implement recaptcha, you can use Google reCAPTCHA service and refer to this tutorial of Flask-WTF.

Further Reading

  1. Flask-WTF — User's Guide
  2. Flask-SQLAlchemy

Troubleshooting

  1. If you get this error message - Exception: Install 'email_validator' for email validation support. - when creating a database migration by executing such as flask db upgrade command, you can try to install email_validator extension by executing the command pip install email-validator in the current working Python environment.

J4F 😊

Fig 17. J4F
Fig 17. J4F (Source: Internet)

Relaxing 🧘