WTForms forms in Flask 3

- Andrés Cruz

En español
WTForms forms in Flask 3

Forms are a crucial part of web applications today, they are the mechanism used par excellence to obtain user data, although web forms are purely HTML and can be used directly in Flask, which has the disadvantage that, these forms must be validated both on the client side and more importantly, on the server side, so that when the data is received before processing and saving it, it is completely valid.

Another problem is that you have to create an equivalent between the form fields with the models in the database, that is, usually these form fields have a direct or at least fairly direct relationship with the tables in the database, If we are going to create a module to manage publications whose form fields would be title, description and content, these fields must be present in the database table, in order to register them, therefore, we have a double job to define these fields in the database (in the case of Flask, through models) and form fields.

These situations occur with any server-side technology that ultimately consists of defining fields and validations with the same purpose, but with different logic on the client and on the server; that is, if we want a field for the name, in which it is required, in HTML it would be like:

<input type="text" name="task" required>

And on the server, using Flask it would be something like:

task = request.args.form('task')
if task is not None:
  #TO DO     

And if at any point, we are asked to change the validation or add more, we have to make changes on both sides.

Luckily, in the case of Flask, there is a package that greatly facilitates the process and the specified logic, the WTForms package provides several fields to which we can apply validations on the server side, and through Jinja, we can represent these fields easily to be converted to HTML form fields; these fields are actually properties that correspond to a class that inherits from FlaskForm to whose fields/properties we can define validations; a typical validation class looks like:

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired

class Task(FlaskForm):
    name = StringField('Name', validators=[InputRequired()])

Validations are mainly applied on the server side, which is the most important side, although, depending on the validation, certain rules are specified:

<input id="name" name="name" required type="text" value="">

WTForms

Let's start by installing the package with:

$ pip install Flask-WTF 

Two packages are installed, the plugin:

WTForms==***

And the connector for Flask:

Flask-WTF==***

For simplicity, we will refer to WTForm as simply WTF or Flask WTF.

Main fields

We have different types of fields that we can define to create a form using Flask WTF, which are the following:

  • fields.StringField: Represents a text type field.
  • fields.FileField: Represents a file type field.
  • fields.DateField y fields.DateTimeField: Represents a field of type date or datetime in Python, also, it receives an optional parameter called format that receives the format in String for the default date is(format='%Y-%m-%d').
  • fields.IntegerField: Represents a field of type integer.
  • fields.FloatField: Represents a float type field.
  • fields.PasswordField: Represents a password type field.
  • fields.RadioField: Represents a radio type field in HTML, as expected, it is specified by a parameter to select a list of options; this listing can be a list or tuple in Python.
  • fields.SelectField: Represents a selection type field in which a parameter called choices is specified in which a list of options is specified.

You can get more information about the available fields at:

https://wtforms.readthedocs.io/en/master/fields/

Validation

In addition to defining the fields, it is possible to add validations to the fields of the form; to do this, we have predefined classes in:

from wtforms.validators import ***

Among the main ones:

  • InputRequired: Indicates that the field is required.
  • NumberRange: A minimum and maximum number is established.
  • DataRequired: Indicates that the field is required.
  • Length: A minimum and maximum number is established for the size of a string.
  • Regexp: Evaluate a regular expression.

You can get more information about the validations available at:

https://wtforms.readthedocs.io/en/master/validators/

And then, from the controller, it is verified if the form is valid before performing any other operation on it, such as saving in the database:

@app.route('/submit', methods=['GET', 'POST'])
def submit():
    form = MyForm()
    if form.validate_on_submit():
       # TO DO

And from the template, we can show the errors that occurred by a particular field, for example, the so-called name:

{% if form.name.errors %}
    <ul class="errors">
    {% for error in form.name.errors %}
        <li>{{ error }}</li>
    {% endfor %}
    </ul>
{% endif %}

Or all fields:

{% if form.errors %}
    <ul class="errors">
    {% for error in form.errors %}
        <li>{{ error }}</li>
    {% endfor %}
    </ul>
{% endif %}

Personalized validations

Many times it is necessary to add more complex rules than simply indicate whether a field is required, or by type or by regular expression, if not, we need to make validations through functions and perform conditional among others, in these cases, we can use personalized validations and be by fields; for example, for the name field:

my_app\tasks\forms.py

from wtforms.validators import ValidationError
***
class Task(FlaskForm):
  ***
    def validate_name(form, field):
        if len(field.data) > 2:
            raise ValidationError('Name must be less than 2 characters')

CSRF protection

When using some form, we will see an error like the following:

RuntimeError: A secret key is required to use CSRF.

The WTFORMS forms use CSRF protection and we must specify a class for the generation of this token, for this, we can use the configuration file:

my_app\config.py

class Config(object): 
    SQLALCHEMY_DATABASE_URI="mysql+pymysql://root:@localhost:3306/test_flask" 
    SECRET_KEY="SECRETKEY"
    ***

In this example, the secret key is Secretkey, but you can customize it at ease.

Creating a form with WTForms for tasks

Being clear about the operation of the forms with Flak WTF, we will implement a form for tasks:

my_app\tasks\forms.py

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired

class Task(FlaskForm):
    name = StringField('Name', validators=[InputRequired()])

As you can see in the previous code, since, the field for tasks consists in specifying the name, a defined text type field is used using the StringField class and that is required using InputRequired.

From the controller, we create an instance of the form

my_app\tasks\controllers.py

from my_app.tasks import forms
***
@taskRoute.route('/create', methods=('GET', 'POST'))
def create():

   # form = forms.Task(csrf_enabled=False)
   form = forms.Task()
   if form.validate_on_submit():
      print("listo")
   #      return redirect('/success')
   #  return render_template('submit.html', form=form)

   # task_list.append(request.form.get('task')) #request.args.get('task')
   return render_template('dashboard/task/create.html', form=form)
Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.