Inicio / Python / Flask: Backend Ligero con Python / Rutas y Vistas

Rutas y Vistas

@app.route, métodos HTTP, variables de ruta y url_for.

Principiante


title: "Rutas y Vistas" slug: "flask-rutas-vistas" description: "Domina el sistema de enrutamiento de Flask: decoradores, métodos HTTP, variables en rutas, redirecciones y manejo de errores."

Rutas y Vistas

El sistema de enrutamiento es el corazón de cualquier aplicación web. En Flask, las rutas definen qué URLs están disponibles y las vistas (funciones de vista) determinan qué ocurre cuando un usuario accede a cada URL. En esta lección exploraremos en profundidad cómo Flask gestiona las rutas y cómo puedes crear un enrutamiento sólido y flexible.

El Decorador @app.route()

El decorador @app.route() es la forma principal de asociar una URL con una función en Flask:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def inicio():
    return 'Página principal'

@app.route('/productos')
def productos():
    return 'Lista de productos'

También puedes registrar rutas usando app.add_url_rule(), que es el equivalente programático del decorador:

def contacto():
    return 'Página de contacto'

app.add_url_rule('/contacto', 'contacto', contacto)

Métodos HTTP

Por defecto, una ruta solo responde a solicitudes GET. Para aceptar otros métodos HTTP, usa el parámetro methods:

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        usuario = request.form.get('usuario')
        password = request.form.get('password')
        # Procesar autenticación
        return f'Intentando login como {usuario}'
    # Mostrar formulario para GET
    return '''
    <form method="POST">
        <input name="usuario" placeholder="Usuario">
        <input name="password" type="password" placeholder="Contraseña">
        <button type="submit">Entrar</button>
    </form>
    '''

Los métodos HTTP más comunes son:

Método Propósito
GET Obtener un recurso
POST Crear un recurso
PUT Actualizar un recurso completo
PATCH Actualizar parcialmente
DELETE Eliminar un recurso

A partir de Flask 2.0, también puedes usar decoradores específicos por método:

@app.get('/usuarios')
def listar_usuarios():
    return 'Lista de usuarios'

@app.post('/usuarios')
def crear_usuario():
    return 'Usuario creado'

@app.delete('/usuarios/<int:id>')
def eliminar_usuario(id):
    return f'Usuario {id} eliminado'

Variables en las Rutas

Flask permite capturar segmentos dinámicos de la URL usando variables de ruta con la sintaxis <variable>:

@app.route('/usuario/<nombre>')
def perfil_usuario(nombre):
    return f'Perfil de {nombre}'

# Visitar /usuario/ana → "Perfil de ana"

Convertidores de Tipo

Puedes especificar el tipo de la variable con convertidores:

@app.route('/articulo/<int:id>')
def ver_articulo(id):
    return f'Artículo #{id}'

@app.route('/precio/<float:valor>')
def mostrar_precio(valor):
    return f'Precio: ${valor:.2f}'

@app.route('/ruta/<path:subruta>')
def mostrar_ruta(subruta):
    return f'Subruta: {subruta}'

Los convertidores disponibles son:

Convertidor Descripción
string Texto sin barras (por defecto)
int Enteros positivos
float Números de punto flotante
path Como string, pero acepta barras /
uuid Cadenas UUID

Múltiples Variables

Puedes usar varias variables en una misma ruta:

@app.route('/curso/<categoria>/<int:leccion_id>')
def ver_leccion(categoria, leccion_id):
    return f'Categoría: {categoria}, Lección: {leccion_id}'

# /curso/python/5 → "Categoría: python, Lección: 5"

Trailing Slash (Barra Final)

Flask diferencia entre rutas con y sin barra final:

@app.route('/about/')
def about():
    # Accesible como /about y /about/
    # Flask redirige /about → /about/
    return 'Acerca de'

@app.route('/info')
def info():
    # Solo accesible como /info
    # /info/ devuelve un error 404
    return 'Información'

La convención es usar barra final (/about/) para páginas y sin barra (/api/datos) para endpoints API.

Generación de URLs con url_for()

En lugar de escribir URLs manualmente, usa url_for() para generarlas dinámicamente a partir del nombre de la función de vista:

from flask import url_for

@app.route('/')
def inicio():
    # Genera la URL para la función 'perfil_usuario' con nombre='juan'
    url_perfil = url_for('perfil_usuario', nombre='juan')
    # url_perfil = '/usuario/juan'
    return f'<a href="{url_perfil}">Ver perfil de Juan</a>'

@app.route('/usuario/<nombre>')
def perfil_usuario(nombre):
    url_inicio = url_for('inicio')
    return f'Perfil de {nombre} | <a href="{url_inicio}">Volver</a>'

Ventajas de url_for():

  • Si cambias la URL de una ruta, los enlaces se actualizan automáticamente.
  • Maneja correctamente caracteres especiales.
  • Genera URLs absolutas cuando se necesita: url_for('inicio', _external=True).

Parámetros adicionales que no correspondan a variables de ruta se añaden como query string:

url_for('buscar', q='flask', pagina=2)
# Resultado: /buscar?q=flask&pagina=2

Redirecciones

Para redirigir al usuario a otra página, usa redirect():

from flask import redirect, url_for

@app.route('/vieja-pagina')
def pagina_antigua():
    return redirect(url_for('inicio'))

@app.route('/admin')
def admin():
    usuario_autenticado = False
    if not usuario_autenticado:
        return redirect(url_for('login'))
    return 'Panel de Administración'

Por defecto, redirect() emite un código 302 (redirección temporal). Puedes cambiarlo:

# Redirección permanente (301)
return redirect(url_for('inicio'), code=301)

Manejo de Errores con abort()

Usa abort() para interrumpir una solicitud y devolver un código de error HTTP:

from flask import abort

@app.route('/usuario/<int:id>')
def obtener_usuario(id):
    usuarios = {1: 'Ana', 2: 'Carlos', 3: 'María'}
    if id not in usuarios:
        abort(404)  # No encontrado
    return f'Usuario: {usuarios[id]}'

Personalizar Páginas de Error

Puedes crear páginas de error personalizadas con @app.errorhandler():

@app.errorhandler(404)
def pagina_no_encontrada(error):
    return '<h1>404 - Página no encontrada</h1><p>Lo sentimos, la página que buscas no existe.</p>', 404

@app.errorhandler(500)
def error_interno(error):
    return '<h1>500 - Error interno</h1><p>Algo salió mal en el servidor.</p>', 500

@app.errorhandler(403)
def acceso_prohibido(error):
    return '<h1>403 - Acceso prohibido</h1><p>No tienes permiso para acceder.</p>', 403

Respuestas Personalizadas

Flask permite personalizar las respuestas completamente usando make_response():

from flask import make_response

@app.route('/datos')
def datos():
    respuesta = make_response('Contenido de la respuesta')
    respuesta.headers['X-Custom-Header'] = 'MiValor'
    respuesta.status_code = 200
    respuesta.set_cookie('visita', 'true', max_age=3600)
    return respuesta

También puedes devolver tuplas con el código de estado y los encabezados:

@app.route('/creado')
def recurso_creado():
    return 'Recurso creado exitosamente', 201, {'X-Info': 'nuevo'}

Ejercicio Práctico

Crea una aplicación Flask que simule una tienda en línea con las siguientes rutas:

  1. / — Página principal con enlaces a categorías.
  2. /categoria/<nombre> — Muestra productos de una categoría. Solo acepta: electronica, ropa, hogar. Si la categoría no existe, devuelve un error 404 personalizado.
  3. /producto/<int:id> — Detalle de un producto por ID (usa un diccionario simulado). Si el ID no existe, devuelve 404.
  4. /buscar — Acepta el parámetro q en la query string y muestra qué se buscó.
  5. /viejo-catalogo — Redirige a la página principal.

Requisitos:

  • Usa url_for() para generar todos los enlaces.
  • Personaliza la página de error 404.
  • Acepta GET y POST en la ruta de búsqueda.
from flask import Flask, redirect, url_for, abort, request

app = Flask(__name__)

CATEGORIAS = ['electronica', 'ropa', 'hogar']
PRODUCTOS = {
    1: {'nombre': 'Laptop', 'precio': 999.99, 'categoria': 'electronica'},
    2: {'nombre': 'Camiseta', 'precio': 19.99, 'categoria': 'ropa'},
    3: {'nombre': 'Lámpara', 'precio': 45.00, 'categoria': 'hogar'},
}

@app.route('/')
def inicio():
    enlaces = ''.join(
        f'<li><a href="{url_for("ver_categoria", nombre=c)}">{c.title()}</a></li>'
        for c in CATEGORIAS
    )
    return f'<h1>Tienda</h1><ul>{enlaces}</ul>'

@app.route('/categoria/<nombre>')
def ver_categoria(nombre):
    if nombre not in CATEGORIAS:
        abort(404)
    items = [p for p in PRODUCTOS.values() if p['categoria'] == nombre]
    lista = ''.join(f"<li>{p['nombre']} - ${p['precio']}</li>" for p in items)
    return f'<h1>{nombre.title()}</h1><ul>{lista}</ul>'

@app.route('/producto/<int:id>')
def ver_producto(id):
    if id not in PRODUCTOS:
        abort(404)
    p = PRODUCTOS[id]
    return f"<h1>{p['nombre']}</h1><p>Precio: ${p['precio']}</p>"

@app.route('/buscar', methods=['GET', 'POST'])
def buscar():
    q = request.args.get('q', '') if request.method == 'GET' else request.form.get('q', '')
    return f'<h1>Resultados para: {q}</h1>'

@app.route('/viejo-catalogo')
def viejo_catalogo():
    return redirect(url_for('inicio'), code=301)

@app.errorhandler(404)
def pagina_no_encontrada(e):
    return f'<h1>404</h1><p>No encontrado.</p><a href="{url_for("inicio")}">Volver</a>', 404

if __name__ == '__main__':
    app.run(debug=True)

Resumen

En esta lección dominaste el sistema de enrutamiento de Flask: aprendiste a definir rutas con @app.route(), manejar diferentes métodos HTTP, capturar variables dinámicas con convertidores de tipo, generar URLs seguras con url_for(), redirigir usuarios con redirect() y manejar errores elegantemente con abort() y @app.errorhandler(). Estos conceptos son la base sobre la que construirás aplicaciones web robustas con Flask.

Ejercicio de práctica

Mini sistema de rutas

Implementa un mini enrutador estilo Flask.

class FlaskRouter:
    def __init__(self):
        self.routes = {}

    def route(self, path: str, methods: list[str] = None):
        """Decorador que registra una función para path y methods.
        Por defecto methods=['GET']."""

    def resolve(self, path: str, method: str = 'GET') -> dict | None:
        """Busca ruta. Retorna {'handler': nombre_func, 'method': method} o None."""

    def list_routes(self) -> list[dict]:
        """Retorna lista de {'path': str, 'methods': list[str], 'handler': str}."""