Mira la lección en vídeo
Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.
Desbloquear Plan PlusCreación de formularios WTForms
WTForms es una biblioteca de Python que simplifica la creación y validación de formularios web. A diferencia de trabajar directamente con formularios HTML, WTForms permite definir la estructura del formulario mediante clases de Python, proporcionando una interfaz orientada a objetos para gestionar campos, tipos de datos y reglas de validación.
La principal ventaja de utilizar WTForms radica en su capacidad para centralizar la lógica del formulario en el código Python, manteniendo separada la presentación (HTML) de la funcionalidad. Esto facilita el mantenimiento del código y permite reutilizar formularios en diferentes contextos.
Instalación y configuración inicial
Para comenzar a trabajar con WTForms en Flask, necesitamos instalar la extensión Flask-WTF, que integra WTForms con Flask de manera nativa:
pip install Flask-WTF
Una vez instalada, debemos configurar una clave secreta en nuestra aplicación Flask. Esta clave es necesaria para la generación de tokens de seguridad:
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'tu-clave-secreta-aqui'
Definición de formularios con clases
Los formularios en WTForms se definen como clases de Python que heredan de FlaskForm
. Cada campo del formulario se representa mediante un atributo de clase con un tipo específico:
from flask_wtf import FlaskForm
from wtforms import StringField, EmailField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Email, Length
class ContactoForm(FlaskForm):
nombre = StringField('Nombre', validators=[DataRequired(), Length(min=2, max=50)])
email = EmailField('Email', validators=[DataRequired(), Email()])
mensaje = TextAreaField('Mensaje', validators=[DataRequired(), Length(min=10, max=500)])
enviar = SubmitField('Enviar mensaje')
En este ejemplo, hemos creado un formulario de contacto con tres campos de entrada y un botón de envío. Cada campo incluye validadores que definen las reglas que deben cumplir los datos introducidos.
Tipos de campos disponibles
WTForms ofrece una amplia variedad de tipos de campos para diferentes necesidades:
Campos de texto básicos:
from wtforms import StringField, TextAreaField, PasswordField
nombre = StringField('Nombre completo')
descripcion = TextAreaField('Descripción')
password = PasswordField('Contraseña')
Campos numéricos y de selección:
from wtforms import IntegerField, SelectField, RadioField, BooleanField
edad = IntegerField('Edad')
categoria = SelectField('Categoría', choices=[('tech', 'Tecnología'), ('design', 'Diseño')])
genero = RadioField('Género', choices=[('m', 'Masculino'), ('f', 'Femenino')])
acepta_terminos = BooleanField('Acepto los términos y condiciones')
Campos especializados:
from wtforms import EmailField, URLField, DateField
from wtforms.widgets import DateInput
email = EmailField('Correo electrónico')
sitio_web = URLField('Sitio web')
fecha_nacimiento = DateField('Fecha de nacimiento', widget=DateInput())
Integración con controladores Flask
Para utilizar un formulario WTForms en un controlador Flask, creamos una instancia del formulario y la pasamos a la plantilla:
from flask import render_template, request, redirect, url_for, flash
@app.route('/contacto', methods=['GET', 'POST'])
def contacto():
form = ContactoForm()
if form.validate_on_submit():
# Procesar los datos del formulario
nombre = form.nombre.data
email = form.email.data
mensaje = form.mensaje.data
# Aquí iría la lógica para guardar o enviar los datos
flash('Mensaje enviado correctamente', 'success')
return redirect(url_for('contacto'))
return render_template('contacto.html', form=form)
El método validate_on_submit()
verifica si el formulario ha sido enviado mediante POST y si todos los validadores han pasado correctamente.
Renderizado en plantillas Jinja
En la plantilla HTML, utilizamos los métodos del objeto formulario para generar los campos:
<form method="POST">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.nombre.label(class="form-label") }}
{{ form.nombre(class="form-control") }}
{% if form.nombre.errors %}
<div class="text-danger">
{% for error in form.nombre.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-group">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
{% if form.email.errors %}
<div class="text-danger">
{% for error in form.email.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-group">
{{ form.mensaje.label(class="form-label") }}
{{ form.mensaje(class="form-control", rows="4") }}
{% if form.mensaje.errors %}
<div class="text-danger">
{% for error in form.mensaje.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
{{ form.enviar(class="btn btn-primary") }}
</form>
La función hidden_tag()
genera campos ocultos necesarios para la seguridad del formulario. Cada campo se puede renderizar con atributos HTML adicionales pasados como argumentos.
Formularios con datos predefinidos
WTForms permite precargar formularios con datos existentes, útil para formularios de edición:
@app.route('/editar-perfil', methods=['GET', 'POST'])
def editar_perfil():
form = PerfilForm()
if request.method == 'GET':
# Cargar datos existentes del usuario
form.nombre.data = "Juan Pérez"
form.email.data = "juan@ejemplo.com"
form.biografia.data = "Desarrollador Python"
if form.validate_on_submit():
# Actualizar datos del usuario
pass
return render_template('editar_perfil.html', form=form)
Esta aproximación permite que el formulario muestre los valores actuales cuando se carga por primera vez, facilitando la experiencia de edición para el usuario.
Validación automática
Guarda tu progreso
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
La validación automática en WTForms se ejecuta cuando el formulario es procesado, aplicando todas las reglas definidas en los validadores de cada campo. Este mecanismo permite verificar la integridad de los datos antes de procesarlos, garantizando que cumplan con los criterios establecidos.
Funcionamiento del sistema de validación
Cuando se invoca el método validate_on_submit()
, WTForms ejecuta automáticamente todos los validadores asociados a cada campo del formulario. El proceso sigue esta secuencia:
@app.route('/registro', methods=['GET', 'POST'])
def registro():
form = RegistroForm()
if form.validate_on_submit():
# Solo se ejecuta si TODOS los validadores pasan
usuario = form.username.data
email = form.email.data
return redirect(url_for('exito'))
# Si hay errores, se renderiza el formulario con mensajes
return render_template('registro.html', form=form)
El método validate_on_submit()
devuelve True
únicamente cuando todos los validadores de todos los campos han pasado correctamente. Si algún validador falla, devuelve False
y los errores quedan disponibles en el atributo errors
de cada campo.
Validadores incorporados
WTForms incluye una amplia gama de validadores predefinidos que cubren las necesidades más comunes:
Validadores de presencia y longitud:
from wtforms.validators import DataRequired, Length, Optional
class PerfilForm(FlaskForm):
nombre = StringField('Nombre', validators=[
DataRequired(message='El nombre es obligatorio'),
Length(min=2, max=50, message='El nombre debe tener entre 2 y 50 caracteres')
])
biografia = TextAreaField('Biografía', validators=[
Optional(), # Campo opcional
Length(max=200, message='La biografía no puede exceder 200 caracteres')
])
Validadores de formato:
from wtforms.validators import Email, URL, Regexp
email = EmailField('Email', validators=[
DataRequired(),
Email(message='Introduce un email válido')
])
sitio_web = URLField('Sitio web', validators=[
Optional(),
URL(message='Introduce una URL válida')
])
telefono = StringField('Teléfono', validators=[
Optional(),
Regexp(r'^\d{9}$', message='El teléfono debe tener 9 dígitos')
])
Validadores numéricos:
from wtforms.validators import NumberRange
edad = IntegerField('Edad', validators=[
DataRequired(),
NumberRange(min=18, max=120, message='La edad debe estar entre 18 y 120 años')
])
precio = DecimalField('Precio', validators=[
DataRequired(),
NumberRange(min=0.01, message='El precio debe ser mayor que 0')
])
Validadores de comparación
Los validadores de comparación permiten establecer relaciones entre diferentes campos del formulario:
from wtforms.validators import EqualTo
class CambiarPasswordForm(FlaskForm):
password_actual = PasswordField('Contraseña actual', validators=[
DataRequired(message='Introduce tu contraseña actual')
])
password_nueva = PasswordField('Nueva contraseña', validators=[
DataRequired(),
Length(min=8, message='La contraseña debe tener al menos 8 caracteres')
])
confirmar_password = PasswordField('Confirmar contraseña', validators=[
DataRequired(),
EqualTo('password_nueva', message='Las contraseñas no coinciden')
])
Validación condicional
WTForms permite implementar validación condicional mediante validadores personalizados que se ejecutan según el estado de otros campos:
from wtforms.validators import ValidationError
class EventoForm(FlaskForm):
tipo_evento = SelectField('Tipo', choices=[
('presencial', 'Presencial'),
('online', 'Online')
])
direccion = StringField('Dirección')
enlace_streaming = URLField('Enlace de streaming')
def validate_direccion(self, field):
if self.tipo_evento.data == 'presencial' and not field.data:
raise ValidationError('La dirección es obligatoria para eventos presenciales')
def validate_enlace_streaming(self, field):
if self.tipo_evento.data == 'online' and not field.data:
raise ValidationError('El enlace es obligatorio para eventos online')
Los métodos que siguen el patrón validate_<nombre_campo>
se ejecutan automáticamente como validadores adicionales para ese campo específico.
Manejo de errores de validación
Los errores de validación se almacenan automáticamente y pueden ser accedidos tanto desde el controlador como desde la plantilla:
@app.route('/producto', methods=['GET', 'POST'])
def crear_producto():
form = ProductoForm()
if form.validate_on_submit():
# Procesar datos válidos
pass
else:
# Mostrar errores específicos en el log
for field, errors in form.errors.items():
for error in errors:
print(f"Error en {field}: {error}")
return render_template('producto.html', form=form)
En la plantilla, los errores se muestran automáticamente utilizando la estructura de errores del campo:
<div class="form-group">
{{ form.precio.label(class="form-label") }}
{{ form.precio(class="form-control" + (" is-invalid" if form.precio.errors else "")) }}
{% if form.precio.errors %}
<div class="invalid-feedback">
{% for error in form.precio.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
Validación de archivos
Para formularios que incluyen subida de archivos, WTForms proporciona validadores específicos:
from flask_wtf.file import FileField, FileRequired, FileAllowed
from werkzeug.utils import secure_filename
class DocumentoForm(FlaskForm):
archivo = FileField('Documento', validators=[
FileRequired(message='Selecciona un archivo'),
FileAllowed(['pdf', 'doc', 'docx'], message='Solo se permiten archivos PDF y Word')
])
imagen = FileField('Imagen', validators=[
Optional(),
FileAllowed(['jpg', 'jpeg', 'png', 'gif'], message='Solo imágenes JPG, PNG o GIF')
])
Personalización de mensajes de error
Los mensajes de error pueden personalizarse tanto a nivel de validador como globalmente:
class UsuarioForm(FlaskForm):
username = StringField('Usuario', validators=[
DataRequired(message='El nombre de usuario es obligatorio'),
Length(min=3, max=20, message='Entre %(min)d y %(max)d caracteres'),
Regexp(r'^[a-zA-Z0-9_]+$', message='Solo letras, números y guiones bajos')
])
email = EmailField('Email', validators=[
DataRequired(message='El email es obligatorio'),
Email(message='Formato de email inválido')
])
Los mensajes pueden incluir variables de plantilla como %(min)d
y %(max)d
que se sustituyen automáticamente con los valores del validador.
Validación manual
Además de la validación automática, es posible ejecutar la validación de forma manual para casos específicos:
@app.route('/api/validar', methods=['POST'])
def validar_datos():
form = ContactoForm(data=request.json)
if form.validate():
return {'status': 'success', 'message': 'Datos válidos'}
else:
return {
'status': 'error',
'errors': form.errors
}, 400
El método validate()
ejecuta todos los validadores sin requerir que el formulario haya sido enviado mediante POST, útil para validación en tiempo real o APIs.
Aprendizajes de esta lección de Flask
- Comprender la integración de WTForms con Flask mediante Flask-WTF.
- Definir formularios como clases de Python con campos y validadores.
- Implementar validación automática y personalizada de datos de formulario.
- Gestionar la renderización de formularios y errores en plantillas Jinja.
- Aplicar validación condicional y manejo de archivos en formularios web.
Completa este curso de Flask y certifícate
Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs