Flask-WTF para manejo de formularios

Publicado por Andrea Navarro en

En este artículo veremos como utilizar la extensión Flask-WTF para la creación y validación de formularios con Flask.

wtfirns

WTForms es una librería de renderizado y validación de formulario para desarrollo web con Python. Soporta validación de datos, protección contra ataques CSRF protection e internationalization (I18N) entre otras cosas.

flask-wtf

Flask-WTF es una extensión que integra la librería WTForms con el framework Flask. Esta librería contiene además soporte para reCAPTCHA y para carga de archivos como así también la creación de token CSRF.

Instalación Flask-WTF

Es posible instalar Flask-WTF utilizando el manejador de paquetes pip.

pip install -U Flask-WTF

Creación modelos de formularios

Para poder manejar un formulario se debe crear un clase que represente dicho formulario que herede de la clase FlaskForm. Esta clase contendrá las definiciones de todos los campos, las validaciones de los mismos, se encargará de tomar los valores de entrada y agregar los errores de validación.

from flask_wtf import FlaskForm

class EjemploFormulario(FlaskForm):
   //Campos y validaciones

Campos básicos

WTForms soporta diferentes tipos de campos que serán traducidos a los correspondientes elementos de HTML cuando el formulario sea renderizado. Cada campo se definirá como un atributo de la clase del formulario. A continuación se listan los tipos de campos disponibles dependiendo del tipo de valor que va a utilizarse. Es importante notar que al manejar los valores del formulario en el código estos estarán en formatos utilizados en Python, esto es especialmente útil cuando se trabaja con fechas, valores decimales o booleanos.

TipoCampoValor
BooleanoBooleanFieldTrue/False
FechaDateFielddatetime.date
FechaHoraDateTimeFielddatetime.datetime
DecimalDecimalFielddecimal.Decimal
Decimal (Rango)DecimalRangeFielddecimal.Decimal
EmailEmailFieldstring
ArchivoFileFieldfilename
Archivo MúltipleMultipleFileFieldfilename
EnteroIntegerFieldint
Entero(Rango)IntegerRangeFieldint
Boton RadioRadioFieldstring
DesplegableSelectFieldstring
OcultoHiddenFieldstring
ContraseñaPasswordFieldstring
Área de textoTextAreaFieldstring

Se describe cada campo inicializando la clase correspondiente al tipo de campo que quiere utilizarse. Como primer argumento se pasa el valor correspondiente a la etiqueta para dicho campo.

from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField

class PersonaForm(FlaskForm): 
   nombre = StringField('Nombre Completo')
   edad = IntegerField('Edad')

Algunos campos requieren el uso de parámetros adicionales. En el caso de los campos tipo Select es necesario especificar la lista de opciones que aparecerán en la lista utilizando el parámetro choices.

pais = SelectField('País', choices=[('AR', 'Argentina'), ('AT', 'Austria'), ('AU', 'Australia')])

La mayoría de los formularios contará también con un botón de envío o confirmación, este también debe ser especificado en la clase.

 submit = SubmitField("Enviar")

Renderizado de formularios con Flask-WTF

Para poder renderizar correctamente un formulario este debe primero ser inicializado en la ruta correspondiente y luego debe enviarse dicho objeto en la función de renderización correspondiente.

@app.route('/crear', methods=["POST","GET"])
def crear():
    formulario = PersonaForm() #Instanciar formulario   
    return render_template('persona_form.html', form = formulario) #Renderizar el formulario

Luego, en el template correspondiente debe referenciarse el elemento del formulario que quiere mostrarse.

<form method="POST" action="{{ url_for('crear') }}" >
	      <div class="form-row">
	    		<div class="form-group col-md-4">
	      			{{ form.nombre.label }} 
	                {{ form.nombre(class='form-control')|safe }}
	    		</div>
               <div class="form-group col-md-4">
	      			{{ form.edad.label }} 
	                {{ form.edad(class='form-control')|safe }}
	    		</div>
	    		<div class="form-group col-md-4">
	      			{{ form.submit( class='btn btn-success') }} 
	    		</div>
           </div>       
	  	</form>

En la linea 4 solicitaremos que se muestre en el template la etiqueta correspondiente a el campo nombre. En la siguiente se solicita que se renderice el campo nombre en sí, el tipo de campo que se muestre dependerá del tipo especificado en la clase. También es posible pasar argumentos que serán insertados en el elemento HTML del elemento, en este caso se le está pasando la clase form-control de Boostrap que modificará la apariencia del campo.

En las lineas 8 y 9 se hace lo mismo con el campo edad. La separación entre etiqueta y campo permite poder estructurar el formulario en diferentes configuraciones dentro del template.

Finalmente en la linea 12 se inserta el botón de envío de formulario al que se le pasa una clase para modificar su estilo.

Obtención y carga de datos de formulario

Es posible verificar si un formulario ha sido enviado a través del método is_submitted(). Los datos cargados dentro de cada campo del formulario se encontrarán dentro del atributo data de cada uno.

@app.route('/crear', methods=["POST","GET"])
def crear():
    formulario = PersonaForm() #Instanciar formulario
    if formulario.is_submitted():
        print(formulario.nombre.data) # Imprimir nombre
        print(formulario.edad.data)  # Imprimir edad
        return redirect(url_for('index')) #Redireccionar
    return render_template('persona_form.html', form = formulario) #Renderizar el formulario

De manera similar, si se quiere cargar datos a los campos de formularios antes de que este sea renderizado se debe cargar un valor al atributo data de el campo correspondiente. Esto puede utilizarse para cargar valores por defecto o para formularios de actualización donde los datos anteriores se encuentran almacenados previamente en una base de datos.

@app.route('/crear', methods=["POST","GET"])
def crear():
    formulario = PersonaForm() #Instanciar formulario
    if formulario.is_submitted():
       return redirect(url_for('index')) #Redireccionar
    formulario.nombre.data = "Pedro"
    formulario.edad.data = 27
    return render_template('persona_form.html', form = formulario) #Renderizar el formulario

Validación de datos

Es posible agregar todo tipo de validaciones a cada campo del formulario. Dichas validaciones deben ser especificadas dentro de la clase del formulario. Existen validadores pre-definidos para los casos más comunes, un parámetro común a todos es el message que permite configurar el mensaje de error de validación que se mostrará si no se cumple con dicha validación. A continuación se muestran algunos de estos validadores, la lista completa puede verse dentro de la documentación de WTForms.

ValidadorParámetros requeridos
DataRequiredVerifica que el campo tenga un valor
EqualToVerifica que el valor del campo sea igual al de otro campo. Campo con el cual se desea comparar
LengthVerifica la longitud del valor del campoValor mínimo y/o valor máximo
NumberRangeVerifica que el valor numérico del campo se encuentre dentro del rangoValor mínimo y/o máximo
RegexpVerifica el valor del campo con una expresión regularExpresión regular

Es posible agregar en forma de lista tantas validaciones como se requiera para cada campo.

from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField
from wtforms import validators #Importa validaciones

class PersonaForm(FlaskForm): 
   nombre = StringField('Nombre Completo',
    [
        #Definición de validaciones
        validators.Required(message = "El campo Nombre Completo es obligatorio"),
        validators.Length(min=5, message ="Nombre Completo debe tener más de 5 caractéres")
    ])

   edad = IntegerField('Edad,
    [
        #Definición de validaciones
        validators.Required(message = "El campo Edad es es obligatorio"),
        validators.NumberRange(min=5, max =110, message= "Edad fuera de rango")
    ])
   submit = SubmitField("Enviar")

Validaciones personalizadas

Si ninguna de las validaciones pre-definidas cumplen con la función requerida para el formulario es posible crear funciones de validación personalizadas y asignarlas a los campos. En el siguiente ejemplo se creará una función que validará que el contenido del campo no contenga más de tres palabras, por parámetro. Si la validación no se cumple entonces ocurrirá un error de validación utilizando ValidationError al que se le pasará el mensaje de error que se mostrará.

def cantPalabras(form, campo):
    if len(campo.data.split()) >3:
        raise ValidationError('El campo no puede tener más de tres palabras')

 nombre = StringField('Nombre Completo',
    [
        #Definición de validaciones
        validators.Required(message = "El campo Nombre Completo es obligatorio"),
        validators.Length(min=5, message ="Nombre Completo debe tener más de 5 caractéres"),
        cantPalabras
    ])

Dentro del template donde se renderizará el formulario es necesario especificar donde se mostrarán los mensajes de error en el caso que las validaciones no se cumplan.

<form method="POST" action="{{ url_for('crear') }}" >
	      <div class="form-row">
	    		<div class="form-group col-md-4">
	      			{{ form.nombre.label }} 
	                {{ form.nombre(class='form-control')|safe }}
                    {% if form.nombre.errors %} <!--Si existen errores -->
	                 <div class="alert alert-danger" role="alert">
	                 {% for error in form.nombre.errors %} <!--Recorrer errrores-->
	   	         {{ error }}<br><!--Mostrar error -->
	                 {% endfor %}
	                  </div>
	                 {% endif %}
	    		</div>
               <div class="form-group col-md-4">
	      			{{ form.edad.label }} 
	                {{ form.edad(class='form-control')|safe }}
                     {% if form.edad.errors %} <!--Si existen errores -->
	                 <div class="alert alert-danger" role="alert">
	                 {% for error in form.edad.errors %} <!--Recorrer errrores-->
	   	         {{ error }}<br><!--Mostrar error -->
	                 {% endfor %}
	                  </div>
	                 {% endif %}
	    		</div>
	    		<div class="form-group col-md-4">
	      			{{ form.submit( class='btn btn-success') }} 
	    		</div>
           </div>       
	  	</form>

Las lineas de código resaltadas verificarán si el campo correspondiente tiene algún error de validación, de ser así se recorrerá cada uno de los errores y se mostrará dentro de un mensaje de alerta debajo del campo. Es importante notar que este código puede reducirse significativamente utilizando los macros de Jinja.

Para que el formulario tenga cargado los errores de validación es necesario que está se realice en la ruta una vez que el formulario ha sido enviado. Para esto se reemplazará la función is_submitted por validate_on_submit que verificará que el formulario haya sido enviado y validará los datos.

@app.route('/crear', methods=["POST","GET"])
def crear():
    formulario = PersonaForm() #Instanciar formulario
    if formulario.validate_on_submit():
       return redirect(url_for('index')) #Redireccionar
    return render_template('persona_form.html', form = formulario) #Renderizar el formulario

De esta manera, si el formulario que ha sido enviado tiene errores de validación se volverá a renderizar el mismo template con los mensajes de error incluidos en el objeto.


En este artículo hemos visto como utilizar la extensión Flask-WTF para la creación y manejo de formularios en Flask. Espero que les sirva!


¿Preguntas? ¿Comentarios?

Si tenés dudas, o querés dejarnos tus comentarios y consultas, sumate al grupo de Telegram de la comunidad JuncoTIC!
¡Te esperamos!


Andrea Navarro

- Ingeniera en Informática - Docente universitaria - Investigadora