Flask
Tutorial Flask: Controlador MVC con métodos GET en Flask
Flask: Aprende la estructura básica de un controlador MVC, gestionando peticiones y vistas, para aplicaciones web eficientes y organizadas.
Aprende Flask GRATIS y certifícateEstructura básica de un controlador MVC
El patrón Modelo-Vista-Controlador (MVC) es fundamental para estructurar aplicaciones web de manera organizada y modular. En Flask, el controlador es responsable de manejar las peticiones HTTP, interactuar con el modelo y devolver respuestas utilizando las vistas.
En Flask, un controlador se implementa mediante funciones asociadas a rutas específicas utilizando el decorador @app.route
o, preferiblemente, mediante Blueprints para una mejor modularidad. A continuación se presenta un ejemplo básico de un controlador MVC utilizando un Blueprint:
from flask import Blueprint, render_template
from models import Producto
productos_bp = Blueprint('productos', __name__)
@productos_bp.route('/productos', methods=['GET'])
def listar_productos():
lista_productos = Producto.query.all()
return render_template('productos/lista.html', productos=lista_productos)
En este ejemplo:
- Creación del Blueprint: Se crea un Blueprint llamado
productos_bp
para agrupar las rutas relacionadas con productos. - Definición de la ruta: Utilizando
@productos_bp.route
, se define la ruta/productos
que responderá a métodos GET. - Interacción con el modelo: Se realiza una consulta al modelo
Producto
para obtener todos los registros. - Renderización de la vista: Se utiliza
render_template
para renderizar la plantillalista.html
, pasando los datos obtenidos.
El controlador sigue estos pasos esenciales:
- Recibe una petición: El usuario accede a la ruta definida, por ejemplo,
/productos
. - Procesa la lógica de negocio: Interactúa con el modelo para obtener o manipular datos necesarios. En este caso, se obtienen todos los productos de la base de datos.
- Prepara la respuesta: Los datos se pasan a una plantilla Jinja para generar la respuesta HTML que verá el usuario.
Es importante mantener la separación de responsabilidades:
- Controlador: Gestiona las peticiones y coordina la interacción entre el modelo y la vista.
- Modelo: Encapsula la lógica de acceso a datos y las reglas de negocio.
- Vista: Define cómo se presentan los datos al usuario.
Otra práctica recomendada es registrar el Blueprint en la aplicación principal para que las rutas sean reconocidas:
from flask import Flask
from controllers.productos import productos_bp
app = Flask(__name__)
app.register_blueprint(productos_bp)
Al estructurar el controlador de esta manera, se logra:
- Modularidad: Facilita el mantenimiento y escalabilidad al tener componentes independientes.
- Reutilización de código: Permite reutilizar controladores y modelos en diferentes partes de la aplicación.
- Claridad en la organización: Mejora la legibilidad del código al seguir un patrón reconocido.
Además, es recomendable manejar posibles errores y excepciones dentro del controlador para garantizar la robustez de la aplicación:
@productos_bp.route('/productos/<int:id>', methods=['GET'])
def detalle_producto(id):
producto = Producto.query.get_or_404(id)
return render_template('productos/detalle.html', producto=producto)
En este caso, si el producto no existe, la función get_or_404
devuelve una respuesta 404, gestionando el error de forma elegante.
Al desarrollar controladores MVC en Flask, se deben considerar las siguientes buenas prácticas:
- Validación de datos: Verificar y sanitizar la información recibida en las peticiones.
- Manejo de sesiones y autenticación: Integrar mecanismos para controlar el acceso a ciertas rutas si es necesario.
- Optimización de consultas: Utilizar técnicas eficientes al interactuar con el modelo para mejorar el rendimiento.
Cargar datos en el modelo para la vista
En el desarrollo de aplicaciones web con Flask, es esencial cargar correctamente los datos desde el modelo para presentarlos en las vistas. Esto implica acceder a la información almacenada en la base de datos mediante consultas eficientes y preparar esos datos para su renderización en las plantillas Jinja.
El primer paso es realizar consultas al modelo utilizando SQLAlchemy, el ORM integrado en Flask. Por ejemplo, para obtener una lista de productos disponibles en la tienda:
from models import Producto
def obtener_productos_disponibles():
productos = Producto.query.filter_by(disponible=True).order_by(Producto.nombre.asc()).all()
return productos
En este caso, se utiliza filter_by
para filtrar solo los productos disponibles y order_by
para ordenarlos alfabéticamente. La función all()
devuelve todos los registros que cumplen con los criterios.
Una vez obtenidos los datos, el controlador debe pasarlos a la vista a través de render_template
. Es común preparar un diccionario de contexto que contenga todas las variables necesarias para la plantilla:
from flask import render_template
@productos_bp.route('/productos', methods=['GET'])
def listar_productos():
productos = obtener_productos_disponibles()
contexto = {
'titulo': 'Listado de Productos',
'productos': productos,
'total_productos': len(productos)
}
return render_template('productos/lista.html', **contexto)
Aquí se está pasando el título de la página, la lista de productos y el total de productos disponibles. Al utilizar **contexto
, se desempaca el diccionario y se envían todas las variables a la plantilla.
Para manejar datos relacionados, es importante aprovechar las relaciones definidas en los modelos. Por ejemplo, si cada producto tiene una categoría asociada:
class Producto(db.Model):
__tablename__ = 'productos'
id = db.Column(db.Integer, primary_key=True)
nombre = db.Column(db.String(100), nullable=False)
disponible = db.Column(db.Boolean, default=True)
categoria_id = db.Column(db.Integer, db.ForeignKey('categorias.id'), nullable=False)
categoria = db.relationship('Categoria', backref='productos')
Al consultar los productos, es posible acceder directamente a la categoría gracias a la relación establecida:
def obtener_productos_con_categorias():
productos = Producto.query.options(db.joinedload(Producto.categoria)).all()
return productos
El uso de joinedload
permite realizar una carga anticipada de las categorías asociadas, evitando el problema conocido como "N+1 queries" y mejorando la eficiencia de las consultas.
En el controlador, se puede ajustar la función para utilizar esta nueva consulta:
@productos_bp.route('/productos', methods=['GET'])
def listar_productos():
productos = obtener_productos_con_categorias()
return render_template('productos/lista.html', productos=productos)
Al preparar los datos, es posible también transformarlos o enriquecerlos antes de enviarlos a la vista. Por ejemplo, calcular un descuento o formatear fechas:
def preparar_productos(productos):
for producto in productos:
producto.precio_con_descuento = producto.precio * 0.9 # Aplicar un 10% de descuento
return productos
Luego, en el controlador:
@productos_bp.route('/productos/ofertas', methods=['GET'])
def listar_ofertas():
productos = obtener_productos_disponibles()
productos = preparar_productos(productos)
return render_template('productos/ofertas.html', productos=productos)
Al preprocesar los datos en el controlador, se facilita el trabajo en las vistas y se mantiene la lógica de negocio fuera de las plantillas.
Es recomendable también manejar la paginación de resultados cuando se trabaja con grandes conjuntos de datos. Flask-SQLAlchemy proporciona métodos para ello:
def obtener_productos_paginados(pagina, elementos_por_pagina):
paginacion = Producto.query.paginate(page=pagina, per_page=elementos_por_pagina)
return paginacion.items, paginacion.pages
En el controlador:
@productos_bp.route('/productos/pagina/<int:pagina>', methods=['GET'])
def listar_productos_paginados(pagina):
productos, total_paginas = obtener_productos_paginados(pagina, 10)
contexto = {
'productos': productos,
'pagina_actual': pagina,
'total_paginas': total_paginas
}
return render_template('productos/lista_paginada.html', **contexto)
Así, se puede ofrecer una navegación paginada en la vista, mejorando la experiencia del usuario.
Al cargar datos en el modelo para la vista, es crucial considerar:
- Eficiencia en las consultas: Optimizar el acceso a la base de datos para reducir tiempos de carga.
- Integridad de los datos: Verificar que los datos obtenidos sean correctos y estén actualizados.
- Seguridad: Protegerse contra inyecciones SQL y manejar adecuadamente los datos sensibles.
Creación y conexión de vistas Jinja desde controladores MVC
En el desarrollo de aplicaciones web con Flask, las vistas Jinja son esenciales para presentar información al usuario de manera dinámica. Jinja es el motor de plantillas por defecto en Flask y permite integrar código Python dentro de HTML para renderizar contenido personalizado.
Las plantillas Jinja se almacenan en el directorio **templates**
de tu proyecto Flask. Para empezar, crea este directorio en la raíz de tu aplicación si aún no existe. Dentro de este directorio, puedes organizar tus plantillas en subcarpetas para mantener una estructura ordenada.
Por ejemplo, para una aplicación de gestión de productos, podrías tener la siguiente estructura:
- app/
- templates/
- productos/
- lista.html
- detalle.html
- base.html
La plantilla base.html
suele actuar como plantilla principal que otras plantillas extienden, utilizando la herencia de plantillas de Jinja. Esta práctica promueve la reutilización de código y un diseño consistente a lo largo de la aplicación.
A continuación, se presenta un ejemplo de una plantilla base.html
:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>{% block titulo %}Mi Aplicación{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/estilos.css') }}">
</head>
<body>
<header>
<h1>{% block encabezado %}Bienvenido a mi aplicación{% endblock %}</h1>
</header>
<main>
{% block contenido %}{% endblock %}
</main>
<footer>
<p>© 2024 Mi Empresa</p>
</footer>
</body>
</html>
En esta plantilla, se definen bloques que pueden ser sobrescritos por las plantillas hijo. Por ejemplo, para crear una vista que liste productos, podríamos crear lista.html
de la siguiente manera:
{% extends "base.html" %}
{% block titulo %}Listado de Productos{% endblock %}
{% block contenido %}
<h2>Productos Disponibles</h2>
<ul>
{% for producto in productos %}
<li>
<a href="{{ url_for('productos.detalle_producto', id=producto.id) }}">{{ producto.nombre }}</a>
</li>
{% else %}
<li>No hay productos disponibles.</li>
{% endfor %}
</ul>
{% endblock %}
En esta vista, estamos extendiendo base.html
y definiendo los bloques titulo
y contenido
. Utilizamos una estructura de control iterativa for
para recorrer la lista de productos y mostrarlos en un listado.
Para conectar esta vista desde el controlador MVC, necesitamos renderizar la plantilla y pasarle los datos necesarios. Suponiendo que tenemos un controlador como el siguiente:
from flask import Blueprint, render_template
from models import Producto
productos_bp = Blueprint('productos', __name__)
@productos_bp.route('/productos', methods=['GET'])
def listar_productos():
productos = Producto.query.all()
return render_template('productos/lista.html', productos=productos)
Aquí, la función listar_productos
utiliza render_template
para renderizar lista.html
y pasa la lista de productos a la plantilla mediante el parámetro productos
. De esta forma, la vista tiene acceso a los datos y puede presentarlos al usuario.
Es importante destacar que las variables de contexto que se pasan a render_template
están disponibles en la plantilla Jinja. Además, podemos utilizar funciones de ayuda como url_for
dentro de las plantillas para generar URLs de manera dinámica, lo que es especialmente útil para enlaces y recursos estáticos.
Si deseamos mostrar el detalle de un producto, podríamos tener en el controlador:
@productos_bp.route('/productos/<int:id>', methods=['GET'])
def detalle_producto(id):
producto = Producto.query.get_or_404(id)
return render_template('productos/detalle.html', producto=producto)
Y una plantilla detalle.html
:
{% extends "base.html" %}
{% block titulo %}Detalle de {{ producto.nombre }}{% endblock %}
{% block contenido %}
<h2>{{ producto.nombre }}</h2>
<p>Precio: {{ producto.precio }} €</p>
<p>Categoría: {{ producto.categoria.nombre }}</p>
<p>{{ producto.descripcion }}</p>
<a href="{{ url_for('productos.listar_productos') }}">Volver al listado</a>
{% endblock %}
En esta plantilla, utilizamos expresiones de variables para mostrar la información específica del producto. También utilizamos url_for
para generar un enlace de retorno al listado de productos.
Las plantillas Jinja permiten también utilizar estructuras de control condicionales. Por ejemplo, si quisiéramos mostrar un mensaje especial si el producto está en oferta:
{% if producto.en_oferta %}
<p><strong>¡Producto en oferta!</strong></p>
{% endif %}
Algunas buenas prácticas al crear y conectar vistas Jinja desde controladores MVC incluyen:
- Mantener el código Python fuera de las plantillas: Las plantillas deben centrarse en la presentación, evitando lógica de negocio.
- Utilizar filtros de Jinja para formatear datos, como fechas o números. Por ejemplo:
{{ producto.precio | round(2) }}
- Proteger contra ataques XSS: Por defecto, Jinja escapa las variables para evitar la inyección de código malicioso.
- Organizar las plantillas: Utilizar subdirectorios y nombres claros facilita el mantenimiento.
Además, es posible pasar funciones y objetos al contexto de las plantillas. Si necesitas utilizar una función personalizada dentro de una plantilla, puedes agregarla al contexto:
def formato_moneda(cantidad):
return f"{cantidad:.2f} €"
@productos_bp.route('/productos', methods=['GET'])
def listar_productos():
productos = Producto.query.all()
return render_template('productos/lista.html', productos=productos, formato_moneda=formato_moneda)
Y en la plantilla:
<li>{{ producto.nombre }} - {{ formato_moneda(producto.precio) }}</li>
Otra característica avanzada es el uso de macros en Jinja para reutilizar fragmentos de código. Puedes definir macros en un archivo y luego importarlas en tus plantillas.
Por ejemplo, en macros.html
:
{% macro mostrar_producto(producto) %}
<li>
<a href="{{ url_for('productos.detalle_producto', id=producto.id) }}">
{{ producto.nombre }}
</a>
- Precio: {{ producto.precio }} €
</li>
{% endmacro %}
Luego, en lista.html
:
{% import "macros.html" as macros %}
<ul>
{% for producto in productos %}
{{ macros.mostrar_producto(producto) }}
{% else %}
<li>No hay productos disponibles.</li>
{% endfor %}
</ul>
Mediante el uso de macros, se promueve la reutilización y se evita la repetición de código.
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
- Comprender el patrón MVC y su aplicación en Flask.
- Crear e implementar Blueprints para modularizar rutas.
- Manejar peticiones y coordinar la lógica de negocio.
- Utilizar render_template para presentar datos en las vistas.
- Asegurar separación de responsabilidades entre modelo, vista y controlador.
- Implementar manejo de errores como el uso de get_or_404.
- Optimizar consultas y gestionar datos de manera eficiente.