Modelos y ORM: De 0 a Experto 🚀
Esta guía te llevará desde la definición básica de tablas hasta consultas complejas y optimizadas con el ORM de Django.
1. Anatomía de un Modelo Experto
Un modelo robusto utiliza los tipos de datos correctos para garantizar la integridad y el rendimiento.
# src/conceptos_basicos/models.py
from django.db import models
class Curso(models.Model):
# Enum para opciones limitadas (Django 3.0+)
NIVEL_CHOICES = [
('BASICO', 'Básico'),
('INTERMEDIO', 'Intermedio'),
('AVANZADO', 'Avanzado'),
]
titulo = models.CharField(max_length=200, verbose_name="Título")
# DecimalField es CRÍTICO para dinero. Float pierde precisión.
precio = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
# BooleanField con valor por defecto
publicado = models.BooleanField(default=False)
# Choices limita la entrada a valores predefinidos en la BD y Admin
nivel = models.CharField(max_length=10, choices=NIVEL_CHOICES, default='BASICO')
2. Relaciones entre Tablas
El poder de las bases de datos relacionales reside en... las relaciones. Django ofrece tres tipos principales:
A. ForeignKey (Uno a Muchos)
Un Curso pertenece a una Categoria. Una Categoria tiene muchos Cursos.
class Categoria(models.Model):
nombre = models.CharField(max_length=50)
class Curso(models.Model):
# related_name='cursos' permite acceder desde categoria: mi_categoria.cursos.all()
categoria = models.ForeignKey(Categoria, on_delete=models.SET_NULL, null=True, related_name="cursos")
B. ManyToManyField (Muchos a Muchos)
Un Estudiante puede tomar muchos Cursos. Un Curso tiene muchos Estudiantes.
class Estudiante(models.Model):
# Django crea una tabla intermedia automáticamente
cursos = models.ManyToManyField(Curso, related_name="estudiantes")
C. OneToOneField (Uno a Uno)
Extender información de un usuario o entidad.
class PerfilEstudiante(models.Model):
estudiante = models.OneToOneField(Estudiante, on_delete=models.CASCADE)
bio = models.TextField()
3. Consultas con el ORM (El Poder Real)
Aquí es donde dejamos de ser principiantes. Ejemplos reales validados en nuestros tests (tests/test_orm_avanzado.py).
Filtrado Básico y Lookups
Los "lookups" se añaden al nombre del campo con doble guion bajo (__).
# Obtener cursos que contengan "django" (ignora mayúsculas/minúsculas)
Curso.objects.filter(titulo__icontains="django")
# Cursos GRATUITOS (precio menor o igual a 0)
Curso.objects.filter(precio__lte=0)
# Cursos publicados en el año 2024
Curso.objects.filter(fecha_inicio__year=2024)
Consultas Avanzadas con Q Objects
Cuando necesitas lógica OR (|) o negaciones complejas (~), usas objetos Q.
from django.db.models import Q
# Quiero cursos que sean GRATIS O que sean nivel AVANZADO
Curso.objects.filter(Q(precio=0) | Q(nivel="AVANZADO"))
Relaciones Inversas (Spanning)
Puedes filtrar basándote en campos de modelos relacionados.
# Todos los cursos de la categoría "Desarrollo Web"
Curso.objects.filter(categoria__nombre="Desarrollo Web")
# Todos los estudiantes inscritos en cursos "Avanzados"
Estudiante.objects.filter(cursos__nivel="AVANZADO").distinct()
Agregación y Anotación
Operaciones matemáticas sobre grupos de datos.
from django.db.models import Avg, Count
# ¿Cuál es el precio promedio de mis cursos?
Curso.objects.aggregate(Avg('precio'))
# Retorna: {'precio__avg': 49.99}
# Añadir un campo 'num_estudiantes' a cada curso en la consulta
cursos = Curso.objects.annotate(num_estudiantes=Count('estudiantes'))
for c in cursos:
print(f"{c.titulo}: {c.num_estudiantes} alumnos")
Expresiones F (Atomic Updates)
Actualizar un campo basándose en su valor actual, directamente en la base de datos (sin race conditions).
from django.db.models import F
# Incrementa el precio de cada curso en un 10%
Curso.objects.update(precio=F('precio') * 1.1)