Índice de contenido
- Bootstrap-Flask
- Configuración e Inicialización
- ️ Flask-Migrate
- Instalación y Configuración
- Inicialización del Repositorio
- Generar y Aplicar Migraciones
- Modificar y Revertir Cambios
- Incorporar nuevas tablas
- Flask-Babel
- Instalación
- Configuración del Selector de Idioma
- Inicialización en el código
- Marcado de Textos para Traducir
- El Proceso de Extracción y Traducción
- 1. Archivo de configuración (babel.cfg)
- 2. Extracción de textos
- 3. Inicialización de idiomas
- 4. Traducción manual
- 5. Compilación
- Configuración del Directorio de Traducciones
- Flask-Admin
- Instalación
- Configuración Inicial
- Creación de Vistas de Modelo (ModelView)
- Registro de Modelos
- Explorando el Panel de Administración
- Personalización de Flask-Admin mediante Atributos
- Personalización de Flask-Admin mediante Atributos
- Creación de un ModelView Personalizado
- Manipulación de Datos: Exportación, Búsqueda y Filtros
- 1. Exportación de Datos
- Limitar la exportación
- Opciones de Búsqueda (Search)
- Opciones de Filtrado (Filters)
- Manejo de Fechas con el Date Picker
- 1. Uso de Columnas Tipo DateTime
- 2. Cambio a Tipo Date (Solo Fecha)
- Conclusión
- Creación de Campos de Selección con form_choices
- 1. Definición de Opciones (Choices)
- 2. Formato de las Tuplas (Valor y Label)
- Ejemplo
- Validación de Formularios con WTForms
- 1. Configuración de form_args
- 2. Implementación de Etiquetas y Validadores
- 3. Validando Múltiples Campos
- El Problema: Contraseñas sin Hash en Flask-Admin
- Buscando una solución en la documentación
- Implementación del método on_model_change
- Aplicando el Hash a la contraseña
- Métodos adicionales y casos de uso
- El Problema: El Hash en el Formulario de Edición
- Explorando la Documentación de ModelView
- Implementación del método edit_form
- Personalizando los Campos de manera Dinámica
- Personalización visual de Flask-Admin
- Extendiendo los Templates Oficiales
- Uso de Bloques para Personalización Avanzada
- Personalización de plantillas específicas en Flask-Admin
- Personalizando la vista de listado (list.html)
- Integración de estilos externos (FontAwesome)
- Protegiendo Flask-Admin con Flask-Login
- Métodos clave: is_accessible e inaccessible_callback
- Optimización mediante Herencia
- Verificación final
- Flask Debug Toolbar
- Instalación e Integración
- Requisitos previos
- Implementación en el Código
- Explorando las Funcionalidades del Toolbar
- Análisis de Consultas (Queries)
- Flask CLI
- Flask-Security-Too: El Estándar Moderno para Autenticación
- ¿Qué ofrece Flask-Security-Too?
- Dependencias Principales
- Instalación y Configuración Básica
- Ejemplo de Implementación Rápida
- ¿Por qué elegirlo sobre Flask-User?
- Flask User (Legacy)
- Flask-Restless (Legacy)
- Instalación
- Ejemplo con Flask-RESTful (Manual)
- Clase Base y Utilidades
- Recursos: Gestión de Categorías
- Recurso de Búsqueda
- Configuración del API Manager
- Acceso a los Recursos y Rutas
- Personalización de Métodos y Prefijos
- Control de Métodos HTTP
- Cambio de la URL (Prefijo)
- Operaciones Avanzadas
- Pruebas de Operaciones CRUD con Flask-Restless
- Creación de Recursos (POST)
- Manejo de errores comunes
- Actualización de Registros (PATCH y PUT)
- Eliminación de Recursos (DELETE)
- Operaciones de Búsqueda y Filtrado en Flask-Restless
- Uso de Filtros (filters)
- Ejemplos Prácticos en Postman:
- Paginación y Ordenamiento
- Conclusión
- Lo que NO debes usar (Legacy/Obsoleto)
La verdadera magia de Flask no reside únicamente en su núcleo minimalista, sino en su capacidad casi ilimitada de extensión. Al ser un micro-framework, Flask nos entrega las herramientas justas para empezar, pero nos otorga la libertad total de elegir qué "músculo" queremos añadir a nuestra aplicación según las necesidades de nuestro proyecto.
En este capítulo, daremos el salto de una aplicación básica a una estructura profesional y robusta. Aprenderás a integrar componentes críticos que toda aplicación moderna requiere: desde interfaces visuales con Bootstrap 5 y gestión automatizada de bases de datos con Flask-Migrate, hasta sistemas complejos de administración con Flask-Admin y optimización de rendimiento mediante Flask-Caching.
Nota del autor: El ecosistema de Python evoluciona rápidamente. A lo largo de estas lecciones, no solo aprenderás a implementar estas herramientas, sino que también identificaremos qué paquetes siguen siendo el estándar de la industria y cuáles han cedido su lugar a alternativas más modernas y seguras, como el paso de Flask-Mail al renovado Flask-mailman.
A lo largo del libro, hemos empleado varias extensiones específicas para Flask para poder implementar varias funcionalidades, entre las principales tenemos:
- Flask-WTF, Definir formularios y validaciones en base a clases y utilizar en el lado del cliente y servidor.
- Flask-SQLAlchemy, Usar para poder conectar la aplicación a una base de datos.
- Flask-Migrate, Para crear un sistema de migraciones para las tablas en la base de datos.
- Flask-Restless, Para crear una Api Rest.
- Flask-User, Para crear un módulo para el usuario.
Todas estas extensiones son muy empleadas en los proyectos en Flask ya que la mayoría de las aplicaciones web necesitan de estas características para poder implementar las distintas funcionalidades de la misma.
Flask, al ser un micro framework como comentamos al inicio, trae lo mínimo y necesario para poder funcionar y es mediante estas extensiones que podemos potenciar al framework; en este apartado veremos cómo emplear otras extensiones que seguramente te resultará de interés y que no hemos podido emplear en el proyecto por la sencilla razón de que no han sido necesarias.
Para este apartado, te recomiendo que clones el proyecto que ya tenemos y lo llamaremos como:
my_app_extensions/
Creamos el ambiente virtual e instalamos las dependencias con:
$ pip freeze > requirements.txtEsto lo manejamos de esta manera ya que las implementaciones realizadas aquí son netamente experimentales y no queremos que formen parte del proyecto que hemos estado desarrollando.
Bootstrap-Flask
Vamos a conocer un paquete bastante interesante que sería el de Flask-Bootstrap5, que como puedes suponer, simplemente nos permite integrar Bootstrap 5 en nuestro proyecto de Flask de manera nativa.
Es muy importante que tengas en cuenta la versión que necesitas, ya que existen diferentes paquetes para cada generación de Bootstrap:
- Flask-Bootstrap: Instala la versión 3 (obsoleta para proyectos modernos).
- Flask-Bootstrap4: Para la versión 4.
- Flask-Bootstrap5: Es la versión que emplearemos, ya que es la actual y la que mejor se adapta a los estándares modernos sin depender de jQuery.
- Bootstrap-Flask: Otra versión de Bootstrap 5 que es la que usamos.
Como puedes ver en la documentación oficial de Bootstrap, nosotros estamos empleando la versión 5, por lo tanto, los paquetes anteriores vienen siendo obsoletos para este fin. Para instalarlo, simplemente ejecutamos:
$ pip install Bootstrap-FlaskLe damos un enter; yo en mi caso ya lo tenía instalado, así que el proceso fue bastante rápido. Así que ahora simplemente tenemos que emplear este paquete.
Configuración e Inicialización
Configuramos:
from flask import Flask
from flask_bootstrap import Bootstrap5
app = Flask(__name__)
bootstrap = Bootstrap5(app)Y cargamos el CSS y JavaScript:
<head>
....
{{ bootstrap.load_css() }}
</head>
<body>
...
{{ bootstrap.load_js() }}
</body>Opinión del autor: Es el que menos me gusta. Personalmente, prefiero usar la CDN directa de Bootstrap para tener control total sobre las versiones. Los paquetes suelen ir por detrás de los lanzamientos oficiales. Si decides usarlo, asegúrate de instalar la versión 5 y no las anteriores.
Uso: Importar y cargar el CSS/JS directamente en tus plantillas Jinja2.
️ Flask-Migrate
Vamos a conocer un nuevo módulo bastante interesante que nos permitirá manejar fácilmente nuestra base de datos.
Recordarás que, anteriormente, cuando quisimos agregar una nueva columna a la tabla de productos, lo hicimos de manera manual para agilizar el proceso. Esto fue necesario porque ya habíamos creado las tablas secciones atrás; de no haberlo hecho así, habríamos tenido que borrarlas todas y volverlas a crear automáticamente desde Flask para incorporar el cambio.
Para evitar este problema al integrar nuevas tablas o renombrar columnas, existe un módulo llamado Flask-Migrate. Si has tomado mis cursos de Laravel o CodeIgniter, esto te resultará familiar, ya que sigue el mismo esquema de las migraciones: manejar los distintos estados o versiones de nuestra base de datos.
Instalación y Configuración
Lo primero que debemos hacer es instalar el módulo mediante pip:
$ pip install Flask-MigrateUna vez instalado, debemos integrarlo en nuestra aplicación. Aunque Flask no obliga a usar una estructura MVC, siempre es lo recomendado para mantener la organización. Necesitaremos tres elementos clave:
- La instancia de nuestra aplicación Flask (app).
- La configuración de la base de datos.
- La instancia de SQLAlchemy (db).
Integración en el código:
En nuestro archivo __init__.py, importamos el módulo y creamos la instancia de migrate pasándole tanto la app como la db:
from flask_migrate import Migrate
# ...
migrate = Migrate(app, db)Inicialización del Repositorio
Flask-Migrate detectará automáticamente todos los modelos que tengamos definidos en la aplicación, sin importar que estén en archivos separados. El primer comando que debemos ejecutar en la terminal es:
$ flask db initEste comando inicializa un repositorio de migraciones. Verás que se crea una carpeta llamada migrations en tu proyecto. Es muy similar a lo que hace Git con git init. Dentro encontrarás una carpeta llamada versions, que de momento estará vacía.
Nota importante: Antes de continuar, he creado una nueva base de datos vacía para estas pruebas y he comentado la línea db.create_all() en el código, ya que a partir de ahora el control de las tablas lo llevará Flask-Migrate.
Generar y Aplicar Migraciones
Para crear nuestras tablas por primera vez (o detectar cambios nuevos), utilizamos el comando migrate:
$ flask db migrate -m “Initial migration”Este comando detecta nuestros modelos y genera un archivo de script dentro de migrations/versions. Este archivo contiene dos funciones principales:
- upgrade(): Aplica los cambios (crear tablas, agregar columnas).
- downgrade(): Revierte los cambios realizados.
Para aplicar finalmente estos cambios en la base de datos real, ejecutamos:
$ flask db upgradeModificar y Revertir Cambios
Lo más interesante de Flask-Migrate es la facilidad para modificar la estructura. Por ejemplo, si decidimos renombrar la columna username a user en el modelo de Usuarios:
- Hacemos el cambio en el archivo models.py.
- Ejecutamos flask db migrate para que el sistema detecte que se borró una columna y se agregó otra.
- Ejecutamos flask db upgrade para aplicar el cambio.
Si te equivocas, puedes usar flask db downgrade para volver al estado anterior.
Incorporar nuevas tablas
Si creamos un modelo nuevo (por ejemplo, una clase Test), simplemente ejecutamos migrate y luego upgrade. Flask-Migrate detectará que es un elemento nuevo y lo incorporará sin afectar el contenido de las tablas que ya existían.
De esta forma, mantienes un historial de versiones de tu base de datos, permitiéndote subir o bajar de versión según lo necesites durante el desarrollo de tu aplicación.
Flask-Babel
Vamos a conocer un paquete bastante interesante llamado Flask-Babel. Como puedes suponer, este módulo sirve para agregar la funcionalidad de translation, es decir, las traducciones a nuestra aplicación. Gracias a esto, podremos ofrecer nuestra plataforma en múltiples idiomas.
Aunque el proceso puede parecer un poco enredado debido a que emplea varios comandos, esto se debe a que el sistema está muy automatizado. Flask-Babel traduce nuestras aplicaciones mediante archivos de traducción específicos.
Instalación
Para instalarlo, simplemente ejecutamos:
$ pip install Flask-BabelEs un paquete liviano, por lo que la instalación será rápida. Una vez instalado, ya tendremos todas las referencias necesarias en nuestro proyecto.
Configuración del Selector de Idioma
Lo primero que debemos configurar es una función llamada get_locale. En ella indicamos qué idioma debe emplear la aplicación por defecto. Podemos hacerlo de varias maneras:
- Hardcoded: Establecer un idioma fijo (ej. español o inglés).
- Configuración de usuario: Obtenerlo del perfil del usuario autenticado.
- Selector del navegador: Consultar los headers del navegador para detectar el idioma preferido del usuario. Esta es la mejor opción para que la experiencia sea dinámica.
Inicialización en el código
Primero, debemos importar e inicializar Babel en nuestro archivo principal:
from flask_babel import Babel
babel = Babel(app)Luego, definimos la función con el decorador @babel.localeselector (o el correspondiente según tu versión de Babel):
@babel.localeselector
def get_locale():
# En esta prueba retornamos 'es' para español
return 'es' Marcado de Textos para Traducir
Para que Flask-Babel sepa qué palabras traducir, debemos usar la función gettext. Primero la importamos y luego envolvemos nuestras cadenas de texto con ella:
from flask_babel import gettext
# Ejemplo en un controlador
print(gettext('name'))
print(gettext('save'))El Proceso de Extracción y Traducción
Aquí es donde el proceso se vuelve interesante. Debemos seguir una serie de pasos técnicos para generar los archivos:
1. Archivo de configuración (babel.cfg)
Creamos un archivo llamado babel.cfg para indicar qué archivos debe analizar el sistema en busca de textos para traducir (archivos .py y .html de Jinja2):
[python: **.py]
[jinja2: **/templates/**.html]2. Extracción de textos
Ejecutamos el comando de extracción para generar un archivo base llamado messages.pot. Este archivo contendrá todas las "keys" que definimos con gettext:
$ pybabel extract -F babel.cfg -o messages.pot .3. Inicialización de idiomas
Ahora creamos las carpetas para cada idioma específico (ej. español e inglés) basándonos en el archivo .pot:
pybabel init -i messages.pot -d translations -l es
pybabel init -i messages.pot -d translations -l en4. Traducción manual
Dentro de la carpeta translations, encontraremos archivos .po. Aquí es donde nosotros escribimos manualmente la traducción para cada palabra (ej. "Save" -> "Guardar").
5. Compilación
Finalmente, compilamos las traducciones a un formato binario (.mo) que la aplicación pueda leer rápidamente:
$ pybabel compile -d translationsConfiguración del Directorio de Traducciones
Si al ejecutar tu aplicación ves que no se aplican las traducciones y solo aparece la "key", es probable que Flask no esté encontrando la carpeta translations.
Debido a que nuestra estructura de carpetas es personalizada, debemos especificar la ruta en la configuración:
BABEL_TRANSLATION_DIRECTORIES = ‘ruta/completa/a/tu/carpeta/translations’Una vez configurada la ruta absoluta, si tu navegador está en español, la aplicación mostrará automáticamente "Nombre" o "Guardar". Si cambias el selector a inglés, verás los textos con los cambios que realizaste en el archivo de traducción de inglés.
Opinión del autor: Esencial para la internacionalización (traducciones). Es un proceso manual pero robusto:
- Marcar textos con gettext o _().
- Crear un archivo de mapeo y extraer los textos.
- Inicializar los idiomas y compilar los archivos binarios (.mo).
Flask-Admin
Vamos a conocer otro módulo bastante interesante llamado Flask-Admin. Si has tomado mi curso de Master en CodeIgniter, verás que es muy similar a la librería Grocery CRUD, ya que nos permite generar interfaces de administración (CRUD) de manera fácil y rápida.
Instalación
Para comenzar, vamos a su repositorio en GitHub para obtener el comando de instalación. Simplemente ejecutamos en la terminal:
$ pip install flask-adminAl igual que en los videos anteriores, yo ya lo tengo instalado en mi equipo, por lo que el proceso será inmediato.
Configuración Inicial
Lo primero que debemos hacer es importar el módulo e inicializarlo en nuestra aplicación.
- Importación: En el archivo __init__.py, importamos la clase principal:
from flask_admin import Admin
- Instancia: Creamos la instancia después de la configuración de la base de datos (db).
- Modo de Plantilla: Flask-Admin soporta varios estilos; en este caso, usaremos Bootstrap 3.
from flask_admin import Admin
# ... después de configurar la DB
admin = Admin(app, name='Mi Panel', template_mode='bootstrap3')Creación de Vistas de Modelo (ModelView)
Para que Flask-Admin sepa qué tablas administrar, debemos emplear algo llamado ModelView. Este es un componente dedicado a manejar las páginas de creación, edición, actualización y eliminación de nuestro CRUD.
Debemos importar el soporte específico para SQLAlchemy:
from flask_admin.contrib.sqla import ModelViewRegistro de Modelos
Para registrar un modelo, usamos el método add_view. Es importante pasarle el modelo y la sesión de la base de datos (db.session).
Para esta primera prueba, usaremos el modelo de Usuarios, ya que es más sencillo. Evitaremos de momento los modelos de Categorías o Productos porque tienen relaciones complejas que podrían dar problemas si no se configuran con cuidado.
Corrección de error común: Asegúrate de importar tus modelos después de haber inicializado la base de datos (db). Si intentas importar el modelo User al principio del archivo, podrías obtener un error porque el recurso db aún no existe.
# Dentro del bloque de configuración
from my_app.auth.models import User
admin.add_view(ModelView(User, db.session))Explorando el Panel de Administración
Una vez que la aplicación está corriendo, podemos acceder a la interfaz mediante la ruta:
http://localhost:5000/admin
Al entrar, verás que Flask-Admin ha creado automáticamente todo por nosotros:
- Pestaña de Usuarios: Aparece una tabla con todos los registros.
- Operaciones CRUD: Tienes botones para crear, editar y borrar.
- Manejo de Enums: Si tu modelo tiene campos tipo Enum (como el rol del usuario), Flask-Admin genera automáticamente un menú desplegable para que selecciones la opción correcta.
- Formularios automáticos: Al darle a "Crear nuevo", el sistema genera los campos necesarios basados en las columnas de tu base de datos.
Es un módulo sumamente potente que nos ahorra horas de trabajo manual en la creación de paneles internos.
Personalización de Flask-Admin mediante Atributos
Vamos a seguir trabajando con Flask-Admin para personalizar su comportamiento. En esta sección, veremos cómo ocultar columnas específicas y cómo deshabilitar ciertas operaciones (crear, editar o eliminar).
Para profundizar, podemos consultar en la documentación oficial la sección de "ModelView configuration attributes". Como verás, el mecanismo consiste en crear una clase personalizada que herede de ModelView y definir en ella los parámetros deseados.
Personalización de Flask-Admin mediante Atributos
Vamos a seguir trabajando con Flask-Admin para personalizar su comportamiento. En esta sección, veremos cómo ocultar columnas específicas y cómo deshabilitar ciertas operaciones como crear, editar o eliminar.
Para profundizar, podemos consultar en la documentación oficial la sección de "ModelView configuration attributes". Como verás, el mecanismo consiste en crear una clase personalizada que herede de ModelView y definir en ella los parámetros deseados.
Creación de un ModelView Personalizado
En lugar de utilizar la clase ModelView por defecto, vamos a definir la nuestra en el archivo donde se encuentra el modelo (por ejemplo, en user.py).
1. Definición de la Clase
Primero, debemos asegurarnos de importar ModelView y luego crear nuestra clase personalizada que reciba este como parámetro:
from flask_admin.contrib.sqla import ModelView
class UserModelView(ModelView):
# Aquí definiremos los atributos de personalización
pass2. Restricción de Operaciones
Supongamos que no queremos que los registros de usuario se puedan editar. Para ello, utilizamos el atributo can_edit. Puedes probar otros como can_create o can_delete según tus necesidades:
class UserModelView(ModelView):
can_edit = False # Deshabilita la opción de edición3. Exclusión de Columnas
Para remover columnas sensibles del listado (como el hash de la contraseña), utilizamos el atributo column_exclude_list. Este recibe una lista (array) con los nombres de los campos que queremos omitir:
class UserModelView(ModelView):
can_edit = False
column_exclude_list = ['password_hash'] # Excluye la columna del listadoRegistro del ModelView en la Aplicación
Una vez definida nuestra clase personalizada, debemos indicarle a Flask-Admin que la utilice. Para ello, regresamos a nuestro archivo de inicialización (__init__.py):
Importamos nuestra nueva clase: UserModelView.
- Actualizamos el registro: En lugar de pasar el ModelView genérico, pasamos nuestra clase personalizada al crear la instancia en el admin.
# En el archivo de configuración del Admin (__init__.py)
admin.add_view(UserModelView(User, db.session))Al refrescar la aplicación, verás que la columna de contraseña ha desaparecido del listado y que el botón de edición ya no está disponible. Así de fácil es personalizar las columnas y las operaciones en nuestro panel administrativo.
Manipulación de Datos: Exportación, Búsqueda y Filtros
Ahora vamos a aprender cómo manipular la data fácilmente en Flask-Admin, ya sea exportándola o realizando operaciones de búsqueda y filtrado directamente desde el listado.
1. Exportación de Datos
Para habilitar esta función, regresamos a la documentación oficial y buscamos la opción de export. Básicamente, se trata de activar un atributo llamado can_export.
Por defecto, los datos se exportarán en formato CSV, el cual puedes abrir perfectamente con un software tipo Excel.
class UserModelView(ModelView):
can_export = TrueSi refrescamos la aplicación, veremos una nueva opción de exportación. Al hacer clic, se generará un archivo que incluye todas nuestras columnas (username, password_hash, rol, etc.). Por defecto, la data vendrá separada por comas.
Limitar la exportación
Si necesitas indicar un número máximo de elementos a exportar, puedes usar el atributo export_max_rows. Esto es útil para evitar sobrecargas en la base de datos:
class UserModelView(ModelView):
can_export = True
export_max_rows = 1 # Solo exportará el primer registro encontradoOpciones de Búsqueda (Search)
Para agregar una barra de búsqueda en nuestra tabla, utilizamos el atributo column_searchable_list. Podemos especificar qué columnas queremos que sean rastreables.
Nota: No tiene sentido buscar por la columna de password, ya que al estar convertida en un hash, los caracteres no coincidirán con lo que el usuario escriba. Es mejor excluirla.
class UserModelView(ModelView):
# Definimos que se puede buscar por username y rol
column_searchable_list = ['username', 'role']Al refrescar, aparecerá un campo de entrada (input) donde podrás filtrar rápidamente. Por ejemplo, si buscas "regular", la tabla mostrará solo los usuarios con ese rol.
Opciones de Filtrado (Filters)
El filtrado es un mecanismo distinto que permite realizar operaciones lógicas más complejas. Para activarlo, usamos column_filters. Esta lista puede ser igual o completamente distinta a la de búsqueda.
class UserModelView(ModelView):
column_filters = ['username', 'role']Al usar los filtros, verás que tienes opciones avanzadas. Primero seleccionas la columna y luego la operación (si es igual a, si no es igual, si contiene, etc.). Por ejemplo:
- Seleccionas el filtro por Rol.
- Indicas la operación "equals" (igual a).
- Seleccionas o escribes "regular".
Aplicas el filtro y la tabla se actualizará automáticamente.
Este sistema es muy potente porque te permite combinar múltiples reglas para encontrar registros específicos de forma granular.
Manejo de Fechas con el Date Picker
Como habrás notado en los videos anteriores, agregué de manera temporal una nueva columna a nuestro modelo de usuarios llamada birthday (fecha de nacimiento). Vamos a ver cómo se comporta este campo según el tipo de dato que definamos.
1. Uso de Columnas Tipo DateTime
En la definición inicial de nuestro usuario, configuramos esta columna como un tipo DateTime.
Si regresamos a la aplicación y seleccionamos la opción de Crear o Actualizar, veremos que al hacer clic en el campo se despliega automáticamente un DayPicker (el selector de fecha que emplea Bootstrap 3 por defecto). Al ser una columna de tipo DateTime, el componente nos permite seleccionar tanto la fecha como la hora exacta.
# Ejemplo de definición en el modelo
birthday = db.Column(db.DateTime)2. Cambio a Tipo Date (Solo Fecha)
¿Qué pasa si solo nos interesa la fecha, que es lo ideal para un cumpleaños? Para simplificar la interfaz, podemos cambiar la definición de la columna simplemente a Date.
Al realizar este cambio y refrescar la aplicación, notarás que el selector ahora solo muestra el calendario para elegir el día, mes y año, eliminando por completo las opciones de horas y minutos.
# Definición optimizada para cumpleaños
birthday = db.Column(db.Date)Conclusión
Así de fácil es emplear un DayPicker en Flask-Admin: solo necesitas indicar correctamente si tu columna es de tipo Date o DateTime en tu modelo de base de datos, y el framework se encargará de renderizar el componente visual adecuado para el usuario.
Creación de Campos de Selección con form_choices
Ahora bien, ¿qué pasa si queremos que un campo solo pueda tomar un número determinado de valores? En la práctica, esto consiste en convertir un campo de texto en una lista de selección (select).
1. Definición de Opciones (Choices)
Para lograr esto, vamos a consultar la documentación oficial bajo el apartado de Choices. El atributo que debemos utilizar es form_choices. En este, debemos indicar el nombre del campo (en nuestro caso, username) y una lista de los valores permitidos.
Para facilitar la demostración, vamos a copiar la estructura de la documentación y pegarla en nuestra clase ModelView:
class UserModelView(ModelView):
form_choices = {
'username': [
('usuario1', 'Usuario'),
('admin', 'Administrador'),
('editor', 'Editor')
]
}2. Formato de las Tuplas (Valor y Label)
Como puedes observar, cada opción se define mediante una tupla (una pareja de valores):
- Primer valor: Es el dato real que se guardará en la base de datos.
- Segundo valor: Es el "label" o etiqueta que el usuario verá en la interfaz (por ejemplo, "Usuario").
Si refrescamos la aplicación y nos dirigimos a la sección de Crear, veremos que el campo username ya no es un cuadro de texto libre, sino un campo de tipo select, muy parecido al que ya tenemos configurado para el rol.
Ejemplo
Si agregamos una nueva tupla como ('user_final', 'Usuario Final'), al guardar y refrescar, tendremos disponible esa nueva opción en el menú desplegable. Esta técnica es excelente para limitar las entradas del usuario y evitar errores en la carga de datos.
Aquí tienes el texto mejorado. He organizado la información con títulos, corregido la puntuación y ajustado la terminología técnica (como WTForms y InputRequired) para que la explicación sea más profesional y precisa.
Validación de Formularios con WTForms
Ahora vamos a aprender cómo aplicar reglas de validación a nuestros formularios en Flask-Admin. Específicamente, configuraremos nuestras entradas para que todos los campos sean obligatorios (requeridos).
1. Configuración de form_args
Para establecer estas reglas, regresamos a la documentación oficial y buscamos la opción de validación. El atributo que necesitamos es form_args. Este diccionario nos permite definir argumentos específicos para cada campo, como validadores y etiquetas personalizadas (labels).
Es fundamental recordar que Flask-Admin construye estos formularios utilizando internamente el módulo WTForms, el cual ya hemos empleado anteriormente en el curso.
from wtforms.validators import InputRequired
class UserModelView(ModelView):
form_args = {
'username': {
'label': 'Usuario',
'validators': [InputRequired()]
}
}2. Implementación de Etiquetas y Validadores
Como viste en el código anterior, dentro de form_args definimos el nombre de la columna y le asignamos sus propiedades:
- label: Permite cambiar el nombre que ve el usuario final (por ejemplo, cambiar "username" por "Usuario").
- validators: Es una lista donde incluimos la regla InputRequired().
Si regresamos a la aplicación y refrescamos, notarás que el campo ahora tiene un asterisco. Si intentas guardar el formulario sin completar el nombre de usuario, el sistema detendrá el proceso e indicará que el campo es requerido.
3. Validando Múltiples Campos
Podemos extender esta lógica a otros campos de manera muy sencilla. Por ejemplo, para que la contraseña también sea obligatoria, simplemente añadimos la columna correspondiente (en nuestro caso, pw_hash) al diccionario:
class UserModelView(ModelView):
form_args = {
'username': {
'label': 'Usuario',
'validators': [InputRequired()]
},
'pw_hash': {
'label': 'Contraseña',
'validators': [InputRequired()]
}
}Ahora, al intentar crear un usuario, Flask-Admin verificará secuencialmente que ambos campos tengan contenido. Si falta alguno, mostrará el mensaje de error y no permitirá guardar el registro hasta que la información esté completa.
El Problema: Contraseñas sin Hash en Flask-Admin
Como puedes ver, aquí nos enfrentamos a un problema importante: si editamos un usuario directamente desde el panel, la contraseña no se está convirtiendo en un hash. Esto dejaría al usuario completamente inutilizado, ya que no podría iniciar sesión; recuerda que nuestra aplicación está construida para validar contraseñas protegidas. Por ejemplo, si observamos al usuario "admin", veremos que su contraseña sí está debidamente hasheada.
Buscando una solución en la documentación
¿Cómo podemos automatizar este proceso en Flask-Admin? Para resolverlo, debemos acudir a la documentación oficial, específicamente a la sección de ModelView, que es donde gestionamos casi todas las operaciones de nuestros modelos.
Tras investigar los métodos disponibles, encontramos dos muy interesantes:
- on_model_change: Se ejecuta antes de que el modelo se guarde en la base de datos.
- after_model_change: Se ejecuta después de que el cambio se haya realizado con éxito.
En nuestro caso, necesitamos on_model_change, ya que debemos convertir la contraseña en un hash justo antes de que se almacene.
Implementación del método on_model_change
El método on_model_change recibe tres parámetros (además de self):
- form: El formulario de WTForms.
- model: La instancia del modelo con los datos actuales.
- is_created: Un booleano que indica si estamos creando un registro nuevo (True) o actualizando uno existente (False).
Aplicando el Hash a la contraseña
Para realizar la operación, simplemente accedemos al atributo de la contraseña a través del objeto model. Aquí tienes el ejemplo de cómo definirlo en tu clase:
def on_model_change(self, form, model, is_created):
# Imprimimos para verificar si es creación o edición
print(f"¿Es creación?: {is_created}")
# Obtenemos la contraseña del modelo y la convertimos en hash
# Usamos el método definido previamente en nuestra clase User
if model.password:
model.password = generate_password_hash(model.password)Pruebas de funcionamiento
- Edición: Si editamos un registro existente y guardamos, veremos que la consola imprime False. Al volver a entrar al registro, el texto plano que pusimos se habrá transformado en una cadena de hash.
- Creación: Al crear un "Usuario 2", la consola imprimirá True. Flask-Admin detectará que es un registro nuevo, ejecutará la misma lógica y guardará la contraseña protegida automáticamente.
Métodos adicionales y casos de uso
Es importante recordar que existe un método para cada operación (crear, actualizar o borrar) y que estos tienen sus versiones "antes" y "después":
- on_model_delete: Útil si necesitas realizar una limpieza antes de borrar un registro.
- after_model_change: Es ideal para acciones que solo deben ocurrir cuando el registro ya está seguro en la base de datos. Por ejemplo, enviar un correo electrónico de bienvenida o una notificación de confirmación al usuario justo después de que su cuenta haya sido creada.
El Problema: El Hash en el Formulario de Edición
Vamos a cubrir un problema común. Si vamos a la sección de Usuarios y seleccionamos "Crear", todo funciona perfectamente: las reglas indican que el nombre de usuario y la contraseña son requeridos. Sin embargo, ¿qué pasa cuando intentamos editar un usuario existente?
Al entrar en modo edición, aparece el Hash de la contraseña en el campo de texto. Esto es un problema por dos razones:
- Estética: No es visualmente agradable mostrar un token tan largo y complejo.
- Lógica de negocio: Generalmente, los administradores no deberían editar la contraseña directamente desde este módulo; el cambio de claves suele gestionarse en un módulo aparte o por el propio usuario.
Queremos explorar dos variantes: que el campo aparezca vacío al editar, o directamente eliminar el campo de contraseña del formulario de edición.
Explorando la Documentación de ModelView
Para solucionar esto, consultamos la documentación oficial de ModelView. Buscaremos el método llamado edit_form. Como su nombre indica, este método nos permite recibir una instancia del formulario o del modelo que se está "pintando" en pantalla y modificar su comportamiento antes de que se muestre al usuario.
Implementación del método edit_form
Vamos a nuestro archivo de modelos y, dentro de la clase que hereda de ModelView, definiremos el método. Es fundamental llamar a la superclase (super()) para que el formulario se cargue con sus valores por defecto y nosotros solo modifiquemos lo que nos interesa.
def edit_form(self, obj=None):
# Llamamos a la superclase para obtener el formulario base
form = super(UserModelView, self).edit_form(obj)
# Aquí realizaremos nuestras modificaciones
return formPersonalizando los Campos de manera Dinámica
Dado que este es un formulario de Flask-WTF, podemos aplicar todas las operaciones que ya conocemos de esa librería.
Variante 1: Limpiar el campo (Ocultar el Hash)
Si solo queremos que el campo de contraseña aparezca vacío en lugar de mostrar el token feo, podemos limpiar su data:
form.password_hash.data = “”Variante 2: Eliminar el campo completamente
En mi caso, prefiero que el campo ni siquiera aparezca durante la edición. Para borrar un campo de manera programática, utilizamos la instrucción del:
del form.password_hashResultado final
Con este cambio, si vamos al formulario de Creación, aparecerán todos los campos (incluida la contraseña). Pero si vamos al de Edición, el campo de contraseña habrá desaparecido. De esta manera, podemos personalizar dinámicamente qué información puede ver o modificar el administrador según la operación que esté realizando.
Personalización visual de Flask-Admin
Ahora vamos a aprender a personalizar Flask-Admin, específicamente el template o la parte visual. Para lograrlo, lo primero que debemos hacer es crear una carpeta llamada admin dentro de nuestro directorio de templates.
Dentro de esta carpeta, indicaremos el nombre del archivo que queremos modificar. Por ejemplo, para la página de inicio, el archivo debe llamarse index.html. Notarás que, al hacer este cambio, si dejas el archivo vacío, la página principal del administrador aparecerá completamente en blanco. Esto sucede porque Flask-Admin ahora está priorizando tu archivo sobre el original. Si escribes un simple "Hola", verás que eso es lo que se renderiza en el navegador.
Extendiendo los Templates Oficiales
Para no perder toda la estructura del panel, debemos basarnos en los archivos originales. Para ello, podemos ir al repositorio oficial en GitHub de Flask-Admin, navegar a la carpeta de templates y buscar las versiones de Bootstrap.
Si abrimos el archivo index.html original, veremos que es una vista con muy poco código que extiende de una vista "madre". Podemos copiar esa estructura en nuestro archivo local:
{% extends 'admin/master.html' %}
{% block body %}
<h1>Hola Mundo</h1>
<p>Estamos personalizando la vista principal.</p>
{% endblock %}Al recargar el servidor, verás que el diseño del administrador regresa, pero ahora incluye tu contenido personalizado dentro del cuerpo de la página.
Uso de Bloques para Personalización Avanzada
La documentación oficial ofrece una tabla con todos los bloques disponibles que podemos sobrescribir, como el título (title), el encabezado (head), el cuerpo (body), entre otros.
1. Cambiar el título
Si quieres modificar el título que aparece en la pestaña del navegador, puedes usar el bloque title:
{% block title %}Súper CRUD{% endblock %}2. Incluir CSS personalizado (FontAwesome)
Para agregar librerías externas como FontAwesome, debemos usar el bloque head_css. Es muy importante que, dentro de este bloque, llames a la función super(). Si no lo haces, sobrescribirás todo el CSS por defecto de Flask-Admin (Bootstrap 3) y la página se verá "rota".
Aquí tienes cómo incluir un icono de usuario usando FontAwesome:
{% block head_css %}
{{ super() }}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
{% endblock %}
{% block body %}
<h1><i class="fa fa-user"></i> Bienvenido al Panel</h1>
{% endblock %}Al llamar a {{ super() }}, le indicamos a Flask que mantenga el CSS original del administrador y simplemente le añada nuestra nueva hoja de estilos. Así de fácil podemos integrar estilos personalizados y librerías externas para mejorar la experiencia visual de nuestro panel.
Personalización de plantillas específicas en Flask-Admin
Anteriormente vimos cómo personalizar el archivo base de Flask-Admin (el template admin/master.html). Ahora bien, ¿qué pasa con el resto de las plantillas? Me refiero al index, las vistas de creación, edición o detalle.
Si revisamos el repositorio oficial en GitHub, encontraremos una carpeta llamada model. Dentro de ella existen archivos clave como:
- list.html: La vista de la tabla o listado.
- create.html: La vista para crear nuevos registros.
- edit.html: La vista de edición.
- details.html: La vista de detalles del registro.
También existe una carpeta llamada modals, que contiene las plantillas para cuando decides trabajar con formularios en ventanas emergentes (modales). Si deseas personalizar estas vistas, debes seguir un proceso similar al que hicimos con la base.
Personalizando la vista de listado (list.html)
Para personalizar la tabla de registros, debemos crear una carpeta llamada model dentro de nuestro directorio templates/admin. Dentro de ella, crearemos el archivo list.html.
Si ejecutas la aplicación con este archivo vacío, la página aparecerá en blanco. Para evitarlo, debemos copiar el código original de Flask-Admin. Verás que es una estructura compleja llena de bloques y condicionales basados en Bootstrap 3. No entraremos en detalle en cada línea, ya que el objetivo es entender cómo extenderlo para realizar pruebas y ajustes visuales.
Caso práctico: Agregar un icono al botón de creación
Queremos agregar un icono de "más" (+) al enlace de creación utilizando FontAwesome.
Identificar el bloque: En el código de list.html, buscamos el enlace de creación. Flask-Admin suele tener condicionales para verificar si la creación es mediante un modal o una página independiente.
Modificar el enlace: Una vez localizado el enlace, podemos intentar agregar el icono:
<a href="{{ url }}" class="btn btn-primary">
<i class="fa fa-plus"></i> Crear nuevo
</a>Configuración en el modelo: Si al recargar no ves los cambios, asegúrate de que en tu UserModelView no tengas activado create_modal = True, ya que eso obligaría a Flask a usar una plantilla distinta.
Integración de estilos externos (FontAwesome)
Si al insertar el icono este no aparece, es porque la plantilla list.html no tiene acceso a los estilos de FontAwesome. Al igual que hicimos con la plantilla base, debemos sobrescribir el bloque de CSS:
{% extends 'admin/model/list.html' %}
{% block head_css %}
{{ super() }}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
{% endblock %}Al llamar a {{ super() }}, mantenemos el diseño original de Bootstrap y añadimos nuestra librería de iconos.
Este mismo mecanismo es el que debes emplear si deseas personalizar las vistas de detalle, edición o cualquier otra, ya sea en el esquema tradicional o mediante modales.
Protegiendo Flask-Admin con Flask-Login
Antes de comenzar, te explicaré el objetivo de esta sección: queremos proteger nuestro panel de Flask-Admin mediante un usuario y contraseña. Para lograrlo, utilizaremos el módulo que ya tenemos instalado, Flask-Login, integrándolo directamente con nuestras vistas de administración.
El proceso es sencillo y consiste en desarrollar un par de métodos dentro de nuestras clases. Primero protegeremos el módulo de gestión de usuarios y luego veremos cómo aplicar este mismo esquema al resto de los CRUDs de forma eficiente.
Métodos clave: is_accessible e inaccessible_callback
Según la documentación oficial, existen dos métodos fundamentales que debemos sobrescribir en nuestro ModelView:
is_accessible: Debe retornar un valor booleano (True o False) que indique si el usuario actual tiene permiso para ver ese módulo.
inaccessible_callback: Es la acción que se ejecuta cuando is_accessible devuelve False. Aquí podemos mostrar un mensaje de error o, lo más común, redireccionar al usuario a la pantalla de login.
Implementación básica
Vamos a nuestro archivo de modelos y ubicamos la clase UserModelView. Implementaremos ambos métodos:
from flask_login import current_user
from flask import redirect, url_for
class UserModelView(ModelView):
def is_accessible(self):
# Verificamos si el usuario está autenticado a través de Flask-Login
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
# Si no está autenticado, lo enviamos al login
return redirect(url_for('login'))Si retornamos un False manualmente en is_accessible, notarás que la opción del menú desaparece. Si intentas forzar la entrada escribiendo la URL manualmente (ej. /admin/user), el sistema ejecutará el inaccessible_callback y te mostrará el mensaje o redirección que hayas configurado.
Optimización mediante Herencia
Si tienes varios CRUDs (Productos, Categorías, etc.), resultaría poco práctico copiar y pegar estos métodos en cada clase. Como Python es un lenguaje orientado a objetos, lo ideal es utilizar la herencia.
Podemos crear una clase base personalizada que contenga la lógica de protección y hacer que el resto de nuestras vistas hereden de ella:
# Clase base protegida
class AdminModelView(ModelView):
def is_accessible(self):
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for('login'))
# Ahora tus vistas específicas heredan de la base protegida
class UserModelView(AdminModelView):
pass
class ProductModelView(AdminModelView):
passNota de organización: Lo ideal es que esta clase AdminModelView esté definida en un archivo superior o compartido para que puedas importarla en todos tus módulos de forma limpia.
Verificación final
- Modo normal: Si estás logueado, verás todos los módulos y podrás gestionar tus registros.
- Modo incógnito: Al no haber sesión activa, las opciones desaparecerán y el acceso directo estará bloqueado.
Opinión del autor: El equivalente al panel de administración de Django. Permite crear un CRUD completo automáticamente.
Uso: Defines una clase
ModelViewvinculada a tus modelos de base de datos y ya tienes una interfaz de gestión lista. Incluye herramientas útiles como date-pickers.
Flask Debug Toolbar
Es momento de conocer otro módulo bastante interesante y especial que nos permitirá obtener información detallada sobre qué está pasando en nuestra aplicación. En otras palabras, nos facilita realizar el debugging de nuestra aplicación de forma rápida y sencilla.
Este módulo se llama Flask-DebugToolbar. Su funcionamiento es simple: añade una barra de herramientas en el navegador que nos brinda información técnica sobre la ejecución de nuestra aplicación, como configuraciones del sistema, consultas de SQLAlchemy, la versión de Flask, entre otros datos.
Instalación e Integración
Para hacernos con este módulo, lo primero es instalarlo mediante el gestor de paquetes. Simplemente ejecutamos el siguiente comando en la terminal:
$ pip install flask-debugtoolbarUna vez instalado, el proceso de integración es muy similar a lo que hemos hecho con otros módulos. Solo debemos importar la extensión y pasarle la instancia de nuestra aplicación (app).
Requisitos previos
Para que el toolbar funcione correctamente, es indispensable cumplir con dos condiciones en nuestro archivo de configuración (config.py):
- Tener activo el modo Debug (DEBUG = True).
- Tener configurada una Secret Key (SECRET_KEY).
Implementación en el Código
En nuestro archivo de inicio (__init__.py), realizamos la importación y la inicialización. Podemos colocarlo, por ejemplo, después de la configuración del caché:
from flask_debugtoolbar import DebugToolbarExtension
# ... después de otras inicializaciones ...
toolbar = DebugToolbarExtension(app)¡Y listo! Con esto ya tenemos nuestra barra de herramientas activa.
Explorando las Funcionalidades del Toolbar
Al levantar la aplicación y refrescar el navegador, aparecerá el toolbar en el lateral. Podemos ocultarlo para que no moleste o desplegarlo para inspeccionar la información generada.
Información disponible:
- Versión de Flask: Nos indica la versión exacta que estamos empleando.
- Tiempo de procesamiento: Cuánto tardó el servidor en procesar la página actual.
- Headers: Muestra los encabezados de la petición (puerto, servidor, tipo de método como GET, etc.).
- Config: Un listado de todas las configuraciones que hemos definido en el sistema.
- SQLAlchemy: Esta es una de las partes más interesantes. Nos indica cuántas consultas SQL se ejecutaron para cargar la página.
Análisis de Consultas (Queries)
Si navegamos a una página de productos, podríamos ver que se ejecutan, por ejemplo, 3 queries. Al inspeccionarlas, el toolbar nos mostrará:
- La consulta para obtener al usuario autenticado.
- La consulta para seleccionar el producto.
- La consulta para cargar las categorías.
Esta información es fundamental para detectar errores comunes, como realizar consultas excesivas o innecesarias, permitiéndonos optimizar nuestra aplicación al máximo.
Con la extensión de Flask Debug Toolbar en Flask, podemos obtener información sobre la aplicación al momento del desarrollo; al momento de renderizar las vistas, veremos un panel como el siguiente:

Figura 14-1: Flask Debug Toolbar
Instalamos la extensión mediante:
$ pip install flask-debugtoolbarhttps://flask-debugtoolbar.readthedocs.io/en/latest/
Registramos la extensión de manera global a nivel de la aplicación:
app\my_app\__init__.py
from flask_debugtoolbar import DebugToolbarExtension
***
# debug
toolbar = DebugToolbarExtension(app)
# blueprintsY habilitamos el modo debug para que Flask Debug Toolbar pueda inspeccionar los queries:
my_app\config.py
class DevConfig(Config):
***
SQLALCHEMY_RECORD_QUERIES = TrueAl entrar en algún módulo de la aplicación, veremos datos como los queries de SQLAlchemy; por ejemplo:
(ms)6.0144
SELECT EXPLAIN
./tasks/operations.py:49 (pagination)
SELECT count(*) AS count_1 FROM (SELECT tasks.id AS tasks_id, tasks.name AS tasks_name, tasks.document_id AS tasks_document_id, tasks.category_id AS tasks_category_id FROM tasks) AS anon_1Con este plugin, puedes conocer más en detalle las ventajas de emplear la caché en los módulos que sea posible emplearlos y ver una disminución importante en el tiempo de procesamiento con respecto a la opción de SQLAlchemy; puedes ver más datos como el tiempo de respuesta del servidor, configuraciones empleadas, rutas, paquetes instalados, etc.
Por lo tanto, tenemos una aplicación más optimizada con el simple hecho de realizar el cambio anterior; este plugin es excelente para conocer toda clase de detalles y detectar posibles cuellos de boleta en la aplicación para corregir los mismos.
Flask CLI
Flask CLI permite crear comandos personalizados para poder realizar cualquier operación; estos comandos se ejecutan en el contexto de la aplicación; para instalar el paquete, tenemos:
$ pip install flask-cliRegistramos a nivel de la app:
my_app\__init__.py
from flask_cli import FlaskCLI
***
FlaskCLI(app)Su uso es muy sencillo y el proceso es realizado en base a funciones:
my_app\__init__.py
import click
@app.cli.command('mycmd')
def mycmd():
click.echo("Test")Por lo tanto, un comando corresponde a una función que puede tener los siguientes decoradores:
- @app.cli.command registra que nuestra función tiene un nuevo comando de línea de comandos; si no se pasa ningún argumento, Click asumirá el nombre de la función.
- @click.argument agrega un argumento de línea de comando; en nuestro caso, para nombre de usuario y contraseña.
Con el funcionamiento aclarado, creamos una nueva función (comando) para crear un usuario:
my_app\__init__.py
def register(app):
@app.cli.command('create-user')
@click.argument('username')
@click.argument('password')
def create_user(username, password):
existing_username = User.query.filter_by(username=username).first()
if existing_username:
click.echo( 'This username has been already taken. Try another one.')
user = User(username, password)
db.session.add(user)
db.session.commit()
click.echo('User {0} Added.'.format(username))
@app.cli.command('mycmd')
def mycmd():
click.echo("Test")Es importante que registres la aplicación mediante la variable de entorno FLASK_APP o que puedas usar comandos como:
$ flask runPara ejecutar el proyecto, de tal forma que de esta manera se tiene acceso al contexto de la aplicación y con esto la posibilidad de ejecutar los comandos personalizados.
Opinión del autor: No es de lo más utilizados, pero seguramente en producción puedes sacarle alguna utilidad con algun comando útil para backup, reporte, etc
Flask-Security-Too: El Estándar Moderno para Autenticación
Si Flask-Login es el motor y Flask-User fue un prototipo educativo, Flask-Security-Too es el chasis blindado de un auto de carreras. Mientras que otras extensiones se limitan a decirte "quién es el usuario", este plugin se encarga de proteger todo el ecosistema de tu aplicación.
Es importante destacar que Flask-Security-Too es la continuación (fork) mantenida y actualizada del proyecto original Flask-Security. Hoy en día, es la recomendación definitiva para proyectos reales que requieren un sistema de permisos profesional, seguro y compatible con las versiones más recientes de Flask.
¿Qué ofrece Flask-Security-Too?
A diferencia de las herramientas más simples, este plugin integra de forma nativa varias extensiones de Flask para ofrecer una solución "todo en uno":
- Autenticación Robusta: Inicio de sesión, cierre de sesión y registro de nuevos usuarios.
- Autorización Completa: Manejo de Roles (ej. admin, editor, user) y permisos específicos por vistas.
- Recuperación de Cuenta: Gestión de olvido de contraseña y confirmación de email integrada.
- Seguridad Avanzada: Protección contra ataques de fuerza bruta, seguimiento de sesiones y soporte para MFA (Autenticación de dos factores).
- API Ready: Soporte nativo para tokens, ideal si estás construyendo una SPA con Vue, Alpine.js o React.
Dependencias Principales
Flask-Security-Too es potente porque se apoya en los estándares del ecosistema Flask:
- Flask-Login: Para el manejo de la sesión del usuario.
- Flask-WTF: Para la validación y seguridad de los formularios (CSRF protection).
- Flask-Mail: Para el envío de correos de confirmación y recuperación.
- Passlib / Cryptography: Para el hashing seguro de contraseñas.
- SQLAlchemy / MongoEngine: Soporte flexible para múltiples motores de base de datos.
Instalación y Configuración Básica
Para comenzar a utilizar el estándar de la industria, instalamos el paquete principal junto con el soporte para base de datos:
$ pip install Flask-Security-Too Flask-SQLAlchemyEjemplo de Implementación Rápida
En Flask-Security-Too definimos modelos para User y Role, conectándolos mediante una tabla intermedia de forma obligatoria para aprovechar el sistema de permisos:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, login_required
app = Flask(__name__)
app.config['SECRET_KEY'] = 'super-secret-key'
app.config['SECURITY_PASSWORD_SALT'] = 'salt-muy-seguro'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///project.db'
db = SQLAlchemy(app)
# Definición de Modelos
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))
# Setup de Flask-Security-Too
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
@app.route('/')
@login_required
def home():
return "Bienvenido a una aplicación segura"
@app.before_first_request
def create_user():
db.create_all()
# Crear Roles
admin_role = user_datastore.find_or_create_role(name='admin', description='Administrador')
user_role = user_datastore.find_or_create_role(name='user', description='Usuario final')
# Crear Usuario con Rol
user_datastore.create_user(email='admin@ejemplo.com', password='password', roles=[admin_role])
db.session.commit()
# --- Rutas Protegidas ---
@app.route('/admin')
@roles_required('admin') # Solo usuarios con rol 'admin'
def admin_panel():
return "Bienvenido al Panel de Administración"
@app.route('/dashboard')
@roles_required('user') # Solo usuarios con rol 'user'
def user_dashboard():
return "Bienvenido al Dashboard de Usuario"
if __name__ == '__main__':
app.run(debug=True)¿Por qué elegirlo sobre Flask-User?
- Mantenimiento Activo: Recibe actualizaciones constantes para mitigar nuevas vulnerabilidades de seguridad.
- Modularidad: Puedes activar o desactivar funciones (ej. solo login, o login + registro + trackable) mediante variables de configuración.
- Compatibilidad: Funciona perfectamente con las versiones modernas de Flask (3.x+) y Python 3.9+.
Si estás planeando llevar tu aplicación a producción, no pierdas tiempo con sistemas antiguos o sin mantenimiento. Flask-Security-Too es la inversión correcta para la escalabilidad de tu proyecto.
Documentación oficial: https://flask-security-too.readthedocs.io/
Flask User (Legacy)
Otro plugin muy interesante es el de Flask User, que a diferencia del de Flask Login que provee un conjunto de funciones y estructura para implementar en login, Flask User provee un sistema de base para realizar la autenticación; estas son las dependencias de Flask User:
Nota importante: Actualmente Flask-User tiene un mantenimiento muy bajo. Aunque lo cubrimos en este curso por su sencillez educativa, para proyectos reales y modernos se recomienda encarecidamente utilizar Flask-Security-Too, que es el estándar actual de la industria por su robustez y actualizaciones frecuentes.
brypt 2.0+
cryptography 1.6+
Flask 0.9+
Flask-Login 0.2+
Flask-WTF 0.9+
passlib 1.6+Como puedes apreciar, una de las dependencias de Flask User es Flask Login; Flask User provee:
- Registro de usuario
- Confirmación de correo electrónico
- Autenticación de usuario
- Cambie el nombre de usuario
- Cambiar la contraseña
- Has olvidado tu contraseña
- Autorización basada en roles
- Internacionalización
El plugin se puede instalar mediante:
$ pip install flask-userLamentablemente al momento en el cual se escribieron estas palabras, el plugin lleva más de 4 años sin actualizar, por lo tanto, solamente es compatible para Flask 0.X lo cual es una versión muy antigua del framework y por lo tanto, este apartado es netamente informativo y no se recomienda emplear en tus proyectos reales.
Si quieres conocer más detalles de implementación, puedes consultar la documentación oficial:
https://flask-user.readthedocs.io/en/latest/basic_app.html
Opinión del autor: Para manejar usuarios, roles y protección de rutas, Flask-Security-Too es la solución definitiva. Ofrece:
- Gestión de roles y permisos.
- Decoradores @roles_accepted o @roles_required.
- Tablas intermedias de base de datos ya estructuradas.
Flask-Restless (Legacy)
En este punto, ya sabemos cómo crear una REST API funcional empleando Flask. Sin embargo, vamos a conocer un módulo que fue muy popular: Flask-Restless.
ADVERTENCIA: Flask-Restless se incluye aquí únicamente como referencia histórica. Actualmente este paquete está obsoleto, no es compatible con versiones modernas de SQLAlchemy y NO se recomienda su uso en aplicaciones nuevas. Para APIs modernas, considera usar Flask-Smorest o Flask-RESTX.
Dicho esto, veamos mínimamente cómo funcionaba para entender el concepto de generación automática de APIs...
Instalación
Para comenzar, lo primero es instalar el paquete. Puedes hacerlo de la manera típica ejecutando el siguiente comando en tu terminal:
$ pip install Flask-RestlessEjemplo con Flask-RESTful (Manual)
Para contrastar, veamos primero cómo lo haríamos de la manera "tradicional" y manual empleando Flask-RESTful, que requiere definir recursos y rutas una por una. Esto te servirá para valorar lo mucho que simplifica el trabajo Flask-Restless más adelante:
from flask import request
from flask_restful import Resource, abort, reqparse, fields, marshal_with
from flask_httpauth import HTTPBasicAuth
from my_app.product.models import Category
from my_app.auth.model.user import User
from my_app import user_manager
from my_app import db
# Definimos los campos que se devolverán en el JSON
resource_fields = {
'id': fields.Integer,
'name': fields.String
}
auth = HTTPBasicAuth()
@auth.verify_password
def verify_password(username, password):
user = User.query.filter_by(username=username).first()
# Verificamos si el usuario existe y si la contraseña coincide
if not user or not user_manager.verify_password(password_hash=user.password, password=password):
return False
return TrueClase Base y Utilidades
Utilizamos una clase Base para reutilizar lógica común, como la verificación de existencia de registros y la serialización manual.
class Base:
def category_to_json(self, category):
"""Convierte un objeto Category a diccionario."""
return {
'id': category.id,
'name': category.name,
}
def abort_if_doesnt_exist(self, id, as_json=True):
"""
Verifica si la categoría existe por ID.
Si no existe, lanza un error 404.
"""
# Se recomienda usar Session.get(Model, id) en versiones recientes de SQLAlchemy
category = db.session.get(Category, id)
if category is None:
abort(404, message="Categoría {} no existe".format(id))
return self.category_to_json(category) if as_json else categoryRecursos: Gestión de Categorías
Aquí definimos los métodos para manejar una categoría individual o el listado completo.
class CategoryRestFul(Resource, Base):
"""Manejo de operaciones sobre una categoría específica."""
@auth.login_required
@marshal_with(resource_fields, envelope="categoria")
def get(self, id):
return self.abort_if_doesnt_exist(id, False)
@marshal_with(resource_fields, envelope="categoria")
def patch(self, id):
parser = reqparse.RequestParser()
parser.add_argument('name', required=True, help="El nombre es obligatorio")
args = parser.parse_args()
c = self.abort_if_doesnt_exist(id, False)
c.name = args['name']
db.session.add(c)
db.session.commit()
return c
def delete(self, id):
c = self.abort_if_doesnt_exist(id, False)
db.session.delete(c)
db.session.commit()
return {'msj': 'ok'}, 200
class CategoryRestFulList(Resource, Base):
"""Manejo de listado y creación de categorías."""
@marshal_with(resource_fields, envelope="categorias")
def get(self):
return Category.query.all()
@marshal_with(resource_fields, envelope="categoria")
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('name', required=True, help="El nombre es obligatorio")
args = parser.parse_args()
c = Category(args['name'])
db.session.add(c)
db.session.commit()
return c, 201 # Retornamos 201 indicando que se ha creadoRecurso de Búsqueda
Este recurso permite filtrar categorías por nombre utilizando el operador LIKE.
class CategoryRestFulListSearch(Resource, Base):
@marshal_with(resource_fields, envelope="categoria")
def get(self):
parser = reqparse.RequestParser()
parser.add_argument('search', required=True, help="Debes especificar un término de búsqueda")
args = parser.parse_args()
# Realizamos la búsqueda por coincidencia parcial
return Category.query.filter(Category.name.like(f"%{args['search']}%")).all()Configuración del API Manager
Si revisamos la sección de Quickstart en la documentación oficial, veremos un ejemplo que, aunque parezca complejo, es muy sencillo. Para integrar este módulo en nuestro proyecto, debemos seguir estos pasos:
- Importar el módulo:
from flask_restless import APIManager
- Instanciar el Manager: Creamos una variable (por ejemplo, restless_manager) llamando a la clase APIManager. A esta clase debemos pasarle la instancia de nuestra aplicación (app) y la base de datos.
- Debes pasarle el objeto flask_sqlalchemy_db vinculado a tu base de datos (por ejemplo, flask_sqlalchemy_db=db), no solo el objeto db directamente, de lo contrario te dará error.
- Crear el endpoint: Usamos el método create_api indicando el modelo con el que queremos trabajar y los métodos permitidos.
restless_manager = APIManager(app, flask_sqlalchemy_db=db)
restless_manager.create_api(Product, methods=['GET', 'POST', 'DELETE'])Acceso a los Recursos y Rutas
Por defecto, Flask-Restless construye la URI basándose en el atributo __tablename__ de tu modelo. Si tu tabla se llama products, la ruta será /api/products.
- Evita conflictos: Si ya tenías rutas creadas manualmente o mediante Blueprints con el mismo nombre, recuerda comentarlas para que no choquen con la nueva API generada.
- Consulta de datos: Al acceder a localhost:5000/api/products, verás los resultados paginados automáticamente. Si quieres un producto específico, simplemente añade el ID al final: /api/products/28.
Personalización de Métodos y Prefijos
Control de Métodos HTTP
Puedes definir exactamente qué operaciones quieres permitir mediante el parámetro methods. Si defines solo GET y POST, y luego intentas hacer una petición de otro tipo (o quitas el GET de la lista), el servidor devolverá un error indicando que el método no está soportado. Esto es ideal para proteger tus datos.
Cambio de la URL (Prefijo)
Si no quieres que tu API comience con /api/ y prefieres algo como /api/v2/, puedes usar la opción url_prefix:
restless_manager.create_api(
Product,
methods=['GET'],
url_prefix='/api/v2'
)Ahora, tus productos estarán disponibles en localhost:5000/api/v2/products.
Operaciones Avanzadas
La documentación oficial detalla todas las capacidades de este módulo:
- GET: Listado general, detalle por ID u operaciones de búsqueda avanzada.
- DELETE: Borrado por ID o mediante términos de búsqueda.
- POST: Creación de nuevos recursos.
- PUT / PATCH: Actualización de instancias específicas o actualizaciones globales de todas las instancias.
Con Flask-Restless, tenemos una herramienta potente para crear una API robusta, paginada y lista para consumir en cuestión de minutos.
Pruebas de Operaciones CRUD con Flask-Restless
Vamos a seguir realizando pruebas con Flask-Restless. En esta ocasión, veremos cómo funcionan las peticiones de tipo POST, PATCH, PUT y DELETE. Para ello, nos apoyaremos en la documentación oficial para personalizar nuestra API y utilizaremos Postman para ejecutar las peticiones.
Durante este proceso cometeremos algunos errores a propósito para que aprendas a identificarlos y sepas cómo solucionarlos cuando aparezcan.
Creación de Recursos (POST)
Para crear un elemento, utilizaremos el método POST. Recuerda que en este caso no se debe pasar ningún identificador en la URL, ya que estamos creando un recurso nuevo.
- Configuración en Postman: Copia la URL de tu aplicación. En mi caso es api/v2/products (recuerda que cambiamos el prefijo anteriormente).
- Envío de datos: Según la documentación, debemos enviar la información en formato JSON. En Postman, ve a la pestaña Body, selecciona raw y asegúrate de marcar JSON en el menú desplegable.
Manejo de errores comunes
Si intentas enviar solo el nombre, por ejemplo {"name": "p30"}, el servidor te devolverá un error indicando que faltan campos obligatorios como el precio (price) o la categoría (category_id).
Nota sobre archivos: Si tu modelo tiene un campo de archivo (file), tendremos un problema al enviarlo vía JSON. Para simplificar esta prueba, comentaremos temporalmente todo lo relacionado con el campo de archivos en nuestro modelo.
Tras ajustar el JSON con los campos requeridos:
{
"name": "p30",
"price": 50.5,
"category_id": 1
}Actualización de Registros (PATCH y PUT)
Para actualizar, el proceso es muy similar al POST, pero con dos diferencias clave:
- URL: Debes incluir el identificador del recurso al final de la ruta (ej. /api/v2/products/32).
- Métodos: Debes habilitar PATCH o PUT en la configuración de tu create_api.
# Ejemplo de configuración habilitando métodos
manager.create_api(Product, methods=['GET', 'POST', 'PATCH', 'PUT', 'DELETE'])- PATCH: Úsalo para modificaciones parciales. Si envías solo el nombre, solo se actualizará el nombre.
- PUT: Se utiliza para actualizaciones completas. Si no habilitas el método específicamente en el código, Postman te devolverá un error de "Método no soportado".
Eliminación de Recursos (DELETE)
Finalmente, para el método DELETE, no necesitamos enviar ningún cuerpo JSON. Simplemente seleccionamos el método en Postman y apuntamos al ID del registro que queremos borrar.
Al ejecutarlo, notarás que no recibes un contenido de respuesta (es una respuesta vacía), pero si realizas un GET global o intentas buscar ese ID específico, verás que el registro ya no existe.
Operaciones de Búsqueda y Filtrado en Flask-Restless
Ahora vamos a ver otro tipo de operaciones fundamentales: las de búsqueda. En Flask-Restless, podemos pasar un parámetro llamado q en la URL para enviar términos que personalicen la respuesta o el tipo de operación que el servidor va a realizar.
Para entender el formato exacto que debemos especificar, podemos consultar la documentación oficial. La estructura básica se basa en pasar un objeto JSON que contenga los criterios de búsqueda.
Uso de Filtros (filters)
Para realizar filtrados, debemos pasar una lista bajo la clave filters. Cada filtro es un diccionario con el siguiente formato:
- name: El campo que queremos comparar (ej. name, id, category_id).
- op: El operador de comparación (ej. ==, >=, <=, !=, like, in, etc.).
- val: El valor con el que queremos comparar.
Ejemplos Prácticos en Postman:
- Filtro por ID: Si queremos traer todos los productos cuyo ID sea mayor o igual a 1, escribiríamos en la URL: ?q={"filters":[{"name":"id","op":">=","val":1}]} Esto nos devolverá todos los registros que cumplan la condición. Si cambiamos el valor a 26, solo obtendremos aquellos con un ID superior.
- Filtro por Categoría: Podemos preguntar por el category_id. Si colocamos 1, nos traerá los productos de esa categoría específica.
- Filtros Múltiples: Como filters es una lista, podemos pasar más de una condición de búsqueda separándolas por comas dentro de los corchetes. Solo se devolverán los resultados que cumplan todas las condiciones enviadas.
Paginación y Ordenamiento
Además de los filtros, existen otros parámetros muy sencillos de emplear que nos ayudan a organizar la data:
- limit: Indica la cantidad máxima de registros que nos va a devolver la API. Por ejemplo, ?q={"limit":1} para obtener solo un resultado.
- offset: Indica cuántos registros debe saltar antes de empezar a devolver datos. En conjunto con limit, es la base para crear una paginación manual.
- order_by: Permite definir el orden de los resultados (ascendente o descendente) según un campo específico.
Conclusión
Como puedes ver, realizar peticiones complejas es bastante simple si sigues el formato JSON requerido. Solo debes escribir en tu navegador o Postman: ?q= seguido del JSON con tus parámetros de limit, offset o filters.
Te invito a que sigas realizando pruebas con otros operadores y campos; este módulo es muy flexible y te permite construir consultas potentes con muy poco esfuerzo.
Opinión del autor: Hay formas modernas de crear una RestAPI
Lo que NO debes usar (Legacy/Obsoleto)
Es común encontrar estos paquetes en tutoriales viejos de Google, pero evítalos en proyectos nuevos:
| Paquete Obsoleto | Alternativa Moderna | Razón |
|---|---|---|
| Flask-User | Flask-Security-Too | Falta de mantenimiento. |
| Flask-Mail | Flask-Mailman | Descontinuado. |
| Flask-Bootstrap 4 | Flask-Bootstrap 5 | Versión antigua. |
| Flask-Restless | Marshmallow / API-Spec | Muy limitado actualmente. |