IF0100 - Programacion OO II

Unidad 4: Manejo de Persistencia

Clase 3: SQLAlchemy - Relaciones

Jueves, 07 de mayo de 2026 Semana 14 - Jueves (60 minutos) 30 min Teoría 30 min Práctica

E5: Proyecto SQLAlchemy - Hoy (Jueves 07/05/2026)

Objetivos de Aprendizaje

Al finalizar esta clase, seras capaz de:

  • Crear relaciones Uno a Muchos (One-to-Many)
  • Crear relaciones Muchos a Uno (Many-to-One)
  • Usar relationship() y ForeignKey
  • Navegar relaciones como atributos
  • Configurar back_populates

Relacion Uno a Muchos (20 min)

Un Usuario tiene muchos Proyectos.

# models/usuario.py
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from typing import List

class Usuario(Base):
    __tablename__ = "usuarios"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    username: Mapped[str] = mapped_column(String(50), unique=True)
    
    # RELACION: Un usuario tiene muchos proyectos
    proyectos: Mapped[List["Proyecto"]] = relationship(
        back_populates="usuario",
        cascade="all, delete-orphan"
    )
cascade="all, delete-orphan": Al eliminar un usuario, se eliminan sus proyectos.

Relacion Muchos a Uno (15 min)

Muchos Proyectos pertenecen a un Usuario.

# models/proyecto.py
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

class Proyecto(Base):
    __tablename__ = "proyectos"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    nombre: Mapped[str] = mapped_column(String(100))
    
    # CLAVE FORANEA: Referencia al usuario
    usuario_id: Mapped[int] = mapped_column(ForeignKey("usuarios.id"))
    
    # RELACION: Un proyecto pertenece a un usuario
    usuario: Mapped["Usuario"] = relationship(back_populates="proyectos")

Relacion Bidireccional (20 min)

Proyecto tiene muchas Tareas. Tarea pertenece a un Proyecto.

# models/proyecto.py (actualizado)
from typing import List

class Proyecto(Base):
    __tablename__ = "proyectos"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    nombre: Mapped[str] = mapped_column(String(100))
    usuario_id: Mapped[int] = mapped_column(ForeignKey("usuarios.id"))
    
    # Relaciones
    usuario: Mapped["Usuario"] = relationship(back_populates="proyectos")
    tareas: Mapped[List["Tarea"]] = relationship(back_populates="proyecto")


# models/tarea.py
class Tarea(Base):
    __tablename__ = "tareas"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    titulo: Mapped[str] = mapped_column(String(200))
    proyecto_id: Mapped[int] = mapped_column(ForeignKey("proyectos.id"))
    
    # Relacion
    proyecto: Mapped["Proyecto"] = relationship(back_populates="tareas")

Navegar Relaciones

from sqlalchemy.orm import Session
from database import SessionLocal

db: Session = SessionLocal()

# Obtener usuario con sus proyectos
usuario = db.query(Usuario).filter_by(username="juan").first()

# Acceder a proyectos como atributo
for proyecto in usuario.proyectos:
    print(f"Proyecto: {proyecto.nombre}")
    
    # Acceder a tareas del proyecto
    for tarea in proyecto.tareas:
        print(f"  - Tarea: {tarea.titulo}")

# Desde una tarea, acceder al proyecto y usuario
tarea = db.query(Tarea).first()
print(f"Tarea: {tarea.titulo}")
print(f"Proyecto: {tarea.proyecto.nombre}")
print(f"Usuario: {tarea.proyecto.usuario.username}")

Ejercicio: Modelo Comentario (20 min)

Crear un modelo Comentario que pertenezca a una Tarea:

# models/comentario.py
from sqlalchemy import String, ForeignKey, DateTime, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime

class Comentario(Base):
    __tablename__ = "comentarios"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    contenido: Mapped[str] = mapped_column(Text, nullable=False)
    tarea_id: Mapped[int] = mapped_column(ForeignKey("tareas.id"))
    creado_en: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    
    # Relacion: Un comentario pertenece a una tarea
    tarea: Mapped["Tarea"] = relationship(back_populates="comentarios")


# Actualizar Tarea
class Tarea(Base):
    __tablename__ = "tareas"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    titulo: Mapped[str] = mapped_column(String(200))
    proyecto_id: Mapped[int] = mapped_column(ForeignKey("proyectos.id"))
    
    # Relaciones
    proyecto: Mapped["Proyecto"] = relationship(back_populates="tareas")
    comentarios: Mapped[List["Comentario"]] = relationship(
        back_populates="tarea",
        cascade="all, delete-orphan"
    )
E4 Laboratorio: Implementa CRUD completo con las relaciones Usuario-Proyecto-Tarea-Comentario.

Resumen

  • ForeignKey: Define la columna que referencia otra tabla
  • relationship(): Crea la relacion navegable
  • back_populates: Conecta relaciones bidireccionales
  • cascade: Define comportamiento al eliminar
  • List["Modelo"]: Indica relacion uno-a-muchos
← Anterior: SQLAlchemy Modelos
Clase 18 de 25
Siguiente: CRUD SQLAlchemy →