Flask
Tutorial Flask: Controlador MVC con métodos POST en Flask
Jinja para Formularios: Utiliza Bootstrap 5 y aprende sintaxis eficiente en Flask. Crea formularios dinámicos y responsivos hoy.
Aprende Flask GRATIS y certifícateSintaxis Jinja para Formularios con Bootstrap 5
En esta sección, veremos cómo utilizar la sintaxis de Jinja para crear formularios estilizados con Bootstrap 5 en nuestras plantillas de Flask. Al combinar Jinja con Bootstrap, podemos generar formularios dinámicos y responsivos de manera eficiente.
Para empezar, definimos un formulario en una plantilla Jinja utilizando las clases de Bootstrap. Por ejemplo, un formulario básico para añadir un nuevo usuario podría ser:
<form method="post" action="{{ url_for('crear_usuario') }}">
<div class="mb-3">
<label for="nombre" class="form-label">Nombre</label>
<input type="text" class="form-control" id="nombre" name="nombre" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Correo electrónico</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<button type="submit" class="btn btn-primary">Enviar</button>
</form>
En este ejemplo, utilizamos la clase form-control
de Bootstrap para dar estilo a los campos de entrada. Empleamos la función url_for
de Jinja para generar la URL del controlador que manejará la solicitud POST.
Si necesitamos generar formularios dinámicos, podemos utilizar las estructuras de control de Jinja. Por ejemplo, si tenemos una lista de campos definida en el controlador:
# En el controlador
campos = [
{'label': 'Nombre', 'type': 'text', 'name': 'nombre'},
{'label': 'Correo electrónico', 'type': 'email', 'name': 'email'},
{'label': 'Contraseña', 'type': 'password', 'name': 'password'}
]
return render_template('formulario.html', campos=campos)
En la plantilla formulario.html
, iteramos sobre campos
para generar los inputs:
<form method="post" action="{{ url_for('crear_usuario') }}">
{% for campo in campos %}
<div class="mb-3">
<label for="{{ campo.name }}" class="form-label">{{ campo.label }}</label>
<input type="{{ campo.type }}" class="form-control" id="{{ campo.name }}" name="{{ campo.name }}" required>
</div>
{% endfor %}
<button type="submit" class="btn btn-success">Registrar</button>
</form>
Aquí, el bucle for
de Jinja permite crear campos de formulario de manera dinámica. Esto es útil cuando la cantidad o tipo de campos puede variar según el contexto.
Para mostrar mensajes de error o retroalimentación, podemos utilizar condiciones en Jinja:
{% if error %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endif %}
Si la variable error
está definida en el contexto, se mostrará un mensaje de alerta utilizando las clases de Bootstrap.
Cuando trabajamos con formularios que incluyen subida de archivos, es necesario ajustar el atributo enctype
:
<form method="post" action="{{ url_for('subir_archivo') }}" enctype="multipart/form-data">
<div class="mb-3">
<label for="archivo" class="form-label">Seleccione un archivo</label>
<input class="form-control" type="file" id="archivo" name="archivo">
</div>
<button type="submit" class="btn btn-primary">Subir</button>
</form>
El atributo enctype="multipart/form-data"
es esencial para que el formulario pueda enviar archivos al servidor.
Para mejorar la experiencia del usuario, podemos incorporar validación de formularios con Bootstrap 5:
<form class="needs-validation" method="post" action="{{ url_for('crear_usuario') }}" novalidate>
<div class="mb-3">
<label for="usuario" class="form-label">Usuario</label>
<input type="text" class="form-control" id="usuario" name="usuario" required>
<div class="invalid-feedback">
Por favor, ingrese un nombre de usuario.
</div>
</div>
<!-- Otros campos -->
<button type="submit" class="btn btn-primary">Registrar</button>
</form>
Y añadimos el siguiente script para habilitar la validación personalizada:
<script>
(function () {
'use strict'
var forms = document.querySelectorAll('.needs-validation')
Array.prototype.slice.call(forms)
.forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add('was-validated')
}, false)
})
})()
</script>
Con este código, los campos requeridos mostrarán mensajes de error si el usuario intenta enviar el formulario sin completar la información necesaria.
Para reutilizar componentes y mantener las plantillas limpias, podemos crear macros de Jinja. Por ejemplo, en un archivo _campos_formulario.html
:
{% macro campo_formulario(label, tipo, nombre) %}
<div class="mb-3">
<label for="{{ nombre }}" class="form-label">{{ label }}</label>
<input type="{{ tipo }}" class="form-control" id="{{ nombre }}" name="{{ nombre }}" required>
</div>
{% endmacro %}
Luego, en nuestra plantilla principal:
{% from '_campos_formulario.html' import campo_formulario %}
<form method="post" action="{{ url_for('crear_usuario') }}">
{{ campo_formulario('Nombre', 'text', 'nombre') }}
{{ campo_formulario('Correo electrónico', 'email', 'email') }}
{{ campo_formulario('Contraseña', 'password', 'password') }}
<button type="submit" class="btn btn-success">Enviar</button>
</form>
Las macros nos permiten crear elementos reutilizables, facilitando la modularización y mantenimiento del código.
También es posible aplicar herencia de plantillas para evitar repetir estructuras comunes. Por ejemplo, si todos los formularios comparten un layout general, podemos definir una plantilla base:
<!-- base.html -->
<!doctype html>
<html lang="es">
<head>
<!-- Meta etiquetas y enlaces de Bootstrap -->
</head>
<body>
<div class="container">
{% block contenido %}
{% endblock %}
</div>
<!-- Scripts de Bootstrap -->
</body>
</html>
Y en nuestra plantilla de formulario:
{% extends 'base.html' %}
{% block contenido %}
<h1>Registro de Usuario</h1>
<form method="post" action="{{ url_for('crear_usuario') }}">
<!-- Campos del formulario -->
<button type="submit" class="btn btn-primary">Registrar</button>
</form>
{% endblock %}
De esta manera, mantenemos una estructura coherente en todas las páginas y facilitamos la consistencia en el diseño.
Recepción de datos de formularios por POST en controlador
Al enviar un formulario mediante el método POST, los datos ingresados por el usuario son enviados al servidor para ser procesados. En Flask, manejamos esta solicitud en el controlador, definiendo una ruta que acepta métodos POST y accediendo a los datos a través del objeto request
.
Primero, importamos el objeto request
desde el módulo flask
:
from flask import Flask, request, redirect, url_for, render_template
app = Flask(__name__)
@app.route('/crear_usuario', methods=['GET', 'POST'])
def crear_usuario():
if request.method == 'POST':
nombre = request.form['nombre']
email = request.form['email']
# Procesar los datos recibidos
return redirect(url_for('usuario_creado'))
return render_template('crear_usuario.html')
En este ejemplo, la función crear_usuario
maneja tanto solicitudes GET como POST. Cuando el método es POST, accedemos a los datos del formulario mediante request.form
, que es un diccionario inmutable que contiene los valores enviados. Obtenemos el valor de un campo a través de su nombre, como request.form['nombre']
.
Es importante manejar posibles ausencias de datos o errores en la recepción. Para evitar excepciones, podemos usar el método get
con un valor por defecto:
nombre = request.form.get('nombre', '')
Si el campo nombre
no está presente en el formulario, se asignará una cadena vacía. Esto previene errores y permite implementar validaciones posteriores.
Cuando el formulario incluye archivos, los datos se obtienen de request.files
. Por ejemplo:
archivo = request.files.get('archivo')
if archivo and archivo.filename != '':
archivo.save(f'uploads/{archivo.filename}')
Aquí, verificamos que se haya enviado un archivo y que su nombre no esté vacío antes de guardarlo en una carpeta local. El manejo de archivos requiere configurar una carpeta de subida segura y validar correctamente el contenido para evitar vulnerabilidades.
Además, es recomendable utilizar el decorador @app.route
para especificar explícitamente los métodos aceptados:
@app.route('/crear_usuario', methods=['POST'])
def crear_usuario():
# ...
Sin embargo, si necesitamos manejar tanto GET como POST en la misma ruta, podemos condicionar el comportamiento dentro de la función, como se mostró anteriormente.
Para acceder a los datos enviados en la URL (query parameters), utilizamos request.args
. Pero en el caso de formularios enviados por POST, request.form
es el método adecuado.
Es fundamental tener en cuenta la seguridad al procesar datos del usuario. Por ejemplo, para evitar ataques de tipo Cross-Site Request Forgery (CSRF), es recomendable implementar medidas como tokens de autenticidad. En Flask, podemos utilizar extensiones como Flask-WTF, aunque su uso específico se tratará en secciones posteriores.
Si precisamos manejar datos JSON enviados en el cuerpo de la solicitud, podemos acceder a ellos con request.get_json()
:
datos = request.get_json()
nombre = datos.get('nombre')
email = datos.get('email')
Esto es útil cuando trabajamos con API REST y clientes que envían datos en formato JSON en lugar de formularios tradicionales.
En el contexto de una aplicación MVC, el controlador es responsable de procesar los datos recibidos y pasarlos al modelo para realizar operaciones como creación, actualización o eliminación de registros. A continuación, podemos enviar una respuesta al cliente o redirigir a otra vista utilizando funciones como redirect
y url_for
.
Por ejemplo, después de procesar el formulario, podemos redirigir al usuario a una página de confirmación:
return redirect(url_for('usuario_creado', nombre=nombre))
La función usuario_creado
puede utilizar el parámetro nombre
para personalizar el mensaje de bienvenida.
Además, si el procesamiento del formulario produce errores, es conveniente manejar estos casos y proporcionar retroalimentación al usuario. Podemos renderizar la misma plantilla del formulario, pasando mensajes de error:
error = 'El correo electrónico es inválido.'
return render_template('crear_usuario.html', error=error)
En la plantilla, utilizamos la variable error
para mostrar el mensaje correspondiente.
Es recomendable estructurar el código para mantener una separación de responsabilidades. Podemos delegar la lógica de negocio en funciones o métodos del modelo, manteniendo el controlador enfocado en la interacción entre la vista y el modelo.
Por último, es importante recordar que los datos enviados por el usuario deben considerarse no confiables. Debemos implementar validaciones y sanitización de entradas para mantener la integridad y seguridad de la aplicación.
Validación y creación de registros en el modelo
Una vez recibidos los datos del formulario en el controlador, es crucial realizar una validación exhaustiva antes de crear registros en el modelo. La validación garantiza que los datos cumplen con los criterios esperados, lo que ayuda a prevenir errores y mantener la integridad de la aplicación.
Para validar los datos dentro del controlador, se pueden implementar diversas comprobaciones. A continuación, se muestra un ejemplo práctico utilizando los datos nombre
y email
obtenidos del formulario:
from flask import request, redirect, url_for, render_template
from models import Usuario # Suponiendo que existe un modelo Usuario definido con SQLAlchemy
import re
@app.route('/crear_usuario', methods=['GET', 'POST'])
def crear_usuario():
if request.method == 'POST':
nombre = request.form.get('nombre', '').strip()
email = request.form.get('email', '').strip()
errores = []
# Validación del nombre
if not nombre:
errores.append('El nombre es obligatorio.')
elif len(nombre) < 3:
errores.append('El nombre debe tener al menos 3 caracteres.')
# Validación del correo electrónico
patron_email = r'^[\w\.-]+@[\w\.-]+\.\w+$'
if not email:
errores.append('El correo electrónico es obligatorio.')
elif not re.match(patron_email, email):
errores.append('El correo electrónico no es válido.')
if errores:
return render_template('crear_usuario.html', errores=errores, nombre=nombre, email=email)
# Creación del nuevo usuario en el modelo
nuevo_usuario = Usuario(nombre=nombre, email=email)
db.session.add(nuevo_usuario)
db.session.commit()
return redirect(url_for('usuario_creado'))
return render_template('crear_usuario.html')
En este ejemplo:
- Se utiliza una lista
errores
para recopilar mensajes de error de validación. - Se valida que
nombre
no esté vacío y tenga una longitud mínima. - Se comprueba que
email
tenga un formato correcto mediante una expresión regular. - Si existen errores, se reenvía al usuario al formulario mostrando los mensajes correspondientes.
La creación del registro en el modelo se realiza únicamente si los datos son válidos. Se crea una instancia de Usuario
y se agrega a la sesión de la base de datos:
# Creación del nuevo usuario en el modelo
nuevo_usuario = Usuario(nombre=nombre, email=email)
db.session.add(nuevo_usuario)
db.session.commit()
Es recomendable manejar posibles excepciones durante la operación de guardado. Por ejemplo, si el correo electrónico debe ser único y ya existe, se puede capturar la excepción IntegrityError
:
from sqlalchemy.exc import IntegrityError
try:
db.session.add(nuevo_usuario)
db.session.commit()
except IntegrityError:
db.session.rollback()
errores.append('El correo electrónico ya está registrado.')
return render_template('crear_usuario.html', errores=errores, nombre=nombre, email=email)
Al capturar esta excepción:
- Se realiza un rollback de la sesión para revertir cambios.
- Se añade un mensaje de error a la lista
errores
. - Se muestra nuevamente el formulario al usuario con la retroalimentación adecuada.
Para mantener el código limpio y modular, es posible trasladar la lógica de validación a una función independiente:
def validar_datos_usuario(nombre, email):
errores = []
# Validaciones del nombre
# Validaciones del email
return errores
# Dentro del controlador
errores = validar_datos_usuario(nombre, email)
Al separar la validación:
- Se mejora la legibilidad y mantenimiento del código.
- Se facilita la reutilización de la lógica de validación en otros contextos.
Además de las validaciones en el controlador, es una buena práctica definir restricciones a nivel de modelo utilizando SQLAlchemy:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Usuario(db.Model):
id = db.Column(db.Integer, primary_key=True)
nombre = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
En este modelo:
nombre
es un campo obligatorio gracias anullable=False
.email
debe ser único en la base de datos, lo que refuerza la integridad de los datos.
Al implementar restricciones en el modelo y validaciones en el controlador, se añade una doble capa de seguridad y consistencia.
Es importante también considerar la seguridad en el manejo de datos del usuario:
- Aunque SQLAlchemy previene inyecciones SQL al utilizar consultas parametrizadas, siempre es necesario validar y sanitizar los datos de entrada.
- Al renderizar datos en las plantillas, Jinja automáticamente escapa los caracteres especiales para prevenir ataques XSS.
Por ejemplo, al mostrar el nombre del usuario en una plantilla:
<p>Bienvenido, {{ usuario.nombre }}!</p>
Jinja se encarga de escapar cualquier contenido potencialmente peligroso en usuario.nombre
.
Redirecciones y mensajes de retroalimentación
Al procesar formularios en una aplicación Flask, es fundamental proporcionar retroalimentación al usuario y dirigirlo a la página adecuada tras completar una acción. Utilizando las funciones redirect
y url_for
, podemos redirigir al usuario a una ruta específica. Además, con la función flash
, es posible mostrar mensajes informativos que persisten a través de redireccionamientos.
Después de manejar una solicitud POST y realizar las operaciones necesarias (como crear un registro en la base de datos), suele ser conveniente redirigir al usuario para evitar que, al refrescar la página, se reenvíe el formulario. Esto se logra con:
from flask import redirect, url_for
@app.route('/crear_usuario', methods=['GET', 'POST'])
def crear_usuario():
if request.method == 'POST':
# Validar y procesar datos del formulario
# ...
return redirect(url_for('lista_usuarios'))
return render_template('crear_usuario.html')
En este ejemplo, tras procesar el formulario, redireccionamos al usuario a la función lista_usuarios
. De este modo, la respuesta final es una solicitud GET a la nueva ruta.
Para proporcionar mensajes de retroalimentación al usuario, Flask ofrece la función flash
. Esta permite almacenar mensajes que pueden ser recuperados y mostrados en las plantillas. Por ejemplo:
from flask import flash
@app.route('/crear_usuario', methods=['GET', 'POST'])
def crear_usuario():
if request.method == 'POST':
# Validar y procesar datos del formulario
# ...
flash('Usuario creado exitosamente.')
return redirect(url_for('lista_usuarios'))
return render_template('crear_usuario.html')
En la plantilla asociada a la vista lista_usuarios
, podemos mostrar los mensajes almacenados:
{% with mensajes = get_flashed_messages() %}
{% if mensajes %}
{% for mensaje in mensajes %}
<div class="alert alert-success" role="alert">
{{ mensaje }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
Aquí, utilizamos la función get_flashed_messages
para obtener los mensajes y los mostramos dentro de un contenedor estilizado con clases de Bootstrap.
Es posible categorizar los mensajes de retroalimentación para aplicar diferentes estilos o manejo. Al agregar una categoría al mensaje, podemos diferenciar entre éxitos, errores, advertencias, etc.:
flash('El correo electrónico ya está registrado.', 'error')
Al recuperar los mensajes en la plantilla, solicitamos las categorías:
{% with mensajes = get_flashed_messages(with_categories=True) %}
{% if mensajes %}
{% for categoria, mensaje in mensajes %}
<div class="alert alert-{{ 'danger' if categoria == 'error' else 'success' }}" role="alert">
{{ mensaje }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
En este fragmento, utilizamos una expresión condicional para asignar la clase de Bootstrap adecuada según la categoría del mensaje.
Cuando se producen errores durante la validación o procesamiento de los datos, puede ser útil mostrar los mensajes de error y conservar la información ingresada por el usuario. En lugar de redirigir, renderizamos nuevamente la plantilla del formulario, pasando los datos y mensajes correspondientes:
@app.route('/crear_usuario', methods=['GET', 'POST'])
def crear_usuario():
if request.method == 'POST':
nombre = request.form.get('nombre', '').strip()
email = request.form.get('email', '').strip()
errores = []
# Validaciones
if not nombre:
errores.append('El nombre es obligatorio.')
# Otras validaciones...
if errores:
for error in errores:
flash(error, 'error')
return render_template('crear_usuario.html', nombre=nombre, email=email)
# Crear usuario
# ...
flash('Usuario creado exitosamente.', 'success')
return redirect(url_for('lista_usuarios'))
return render_template('crear_usuario.html')
De esta forma, los mensajes de error se almacenan con flash
y se muestran en la plantilla del formulario. Además, pasamos los valores ingresados para que el usuario no tenga que reescribirlos.
En la plantilla crear_usuario.html
, añadimos la lógica para mostrar los mensajes y prellenar los campos:
{% with mensajes = get_flashed_messages(with_categories=True) %}
{% if mensajes %}
{% for categoria, mensaje in mensajes %}
<div class="alert alert-{{ 'danger' if categoria == 'error' else 'success' }}" role="alert">
{{ mensaje }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="post" action="{{ url_for('crear_usuario') }}">
<div class="mb-3">
<label for="nombre" class="form-label">Nombre</label>
<input type="text" class="form-control" id="nombre" name="nombre" value="{{ nombre }}">
</div>
<div class="mb-3">
<label for="email" class="form-label">Correo electrónico</label>
<input type="email" class="form-control" id="email" name="email" value="{{ email }}">
</div>
<button type="submit" class="btn btn-primary">Registrar</button>
</form>
Al utilizar las variables nombre
y email
, los campos del formulario mantienen los datos ingresados previamente.
Es importante resaltar que los mensajes almacenados con flash
se mantienen solo durante una solicitud, lo que los hace ideales para redireccionamientos. Sin embargo, al renderizar la plantilla directamente, como en el caso de errores de validación, los mensajes estarán disponibles en la misma solicitud.
Para mejorar la experiencia de usuario, podemos manipular las categorías de mensajes y asociarlas con diferentes estilos de Bootstrap:
success
para mensajes de éxito (clasealert-success
).error
odanger
para errores (clasealert-danger
).warning
para advertencias (clasealert-warning
).info
para información general (clasealert-info
).
Al manipular categorías y estilos, proporcionamos retroalimentación clara y consistente al usuario.
Manejo de posibles errores en el proceso
Durante el procesamiento de formularios y la interacción con el modelo, es probable que surjan errores que deban ser manejados adecuadamente para mantener la integridad de la aplicación y ofrecer una buena experiencia al usuario. En este apartado, exploraremos cómo manejar excepciones y errores durante el proceso de creación de registros en el controlador, asegurando que la aplicación responda de forma coherente y segura.
Es fundamental anticipar y capturar las excepciones que pueden ocurrir durante la manipulación de datos o las operaciones con la base de datos. Por ejemplo, al intentar guardar un nuevo registro, pueden producirse errores de integridad, como violaciones de restricciones de unicidad o problemas de conexión.
Manejo de excepciones con try-except
Para manejar estas situaciones, utilizamos bloques try-except
en el controlador al momento de interactuar con el modelo:
from flask import request, redirect, url_for, render_template, flash
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
@app.route('/crear_usuario', methods=['GET', 'POST'])
def crear_usuario():
if request.method == 'POST':
nombre = request.form.get('nombre', '').strip()
email = request.form.get('email', '').strip()
errores = validar_datos_usuario(nombre, email)
if errores:
for error in errores:
flash(error, 'error')
return render_template('crear_usuario.html', nombre=nombre, email=email)
nuevo_usuario = Usuario(nombre=nombre, email=email)
try:
db.session.add(nuevo_usuario)
db.session.commit()
flash('Usuario creado exitosamente.', 'success')
return redirect(url_for('lista_usuarios'))
except IntegrityError:
db.session.rollback()
flash('El correo electrónico ya está registrado.', 'error')
return render_template('crear_usuario.html', nombre=nombre, email=email)
except SQLAlchemyError as e:
db.session.rollback()
flash('Ocurrió un error al guardar el usuario. Por favor, inténtelo de nuevo.', 'error')
# Aquí podríamos registrar el error para análisis posterior
app.logger.error(f'Error al guardar usuario: {e}')
return render_template('crear_usuario.html', nombre=nombre, email=email)
return render_template('crear_usuario.html')
En este código:
- Utilizamos un bloque
try-except
alrededor de las operaciones que interactúan con la base de datos. - Capturamos la excepción
IntegrityError
para manejar casos específicos, como violaciones de unicidad. - Realizamos un
rollback
de la sesión condb.session.rollback()
para revertir cualquier cambio pendiente en la transacción. - Capturamos una excepción general
SQLAlchemyError
para manejar otros posibles errores relacionados con la base de datos. - Registramos el error utilizando
app.logger.error()
para facilitar el diagnóstico posterior sin mostrar detalles técnicos al usuario.
Informando al usuario sin revelar detalles técnicos
Es vital proporcionar mensajes de error al usuario que sean útiles pero sin exponer información sensible o detalles internos de la aplicación. En lugar de mostrar el mensaje de excepción directamente, ofrecemos una descripción general del problema:
flash('Ocurrió un error al guardar el usuario. Por favor, inténtelo de nuevo.', 'error')
Esto evita que posibles atacantes obtengan información sobre la estructura de la base de datos o la lógica interna.
Manejo de errores inesperados
Para capturar cualquier otro error no previsto, podemos agregar una cláusula except Exception as e
:
except Exception as e:
db.session.rollback()
flash('Se produjo un error inesperado. Por favor, contacte al administrador.', 'error')
app.logger.error(f'Error inesperado: {e}')
return render_template('crear_usuario.html', nombre=nombre, email=email)
De esta forma, garantizamos que incluso los errores no anticipados sean manejados sin que la aplicación se bloquee, y podemos registrar el evento para su revisión.
Uso de decoradores para manejo global de errores
Para evitar repetir código de manejo de excepciones en múltiples controladores, podemos implementar un manejo global de errores utilizando el decorador @app.errorhandler
de Flask:
@app.errorhandler(500)
def error_500(e):
return render_template('500.html'), 500
Cuando ocurre una excepción no manejada que genera un error 500 Internal Server Error, esta función captura el error y devuelve una plantilla personalizada. En la plantilla 500.html
, ofrecemos al usuario un mensaje amigable:
<!doctype html>
<html lang="es">
<head>
<!-- Metadatos y enlaces -->
</head>
<body>
<h1>Error interno del servidor</h1>
<p>Lo sentimos, se ha producido un error inesperado. Por favor, inténtelo más tarde.</p>
</body>
</html>
Personalización de errores HTTP comunes
Del mismo modo, podemos manejar otros errores HTTP como 404 Not Found o 403 Forbidden:
@app.errorhandler(404)
def error_404(e):
return render_template('404.html'), 404
@app.errorhandler(403)
def error_403(e):
return render_template('403.html'), 403
Esto mejora la experiencia del usuario al proporcionar páginas de error coherentes y personalizadas.
Validación de datos en el modelo
Además de las validaciones en el controlador, es posible definir validaciones adicionales en el modelo utilizando técnicas como validadores a nivel de clase o propiedades personalizadas. Esto asegura que los datos cumplen con los requisitos antes de interactuar con la base de datos.
class Usuario(db.Model):
id = db.Column(db.Integer, primary_key=True)
nombre = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
@classmethod
def validar_email_unico(cls, email):
existe = cls.query.filter_by(email=email).first()
if existe:
raise ValueError('El correo electrónico ya está registrado.')
Podemos utilizar este método en el controlador:
try:
Usuario.validar_email_unico(email)
nuevo_usuario = Usuario(nombre=nombre, email=email)
db.session.add(nuevo_usuario)
db.session.commit()
flash('Usuario creado exitosamente.', 'success')
return redirect(url_for('lista_usuarios'))
except ValueError as ve:
flash(str(ve), 'error')
return render_template('crear_usuario.html', nombre=nombre, email=email)
except SQLAlchemyError as e:
# Manejo de otros errores
Evitar pérdidas de datos y mantener la integridad
Al manejar errores, es esencial garantizar que los datos de la sesión de base de datos estén en un estado coherente. Por ello, el rollback
es una práctica necesaria cuando se produce una excepción después de iniciar una transacción. De esta manera, revertimos cualquier cambio parcial y prevenimos inconsistencias.
Registro de errores para monitoreo
El registro de errores mediante app.logger
permite mantener un historial de los problemas que ocurren en la aplicación. Es recomendable configurar el nivel de registro y la salida adecuada, por ejemplo, escribiendo en un archivo de log o utilizando sistemas de monitoreo externos.
import logging
from logging.handlers import RotatingFileHandler
if not app.debug:
# Configurar registro en archivo
file_handler = RotatingFileHandler('errores.log', maxBytes=10240, backupCount=10)
file_handler.setLevel(logging.ERROR)
app.logger.addHandler(file_handler)
Esto facilita la detección y resolución de problemas, y es una práctica común en entornos de producción.
Manejo de errores en subprocesos asíncronos
Si la aplicación realiza operaciones asíncronas o utiliza tareas en segundo plano, es importante manejar los errores que puedan ocurrir en estos procesos y comunicarlos adecuadamente.
Mejores prácticas
- No exponer datos sensibles: evitar mostrar información de depuración o detalles internos en entornos de producción.
- Proporcionar mensajes claros: los mensajes de error deben ser comprensibles para el usuario sin revelar detalles técnicos.
- Registrar excepciones: utilizar herramientas de registro y monitoreo para mantener un seguimiento de los errores.
- Validación exhaustiva: combinar validaciones en el controlador y en el modelo para asegurar la integridad de los datos.
- Pruebas: implementar pruebas unitarias y de integración que contemplen escenarios de error para garantizar que el manejo es el adecuado.
Al seguir estas prácticas, mejoramos la robustez y confiabilidad de la aplicación, ofreciendo a los usuarios una experiencia segura y consistente incluso ante situaciones inesperadas.
Todas las lecciones de Flask
Accede a todas las lecciones de Flask y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Flask
Introducción Y Entorno
Instalación Y Configuración Flask Con Venv
Introducción Y Entorno
Mysql Con Sqlalchemy En Flask
Modelos Y Migraciones
Tipos De Datos En Modelos
Modelos Y Migraciones
Operaciones Crud Y Consultas
Modelos Y Migraciones
Asociaciones De Modelos
Modelos Y Migraciones
Migraciones Con Flask-migrate
Modelos Y Migraciones
Rutas Endpoints Rest Get
Api Rest
Respuestas Con Esquemas Flask Marshmallow
Api Rest
Rutas Endpoints Rest Post, Put Y Delete
Api Rest
Manejo De Errores Y Códigos De Estado Http
Api Rest
Autenticación Jwt Con Flask-jwt-extended
Api Rest
Controlador Mvc Con Métodos Get En Flask
Mvc
Sintaxis De Plantillas Jinja 2 En Flask
Mvc
Controlador Mvc Con Métodos Post En Flask
Mvc
Inclusión De Archivos Estáticos En Jinja
Mvc
Validación De Formularios Con Wtforms
Mvc
Subir Archivos En Formularios Jinja En Flask
Mvc
Autenticación Con Flask-login
Mvc
Autorización Con Flask-principal
Mvc
Qué Son Los Blueprints Y Cómo Crear Uno
Blueprints
Integrar Openai Api En Flask Api Rest
Aplicación Con Ia
Sqlalchemy Orm En Flask Mysql
Aplicación Con Ia
Resultados De Ia Con Jinja En Flask
Aplicación Con Ia
En esta lección
Objetivos de aprendizaje de esta lección
- Dominio de la sintaxis Jinja para plantillas.
- Creación de formularios dinámicos en Flask.
- Uso de Bootstrap 5 para estilizar formularios.
- Implementación de validación en formularios.
- Manipulación de solicitudes POST en Flask.
- Manejo seguro de subida de archivos.
- Comunicación efectiva entre vistas y controladores.