Los formularios son una parte crucial en las aplicaciones web hoy en día, es el mecanismo usado por excelencia para obtener datos de usuarios, aunque, los formularios web son netamente HTML y que pueden ser usados directamente en Flask, trae el inconveniente de que, a estos formularios hay que aplicar validaciones tanto del lado del cliente, y más importante, del servidor, para cuando se reciban los datos antes de procesarlos y guardarlos sean completamente válidos.
Otro problema consiste en que hay que crear un equivalente entre los campos de formulario con los modelos en la base de datos, es decir, usualmente estos campos de formulario tienen una relación directa o al menos bastante directa con las tablas en la base de datos, si vamos a crear un módulo para administrar publicaciones cuyos campos de formulario serían título, descripción y contenido, estos campos deben de estar presente en la tabla de la base de datos, para poder registrarlos, por lo tanto, tenemos un doble trabajo para definir estos campos en la base de datos (en el caso de Flask, a través de los modelos) y los campos de formularios.
Estas situaciones ocurren con cualquier tecnología del lado del servidor que en definitiva consiste en definir campos y validaciones con un mismo propósito, pero con diferente lógica en el cliente y en el servidor; es decir, que si queremos un campo para el nombre, en el cual sea requerido, en HTML sería como:
<input type="text" name="task" required>
Y en el servidor, mediante Flask sería algo como:
task = request.args.form('task')
if task is not None:
#TO DO
Y si en algún momento, nos solicitan cambiar la validación o agregar más, tenemos que hacer cambios en ambos lados.
Por suerte, en el caso de Flask, existe un paquete que facilita mucho el proceso y la lógica especificada, el paquete de WTForms proporciona una varios campos a los cuales les podemos aplicar validaciones en el lado del servidor, y mediante Jinja, podemos representar estos campos fácilmente para que sean convertidos a campos de formularios en HTML; estos campos son realmente propiedades que corresponden a una clase que hereda de FlaskForm a cuyos campos/propiedades, podemos definir validaciones; una clase típica de validación, luce como:
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import InputRequired
class Task(FlaskForm):
name = StringField('Name', validators=[InputRequired()])
Las validaciones se aplican principalmente en el lado del servidor, que es el lado más importante, aunque, dependiendo de la validación, ciertas reglas son especificadas:
<input id="name" name="name" required type="text" value="">
WTForms
Comencemos instalando el paquete con:
$ pip install Flask-WTF
Se instalan dos paquetes, el del plugin:
WTForms==***
Y el conector para Flask:
Flask-WTF==***
Para simplicidad, nos referiremos a WTForm como simplemente WTF o Flask WTF.
Principales campos
Tenemos distintos tipos de campos que podemos definir para crear un formulario mediante Flask WTF, que vienen siendo los siguientes:
- fields.StringField: Representa un campo de tipo texto.
- fields.FileField: Representa un campo de tipo archivo.
- fields.DateField y fields.DateTimeField: Representa un campo de tipo date o datetime en Python, también, recibe un parámetro opcional llamado format que recibe el formato en String para la fecha por defecto es (format='%Y-%m-%d').
- fields.IntegerField: Representa un campo de tipo entero.
- fields.FloatField: Representa un campo de tipo flotante.
- fields.PasswordField: Representa un campo de tipo password.
- fields.RadioField: Representa un campo de tipo radio en HTML, como es de esperar, se especifica mediante un parámetro para seleccionar un listado de opciones; este listado puede ser una lista o tupla en Python.
- fields.SelectField: Representa un campo de tipo selección en el cual, se especifica un parámetro llamado choices en el cual, se especifican en listado de opciones.
Puedes obtener más información sobre los campos disponibles en:
https://wtforms.readthedocs.io/en/master/fields/
Validaciones
Además de definir los campos, es posible agregar validaciones a los campos del formulario; para ello, tenemos clases predefinidas en:
from wtforms.validators import ***
Entre los principales:
- InputRequired: Indica que el campo es requerido.
- NumberRange: Se establece un número mínimo y máximo.
- DataRequired: Indica que el campo es requerido.
- Length: Se establece un número mínimo y máximo para el tamaño de un string.
- Regexp: Evalúa una expresión regular.
Puedes obtener más información sobre las validaciones disponibles en:
https://wtforms.readthedocs.io/en/master/validators/
Y luego, desde el controlador, se verifica si el formulario es válido antes de realizar cualquier otra operación sobre el mismo, como, por ejemplo, guardar en la base de datos:
@app.route('/submit', methods=['GET', 'POST'])
def submit():
form = MyForm()
if form.validate_on_submit():
# TO DO
Y desde el template, podemos mostrar los errores ocurridos por un campo en particular, por ejemplo, el llamado name:
{% if form.name.errors %}
<ul class="errors">
{% for error in form.name.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
O de todos los campos:
{% if form.errors %}
<ul class="errors">
{% for error in form.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
Validaciones personalizadas
Muchas veces es necesario agregar reglas más complejas que simplemente indicar si un campo es requerido, o por el tipo o mediante una expresión regular, si no, necesitamos hacer validaciones mediante funciones y realizar condicionales entre otros, en estos casos, podemos emplear validaciones personalizadas ya sea por campos; por ejemplo, para el campo name:
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')
Protección CSRF
Al momento de emplear algún formulario, veremos un error como el siguiente:
RuntimeError: A secret key is required to use CSRF.
Los formularios de WTForms emplean la protección CSRF y debemos de especificar una clase para la generación de este token, para ello, podemos usar el archivo de configuración:
my_app\config.py
class Config(object):
SQLALCHEMY_DATABASE_URI="mysql+pymysql://root:@localhost:3306/test_flask"
SECRET_KEY="SECRETKEY"
***
En este ejemplo, la clave secreta es SECRETKEY, pero puedes personalizarla a gusto.
Creando un formulario con WTForms para las tareas
Teniendo claro el funcionamiento de los formularios con Flask WTF, vamos a implementar un formulario para las tareas:
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()])
Como puedes apreciar en el código anterior, ya que, el campo para las tareas consiste en especificar el nombre, se usa un campo de tipo texto definido mediante la clase StringField y que sea requerido mediante InputRequired.
Desde el controlador, creamos una instancia del formulario:
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)
Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter