5. Bases de Datos, Modelos, Migraciones y Django ORM 📚
1. Caso de Estudio: Sistema de Biblioteca Digital
Para dominar las bases de datos en Django no basta con un modelo simple de 4 campos. A lo largo de esta lección desarrollaremos el modelo de datos de una Biblioteca Digital Universitaria, un sistema con alta riqueza relacional y reglas de negocio reales:
- PerfilLector: Relación
1:1con el usuario del sistema, carnet UUID, fotos de credencial y límites de préstamos. - Autor: Información bibliográfica, correos de contacto y fechas de nacimiento.
- Categoría: Estructura jerárquica en árbol autorreferencial (ej: Tecnología > Programación > Python).
- Tag (Etiqueta): Clasificación flexible muchos a muchos (
N:M) con colores hexadecimales. - Libro: La entidad central, que incorpora prácticamente todos los tipos de datos que ofrece Django (UUID, Slugs, Decimales, Duraciones, Archivos, JSON de metadatos, Enums, restricciones
CheckConstrainte índices). - SolicitudPrestamo: Modelo intermedio (
Through Model) enriquecido que gestiona el ciclo de vida de los préstamos, fechas límite, estados y multas por mora.
2. Modelos con Todos los Tipos de Datos y Relaciones
A continuación se presenta la definición profesional completa en models.py:
3. Migraciones a Fondo: Ciclo de Vida y Ejercicios
Una migración en Django es la forma en que el framework propaga los cambios que realizas en tus modelos (agregar un campo, borrar un modelo, etc.) hacia el esquema de tu base de datos (PostgreSQL, SQLite, MySQL).
3.1 Los 4 Comandos Esenciales
python manage.py makemigrations: Inspecciona tus modelos y crea un nuevo archivo de migración con las operaciones detectadas.python manage.py migrate: Aplica las migraciones pendientes sobre la base de datos física.python manage.py showmigrations: Muestra una lista visual con[X]para migraciones ya aplicadas y[ ]para pendientes.python manage.py sqlmigrate app 0001: Muestra el código SQL exacto que ejecutará Django antes de correrlo (ideal para auditoría en producción).
3.2 El Gran Reto en Producción: Agregar Campos NOT NULL
Si tu tabla Libro ya tiene 5.000 registros creados y de pronto agregas una clave foránea autor = ForeignKey(Autor, on_delete=CASCADE) sin valor por defecto, la base de datos rechazará la migración.
La estrategia profesional en 3 pasos:
- Fase 1: Agregar el campo como nullable:
autor = ForeignKey(Autor, null=True, blank=True)y corrermakemigrations+migrate. - Fase 2: Ejecutar una Migración de Datos (Data Migration) con
RunPythonpara calcular o asignar los autores existentes. - Fase 3: Cambiar el campo a
null=Falsey generar la última migración. Ahora que ninguna fila tieneNULL, la base de datos aplica la restricción sin errores.
3.3 Migración de Datos con RunPython
Django permite escribir código Python dentro de las migraciones para transformar datos de manera segura:
3.4 Reversión y Rollbacks
Si una migración causa problemas, puedes revertirla retrocediendo al identificador anterior:
🎯 Ejercicios Prácticos de Migraciones
Ejercicio 1: Añadir ISBN único a una tabla con registros
Problema: Tienes 100 libros en la BD sin ISBN y necesitas agregar isbn = CharField(unique=True). Si agregas default="", todas las filas tendrán la misma cadena vacía, violando la unicidad.
Solución: Agrega el campo con null=True, unique=True. Luego crea una migración vacía con RunPython que asigne un código temporal único (ej: TEMP-0001) a cada libro existente. Finalmente, cambia a null=False, unique=True.
Ejercicio 2: Migrar campo de texto separado por comas a ManyToManyField
Problema: El modelo anterior tenía etiquetas_texto = "python, backend, web". Ahora tenemos el modelo Tag.
4. Seeder con Faker: Generación Masiva de Datos
Probar consultas avanzadas de ORM con solo 2 o 3 registros es imposible. La mejor práctica en Django es crear un Management Command que utilice la librería faker:
Para ejecutar el seeder en la terminal con el entorno virtual activo:
5. Django ORM: De Consultas Básicas a Experto
El ORM (Object-Relational Mapping) de Django traduce tus métodos de Python en sentencias SQL optimizadas. A continuación exploramos su potencial desde lo fundamental hasta técnicas de optimización empresarial.
5.1 Nivel 1: Consultas y Lookups Básicos
5.2 Nivel 2: Consultas Relacionales y Proyecciones
5.3 Nivel 3: El Problema N+1 y su Solución
El problema N+1 es el asesino de rendimiento número 1 en aplicaciones web. Ocurre cuando iteras sobre un listado y en cada iteración disparas una nueva consulta SQL para traer la relación:
Django resuelve esto con dos métodos fundamentales:
select_related(*fields): Para relaciones1:1yForeignKey(muchos a uno). Realiza unINNER JOINoLEFT JOINen una sola consulta SQL.prefetch_related(*fields): Para relacionesManyToManyy relaciones inversas. Realiza consultas separadas agrupadas conWHERE id IN (...)y las ensambla en memoria en Python.
5.4 Nivel 4: Agregaciones y Anotaciones
aggregate(): Reduce todo el QuerySet a un único diccionario resumen (ej: promedio o total global).annotate(): Añade una columna calculada a cada fila del QuerySet (equivalente alGROUP BYde SQL).
5.5 Nivel 5: Consultas Avanzadas (Q, F, Case/When, Subquery)
1. Objetos Q (Lógica Booleana Compleja)
Permiten utilizar operadores OR (|), AND (&) y NOT (~):
2. Objetos F (Comparar columnas y actualizar sin Race Conditions)
3. Expresiones Condicionales con Case y When (IF-THEN-ELSE en SQL)
4. Subconsultas Correlacionadas (Subquery y OuterRef)
Permiten traer campos calculados de otra tabla sin hacer múltiples consultas:
💼 Desafíos Prácticos de ORM
Desafío 1: Top 5 Usuarios más Deudores
Consulta: Obtener los 5 usuarios que acumulen la mayor cantidad total de multas impagas en la biblioteca.
Desafío 2: Autores Inactivos / Sin Publicaciones
Consulta: Encontrar autores que no tengan ningún libro registrado.
⚡ Laboratorio Interactivo: Inspector de Django ORM y SQL Nativo
El ORM de Django es perezoso (lazy): solo ejecuta una consulta SQL cuando los datos son efectivamente consumidos (al iterar, indexar o evaluar). Usa este laboratorio para comparar la consulta en Python con la instrucción SQL nativa generada para ambos proyectos.
Django ORM & SQL Inspector
Observa cómo Django traduce Python a SQL nativo optimizado
Libro.objects.filter(disponible_para_prestamo=True).values('titulo', 'precio_reposicion')SELECT "libros_libro"."titulo", "libros_libro"."precio_reposicion" FROM "libros_libro" WHERE "libros_libro"."disponible_para_prestamo" = TRUE ORDER BY "libros_libro"."fecha_publicacion" DESC;
| titulo | precio_reposicion | disp |
|---|---|---|
| Clean Code | $42.00 | ✔ Sí |
| Python Fluente 2da Ed. | $65.00 | ✔ Sí |
| Django 5 Profesional | $55.00 | ✔ Sí |
5. Ejercicios con Segundo Proyecto: Modelos y ORM de E-Commerce (`tienda`) 🛒
Aplica los conceptos de modelado, restricciones a nivel de base de datos y consultas agregadas sobre el segundo proyecto: Tienda E-Commerce (C:\proyectos\django\tienda).
Reto 5.1: Modelo Producto con Restricciones CheckConstraint y Slugify
Modela en catalogo/models.py la clase Producto con clave foránea a CategoriaProducto (protegiendo el borrado con PROTECT), campos precio y costo, y dos restricciones de integridad en Meta.constraints: 1. El precio debe ser mayor o igual al costo (precio__gte=F('costo')). 2. El stock no puede ser negativo (stock__gte=0).
Ver Solución Explicada
Reto 5.2: Migración de Datos (RunPython) para Sembrar Categorías
Crea una migración de datos vacía (python manage.py makemigrations catalogo --empty --name sembrar_categorias) que inserte de forma idempotente las categorías esenciales: 'Laptops', 'Smartphones', 'Monitores' y 'Periféricos'.
Ver Solución Explicada
Reto 5.3: Consultas de Negocio Avanzadas con el ORM de la Tienda
Escribe las consultas optimizadas en Python para responder a estas 3 necesidades de la dirección comercial:
- A. Ticket Promedio: Calcular el promedio de compra de todos los pedidos en estado 'PAGADO'.
- B. Clientes VIP: Clientes cuyos pedidos acumulados superen los $1,000 USD.
- C. Margen Unitario: Anotar cada producto con su margen de ganancia (
precio - costo) usandoF().
Ver Solución Explicada
Ponte a prueba
Comprueba que entiendes el modelado de datos, las relaciones y las optimizaciones del ORM de Django.


