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 PlusFormularios HTML
Los formularios HTML constituyen la interfaz principal para que los usuarios envíen datos a nuestras aplicaciones Flask. A diferencia de las peticiones GET que simplemente solicitan información, los formularios nos permiten recopilar datos del usuario y procesarlos en el servidor mediante peticiones POST.
Un formulario HTML básico se define utilizando la etiqueta <form>
y debe incluir dos atributos fundamentales: method para especificar el tipo de petición HTTP y action para indicar la ruta donde se procesarán los datos.
<form method="POST" action="/procesar">
<input type="text" name="nombre" placeholder="Tu nombre">
<input type="email" name="email" placeholder="Tu email">
<button type="submit">Enviar</button>
</form>
Elementos básicos de formulario
Los campos de entrada más comunes incluyen diferentes tipos de input que permiten capturar distintos tipos de información del usuario:
<form method="POST" action="/registro">
<!-- Campo de texto simple -->
<input type="text" name="usuario" placeholder="Nombre de usuario" required>
<!-- Campo de contraseña -->
<input type="password" name="password" placeholder="Contraseña" required>
<!-- Campo de email con validación automática -->
<input type="email" name="email" placeholder="correo@ejemplo.com" required>
<!-- Campo numérico -->
<input type="number" name="edad" min="18" max="100">
<!-- Área de texto para contenido largo -->
<textarea name="comentario" rows="4" cols="50" placeholder="Escribe tu comentario"></textarea>
<!-- Botón de envío -->
<button type="submit">Registrarse</button>
</form>
El atributo name es crucial porque define el nombre de la clave que recibiremos en Flask cuando procesemos los datos. Por ejemplo, un input con name="usuario"
estará disponible como request.form['usuario']
en nuestro controlador.
Selección de opciones
Para ofrecer opciones predefinidas a los usuarios, disponemos de varios elementos especializados:
<form method="POST" action="/preferencias">
<!-- Lista desplegable -->
<select name="pais" required>
<option value="">Selecciona tu país</option>
<option value="es">España</option>
<option value="mx">México</option>
<option value="ar">Argentina</option>
</select>
<!-- Botones de radio para selección única -->
<input type="radio" name="genero" value="masculino" id="masc">
<label for="masc">Masculino</label>
<input type="radio" name="genero" value="femenino" id="fem">
<label for="fem">Femenino</label>
<!-- Casillas de verificación para selección múltiple -->
<input type="checkbox" name="intereses" value="programacion" id="prog">
<label for="prog">Programación</label>
<input type="checkbox" name="intereses" value="diseno" id="dis">
<label for="dis">Diseño</label>
<button type="submit">Guardar preferencias</button>
</form>
Los checkboxes tienen un comportamiento especial: cuando están marcados, envían su valor; cuando no lo están, no envían nada. Esto es importante tenerlo en cuenta al procesar los datos en Flask.
Integración con Jinja
En aplicaciones Flask reales, los formularios se integran perfectamente con las plantillas Jinja. Podemos generar formularios dinámicos y manejar datos del contexto:
<!-- template: formulario_producto.html -->
<form method="POST" action="{{ url_for('crear_producto') }}">
<input type="text" name="nombre" placeholder="Nombre del producto"
value="{{ producto.nombre if producto else '' }}" required>
<select name="categoria" required>
<option value="">Selecciona categoría</option>
{% for categoria in categorias %}
<option value="{{ categoria.id }}"
{{ 'selected' if producto and producto.categoria_id == categoria.id else '' }}>
{{ categoria.nombre }}
</option>
{% endfor %}
</select>
<textarea name="descripcion" placeholder="Descripción del producto">{{ producto.descripcion if producto else '' }}</textarea>
<input type="number" name="precio" step="0.01" min="0"
value="{{ producto.precio if producto else '' }}" required>
<button type="submit">{{ 'Actualizar' if producto else 'Crear' }} Producto</button>
</form>
La función url_for() genera automáticamente las URLs correctas para nuestras rutas, mientras que las estructuras condicionales de Jinja nos permiten reutilizar el mismo formulario tanto para crear como para editar registros.
Campos ocultos y CSRF
Los campos ocultos son útiles para enviar información adicional que el usuario no necesita ver o modificar:
<form method="POST" action="/actualizar_perfil">
<!-- Campo oculto con ID del usuario -->
<input type="hidden" name="user_id" value="{{ current_user.id }}">
<!-- Campo oculto para identificar la acción -->
<input type="hidden" name="action" value="update_profile">
<input type="text" name="nombre" value="{{ current_user.nombre }}" required>
<input type="email" name="email" value="{{ current_user.email }}" required>
<button type="submit">Actualizar perfil</button>
</form>
Aunque en esta lección nos centramos en formularios básicos, es importante mencionar que en aplicaciones de producción siempre debemos incluir protección CSRF para prevenir ataques de falsificación de peticiones entre sitios.
Organización y estructura
Para formularios más complejos, es recomendable organizar los campos en secciones lógicas utilizando elementos como <fieldset>
y <legend>
:
<form method="POST" action="/registro_completo">
<fieldset>
<legend>Información personal</legend>
<input type="text" name="nombre" placeholder="Nombre completo" required>
<input type="email" name="email" placeholder="Email" required>
<input type="tel" name="telefono" placeholder="Teléfono">
</fieldset>
<fieldset>
<legend>Dirección</legend>
<input type="text" name="direccion" placeholder="Calle y número" required>
<input type="text" name="ciudad" placeholder="Ciudad" required>
<input type="text" name="codigo_postal" placeholder="Código postal" required>
</fieldset>
<fieldset>
<legend>Preferencias</legend>
<input type="checkbox" name="newsletter" value="1" id="news">
<label for="news">Recibir newsletter</label>
<input type="checkbox" name="promociones" value="1" id="promo">
<label for="promo">Recibir promociones</label>
</fieldset>
<button type="submit">Completar registro</button>
</form>
Esta estructura no solo mejora la experiencia del usuario, sino que también facilita el procesamiento de los datos en nuestros controladores Flask, ya que podemos agrupar lógicamente la información recibida.
Procesamiento con request.form
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
Una vez que el usuario envía un formulario HTML mediante POST, Flask nos proporciona el objeto request.form para acceder a todos los datos enviados. Este objeto funciona como un diccionario que contiene los valores de cada campo del formulario, utilizando el atributo name
como clave.
Para procesar formularios POST, necesitamos configurar nuestras rutas para aceptar tanto peticiones GET (para mostrar el formulario) como POST (para procesarlo):
from flask import Flask, request, render_template, redirect, url_for
app = Flask(__name__)
@app.route('/contacto', methods=['GET', 'POST'])
def contacto():
if request.method == 'POST':
# Procesar datos del formulario
nombre = request.form['nombre']
email = request.form['email']
mensaje = request.form['mensaje']
# Aquí procesaríamos los datos (guardar en BD, enviar email, etc.)
print(f"Contacto de {nombre} ({email}): {mensaje}")
return redirect(url_for('gracias'))
# Si es GET, mostrar el formulario
return render_template('contacto.html')
@app.route('/gracias')
def gracias():
return render_template('gracias.html')
Acceso seguro a los datos
El acceso directo mediante corchetes puede generar errores si el campo no existe. Para un manejo más robusto, utilizamos el método get()
que permite especificar un valor por defecto:
@app.route('/registro', methods=['GET', 'POST'])
def registro():
if request.method == 'POST':
# Acceso seguro con valores por defecto
usuario = request.form.get('usuario', '')
email = request.form.get('email', '')
edad = request.form.get('edad', 0)
# Verificar que los campos obligatorios no estén vacíos
if not usuario or not email:
error = "Usuario y email son obligatorios"
return render_template('registro.html', error=error)
# Procesar registro exitoso
return redirect(url_for('perfil', usuario=usuario))
return render_template('registro.html')
Manejo de diferentes tipos de datos
Los datos de request.form siempre llegan como cadenas de texto. Para trabajar con otros tipos de datos, necesitamos realizar las conversiones apropiadas:
@app.route('/producto', methods=['GET', 'POST'])
def crear_producto():
if request.method == 'POST':
nombre = request.form.get('nombre')
# Conversión a número con manejo de errores
try:
precio = float(request.form.get('precio', 0))
stock = int(request.form.get('stock', 0))
except ValueError:
error = "Precio y stock deben ser números válidos"
return render_template('producto.html', error=error)
# Verificar valores mínimos
if precio <= 0 or stock < 0:
error = "Precio debe ser positivo y stock no negativo"
return render_template('producto.html', error=error)
# Procesar producto válido
producto_data = {
'nombre': nombre,
'precio': precio,
'stock': stock
}
return render_template('producto_creado.html', producto=producto_data)
return render_template('producto.html')
Procesamiento de selecciones múltiples
Los checkboxes y elementos de selección múltiple requieren un tratamiento especial, ya que pueden enviar varios valores con el mismo nombre:
@app.route('/preferencias', methods=['GET', 'POST'])
def preferencias():
if request.method == 'POST':
# Campo simple
nombre = request.form.get('nombre')
# Radio button (un solo valor)
genero = request.form.get('genero')
# Checkboxes (múltiples valores)
intereses = request.form.getlist('intereses')
# Verificar si un checkbox específico está marcado
newsletter = 'newsletter' in request.form
# Procesar los datos
usuario_data = {
'nombre': nombre,
'genero': genero,
'intereses': intereses,
'newsletter': newsletter
}
return render_template('preferencias_guardadas.html', usuario=usuario_data)
return render_template('preferencias.html')
Patrón de redirección POST-redirect-GET
Una buena práctica es implementar el patrón POST-redirect-GET para evitar que los usuarios reenvíen accidentalmente formularios al actualizar la página:
@app.route('/comentario', methods=['GET', 'POST'])
def comentario():
if request.method == 'POST':
autor = request.form.get('autor')
contenido = request.form.get('contenido')
if autor and contenido:
# Simular guardado en base de datos
comentario_id = len(comentarios) + 1 # ID simulado
comentarios.append({
'id': comentario_id,
'autor': autor,
'contenido': contenido
})
# Redirigir después del POST exitoso
return redirect(url_for('ver_comentario', id=comentario_id))
else:
error = "Autor y contenido son obligatorios"
return render_template('comentario.html', error=error)
return render_template('comentario.html')
@app.route('/comentario/<int:id>')
def ver_comentario(id):
# Buscar comentario por ID
comentario = next((c for c in comentarios if c['id'] == id), None)
if comentario:
return render_template('comentario_detalle.html', comentario=comentario)
return "Comentario no encontrado", 404
Procesamiento con contexto para plantillas
Frecuentemente necesitamos mantener los datos del formulario en caso de errores, para que el usuario no tenga que volver a escribir toda la información:
@app.route('/evento', methods=['GET', 'POST'])
def crear_evento():
if request.method == 'POST':
# Recopilar todos los datos
datos_formulario = {
'titulo': request.form.get('titulo', ''),
'fecha': request.form.get('fecha', ''),
'ubicacion': request.form.get('ubicacion', ''),
'descripcion': request.form.get('descripcion', ''),
'categoria': request.form.get('categoria', '')
}
# Lista para acumular errores
errores = []
if not datos_formulario['titulo']:
errores.append("El título es obligatorio")
if not datos_formulario['fecha']:
errores.append("La fecha es obligatoria")
if not datos_formulario['ubicacion']:
errores.append("La ubicación es obligatoria")
# Si hay errores, devolver formulario con datos y errores
if errores:
return render_template('evento.html',
datos=datos_formulario,
errores=errores)
# Procesar evento válido
return redirect(url_for('evento_creado', titulo=datos_formulario['titulo']))
# GET: formulario vacío
return render_template('evento.html', datos={})
En la plantilla correspondiente, podemos utilizar estos datos para repoblar el formulario:
<!-- template: evento.html -->
{% if errores %}
<div class="errores">
{% for error in errores %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<form method="POST">
<input type="text" name="titulo" value="{{ datos.titulo or '' }}"
placeholder="Título del evento" required>
<input type="date" name="fecha" value="{{ datos.fecha or '' }}" required>
<input type="text" name="ubicacion" value="{{ datos.ubicacion or '' }}"
placeholder="Ubicación" required>
<textarea name="descripcion" placeholder="Descripción">{{ datos.descripcion or '' }}</textarea>
<select name="categoria">
<option value="">Selecciona categoría</option>
<option value="conferencia" {{ 'selected' if datos.categoria == 'conferencia' else '' }}>
Conferencia
</option>
<option value="taller" {{ 'selected' if datos.categoria == 'taller' else '' }}>
Taller
</option>
</select>
<button type="submit">Crear evento</button>
</form>
Este enfoque proporciona una experiencia de usuario fluida, manteniendo los datos ingresados y mostrando mensajes de error específicos sin perder el trabajo realizado.
Aprendizajes de esta lección de Flask
- Comprender la estructura y elementos básicos de formularios HTML para enviar datos mediante POST.
- Aprender a procesar datos enviados desde formularios en Flask usando request.form.
- Manejar diferentes tipos de campos, incluyendo inputs, selects, checkboxes y campos ocultos.
- Implementar buenas prácticas como el patrón POST-redirect-GET y el manejo de errores con repoblación de formularios.
- Integrar formularios con plantillas Jinja para crear interfaces dinámicas y reutilizables.
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