Tutorial on Building Web forms in the Python Flask framework, pythonflask
Although the Flask request object provides sufficient support for processing web forms, many tasks become monotonous and repetitive. The HTML code generation and verification of the submitted form data are two good examples.
The Flask-WTF extension gives you a better experience when processing web forms. This extension is a Flask integration that encapsulates WTForms packages unrelated to the framework.
Flask-WTF and its dependency set can be installed using pip:
(venv) $ pip install flask-wtf
1. Cross-Site Request Forgery (CSRF) Protection
By default, Flask-WTF protects various forms of Cross-Site Request Forgery (CSRF) attacks. A csrf attack occurs on another website where a malicious website sends a request to the victim to log on.
To implement CSRF protection, Flask-WTF requires the application to configure an encryption key. Flask-WTF uses this key to generate an encryption token to verify the authenticity of the request form data. The following describes how to configure an encryption key.
Sample hello. py: Flask-WTF Configuration
app = Flask(__name__)app.config['SECRET_KEY'] = 'hard to guess string'
The app. config dictionary is usually the place where the framework, extension, or application itself stores configuration variables. You can use the Standard Dictionary syntax to add configuration values to app. config. The configuration object provides a method to import configuration values from a file or environment.
The SECRET_KEY configuration variable serves as the Flask and some third-party extended general encryption keys. The encryption intensity depends on the value of this variable. Select different keys for each application you build and make sure that this string is not known to anyone else.
Note: to improve security, the key should be stored in an environment variable instead of embedded in the code. This is described in Chapter 7th.
2. Form class
When Flask-WTF is used, each web Form is displayed by a subclass inherited from the Form class. This class defines a set of form fields in the form, each representing an object. Each form field can be connected to one or more validators. validators is a function used to check whether the Input submitted by the user is valid.
The following example shows a simple web form with a text box and a submit button.
Example hello. py: Form class definition
from flask.ext.wtf import Formfrom wtforms import StringField, SubmitField from wtforms.validators import Requiredclass NameForm(Form): name = StringField('What is your name?', validators=[Required()]) submit = SubmitField('Submit')
Fields in a form are defined as class variables, and each class variable specifies a form field type object. In the previous example, the NameForm has a name text box and a submit button. The StringField class indicates a <input> label of the type = "text" attribute. The SubmitField class indicates a <input> label of the type = "submit" attribute. The first parameter of the form field constructor is a label, which is used when the form is rendered to HTML.
The StringField constructor contains the optional parameter validators, which defines a set of checks to verify the data submitted by the user. Required () Verification ensures that the submitted form field is not empty.
Note: The Flask-WTF extension defines the form base class, so it imports data from flask. ext. wtf. Form fields and verification are directly imported from the WTForms package.
The following table shows a set of standard form fields supported by WTForms.
Table WTForms standard HTML form fields
The following shows a set of built-in verification for WTForms.
WTForms Verification
3. HTML rendered forms
Form fields are callable. They are rendered from the template to HTML during the call. Assume that the view function passes a NameForm instance named form to the template, and the template generates a simple HTML form, as shown below:
<form method="POST"> {{ form.name.label }} {{ form.name() }} {{ form.submit() }}</form>
Of course, nothing is returned. To change the appearance of a form, any parameter sent to the form field is converted to an HTML form field attribute. For example, you can specify the form field id or class attribute, then define the CSS style:
<form method="POST"> {{ form.name.label }} {{ form.name(id='my-text-field') }} {{ form.submit() }}</form>
Even if you have HTML attributes, it is very important to try to render the form in this way, so it is best to use a series of form styles that come with Bootstrap as much as possible. Flask-Bootstrap uses Bootstrap's predefined form styles to provide advanced help functions to render the entire Flask-WTF form. All these operations can be completed with only one call. With Flask-Bootstrap, the previous form can be rendered as follows:
{% import "bootstrap/wtf.html" as wtf %}{{ wtf.quick_form(form) }}
The import command serves the same purpose as a regular Python script and allows template elements to be imported and used in many templates. The imported bootstrap/wtf.html file defines the help function to use Bootstrap to render the Flask-WTF form. The wtf. quick_form () function passes in the Flask-WTF form object and uses the default Bootstrap style to render it. Example 4-3 shows the complete hello. py template.
Example templates/index.html: Use Flask-WTF and Flask-Bootstrap to render the form
{% extends "base.html" %}{% import "bootstrap/wtf.html" as wtf %}{% block title %}Flasky{% endblock %}{% block page_content %}<div class="page-header">
Currently, the template has two content zones. The first part is a greeting output by the div class "page-header. The template condition judgment statement is used here. In Jinja2, the format is {% if variable %}... {% else %}... {% endif % }. If the condition is True, the content between if and else is rendered. If the condition is False, the content between else and endif is rendered. The sample template renders the string "Hello, Stranger !" When the name template parameter is undefined. The second part uses the wtf. quick_form () function to render the NameForm object.
4. Start the script
The manage. py file in the top-level directory is used to start the application. This script will be displayed in Example 7-8.
Example manage. py: Startup Script
#!/usr/bin/env pythonimport osfrom app import create_app, dbfrom app.models import User, Rolefrom flask.ext.script import Manager, Shellfrom flask.ext.migrate import Migrate, MigrateCommandapp = create_app(os.getenv('FLASK_CONFIG') or 'default') manager = Manager(app)migrate = Migrate(app, db)def make_shell_context(): return dict(app=app, db=db, User=User, Role=Role)manager.add_command("shell", Shell(make_context=make_shell_context))manager.add_command('db', MigrateCommand)if __name__ == '__main__': manager.run()
This script starts when you create an application. Use the environment variable FLASK_CONFIG. If it has been defined, the configuration is obtained from it. If it does not exist, the default configuration is used. The Flask-Script, Flask-Migrate, and custom context for Python shell are initialized.
For convenience, a line of execution environment will be added, so that the execution script can replace the lengthy python manage. py on the Unix-based operating system through./manage. py.
5. Requirement documents
The application packages include the requirements.txt file to record all dependency packages, including the exact version number. This is important because the virtual environment can be regenerated on different machines, such as deploying applications on machines in the production environment. This file can be automatically generated using the following pip command:
(venv) $ pip freeze >requirements.txt
Update this file after installing or updating a package. The following is an example of a requirement file:
Flask==0.10.1Flask-Bootstrap==3.0.3.1Flask-Mail==0.9.0Flask-Migrate==1.1.0Flask-Moment==0.2.0Flask-SQLAlchemy==1.0Flask-Script==0.6.6Flask-WTF==0.9.4Jinja2==2.7.1Mako==0.9.1MarkupSafe==0.18SQLAlchemy==0.8.4WTForms==1.0.5Werkzeug==0.9.4alembic==0.6.2blinker==1.3itsdangerous==0.23
To replicate a virtual environment perfectly, run the following command to create a new virtual environment:
(venv) $ pip install -r requirements.txt
When you read this file, the version number in the example requirements.txt may be outdated. Try the latest package if you like it. If you encounter any problems, you can roll back to the specified version that is compatible with the application in the requirement file at any time.
6. Unit Test
This application is so small that it does not require too many tests, but as an example, two simple test definitions are displayed in Example 7-9.
Example tests/test_basics.py: unit test
import unittestfrom flask import current_app from app import create_app, dbclass BasicsTestCase(unittest.TestCase): def setUp(self): self.app = create_app('testing') self.app_context = self.app.app_context() self.app_context.push() db.create_all() def tearDown(self): db.session.remove() db.drop_all() self.app_context.pop() def test_app_exists(self): self.assertFalse(current_app is None) def test_app_is_testing(self): self.assertTrue(current_app.config['TESTING'])
The compiled test uses the standard unittest package in the Python standard library. The setUp () and tearDown () Methods run before and after each test, and each method must start with test _ as a test.
Suggestion: If you want to learn more about unit test using the unittest package of Python, refer to the official documentation.
The setUp () method tries to create a test environment, similar to running an application. First, it creates application configurations for testing and activating the context. This step ensures that the test can access current_app like the regular request. Then, you can create a new database for testing as needed. The database and application context will be removed from the tearDown () method.
The first test ensures that the application instance exists. The second test ensures that the application runs under the test configuration. To ensure that the tests directory is valid, add the _ init _. py file under the tests directory. However, the file can be empty so that the unittest package can scan all modules and locate the test.
Suggestion: if you have an application cloned on GitHub, you can run git checkout 7a to switch to this version of the application. To ensure that you have installed all the dependency sets, run pip install-r requirements.txt.
To run the unit test, you can add a custom command to the manage. py script.
The following example shows how to add a test command.
Example manage. pyt: unit test Startup Script
@manager.commanddef test(): """Run the unit tests.""" import unittest tests = unittest.TestLoader().discover('tests') unittest.TextTestRunner(verbosity=2).run(tests)
The manager. command modifier makes it easy to implement custom commands. The decorated function name can be used as a command name, and the document string of the function displays help information. The test () function calls the test runner in the unittest package.
The unit test can be executed as follows:
(venv) $ python manage.py test
test_app_exists (test_basics.BasicsTestCase) ... oktest_app_is_testing (test_basics.BasicsTestCase) ... ok.----------------------------------------------------------------------Ran 2 tests in 0.001sOK
7. Start the database
Compared with a single-script application, the reconstructed application uses different databases.
The URL of the database obtained from the environment variable is the first choice. The default SQLite database is optional. The environment variables in the three configurations are different from those in the SQLite database file name. For example, the URL of the Development configuration is obtained from the DEV_DATABASE_URL environment variable and, if not defined, the SQLite database named data-dev.sqlite is used.
No matter which Database URL source is, you must create a database table for the new database. If Flask-Migrate is used to maintain migration tracking, the database table can be created or updated to the latest version by running the following command:
(venv) $ python manage.py db upgrade
Believe it or not, the first part has ended. You have learned the necessary basic elements of Flask, but you are not sure how to combine these scattered knowledge to form a real application. The purpose of the second part is to develop a complete application to keep you going.