Fundamentos del ORM en Django
El patrón ORM (Object-Relational Mapping) establece una capa de abstración entre los modelos de datos y el sistema de gestión de bases de datos. Esta arquitectura permite que la definición de entidades sea independiente del motor de almacenamiento, facilitando la migración entre diferentes sistemas de bases de datos mediante ajustes configurables.
Operaciones Fundamentales del Administrador de Consultas
Django proporciona un conjunto robusto de métodos para la recuperación y manipulación de registros:
Conjuntos de Resultados
all()— Recupera todos los registros de una entidadfilter()— Filtra registros que cumplen las condiciones especificadasexclude()— Omite registros que coinciden con los criteriosorder_by()— Ordena los resultados por los campos indicadosreverse()— Invierte el ordenamiento del conjuntodistinct()— Elimina duplicados del resultadovalues()— Retorna diccionarios en lugar de instanciasvalues_list()— Devuelve tuplas con los valores solicitados
Recuperación de Instancias Individuales
get()— Obtiene un único registro que cumpla la condiciónfirst()— Primer elemento del conjuntolast()— Último elemento del conjunto
Agregaciones y Verificación
count()— Cuenta registros (retorna entero)exists()— Verifica existencia (retorna booleano)
Consultas con Lookups de Campo
El sistema de lookups de Django utiliza el patrón campo__modificador para realizar comparaciones avanzadas:
from miapp.models import Libro
# Comparaciones de rango
Libro.objects.filter(costo__in=[150, 250, 350])
Libro.objects.filter(costo__gt=150) # mayor que
Libro.objects.filter(costo__lt=150) # menor que
Libro.objects.filter(costo__range=(150, 300)) # entre valores
# Búsquedas de texto
Libro.objects.filter(nombre__contains="django") # sensible a mayúsculas
Libro.objects.filter(nombre__icontains="django") # insensible a mayúscula
Libro.objects.filter(nombre__startswith="Django") # comienza con
# Filtros temporales
Libro.objects.filter(fecha_lanzamiento__year=2023)
Libro.objects.filter(fecha_lanzamiento__year__gt=2023)
Resumen de Modificadores de Campo
| Modificador | Función |
|---|---|
__gt, __lt |
Mayor/menor que |
__gte, __lte |
Mayor/menor o igual que |
__in |
Pertenencia a lista |
__range |
Dentro de intervalo |
__contains, __icontains |
Contenido de cadena |
__startswith, __istartswith |
Prefijo |
__endswith, __iendswith |
Sufijo |
__isnull |
Verificación de nulo |
__year, __month, __day |
Extraccción de componentes de fecha |
Definición de Relaciones entre Entidades
Relaciones de Clave Foránea
class Editorial(models.Model):
identificador = models.AutoField(primary_key=True)
razon_social = models.CharField(max_length=128)
class Ejemplar(models.Model):
titulo = models.CharField(max_length=256)
editorial = models.ForeignKey(
Editorial,
to_field="identificador",
related_name="publicaciones",
on_delete=models.CASCADE
)
Relaciones Muchos a Muchos
class Escritor(models.Model):
nombre_completo = models.CharField(max_length=128)
class Obra(models.Model):
denominacion = models.CharField(max_length=256)
escritores = models.ManyToManyField(Escritor)
# El campo escritores no existe como columna real;
# Django genera una tabla intermedia para gestionar la relación
Para manipular las asociaciones, Django expone un gestor de relaciones:
autor = Escritor.objects.get(id=1)
autor.obra_set.all() # consulta inversa
autor.obra_set.add(obra_nueva) # añadir relación
autor.obra_set.remove(obra_vieja) # eliminar relación
autor.obra_set.clear() # eliminar todas las relaciones
autor.obra_set.set([obra1, obra2]) # reemplazar conjunto
autor.obra_set.create(denominacion="Nuevo Título") # crear y vincular
Estrategias de Navegación entre Tablas
Consulta por Objetos (Subconsultas SQL)
# Dirección directa: usar el nombre del campo ForeignKey
ejemplar = Ejemplar.objects.get(id=5)
ejemplar.editorial.razon_social
# Dirección inversa: usar related_name o nombre_tabla_set
editorial = Editorial.objects.get(id=1)
editorial.publicaciones.all() # usando related_name
# editorial.ejemplar_set.all() # usando el valor por defecto
Consulta por Lookups Cruzados (JOIN en SQL)
# Dirección directa
Ejemplar.objects.filter(denominacion="Python Avanzado").values("editorial__razon_social")
# Dirección inversa
Escritor.objects.filter(nombre_completo__startswith="G").values("obra__denominacion")
Funciones de Agregación y Agrupamiento
Agregaciones Globales
from django.db.models import Avg, Max, Min, Sum, Count
# Promedio de precios
Ejemplar.objects.aggregate(promedio=Avg("costo"))
# Múltiples agregaciones simultáneas
Ejemplar.objects.aggregate(
mayor=Max("costo"),
menor=Min("costo"),
total=Sum("unidades")
)
Agrupamiento con Anotaciones
<p>La cláusula <code>annotate()</code> permite agrupar registros y calcular métricas por grupo, combinando internamente las tablas relacionadas mediante JOIN.</p>
<code># Promedio salarial por departamento
Empleado.objects.values("departamento").annotate(salario_medio=Avg("salario"))
# Autores con precio máximo de sus obras
Escritor.objects.annotate(
precio_maximo=Max("obra__costo")
).values("nombre_completo", "precio_maximo")
# Autores nacidos después de 1996 con precio máximo de publicaciones
Escritor.objects.filter(
fecha_nacimiento__year__gt=1996
).annotate(
precio_maximo=Max("obra__costo")
).values("nombre_completo", "precio_maximo")
# Libros con más de un autor
Obra.objects.annotate(
total_escritores=Count("escritores")
).filter(total_escritores__gt=1).values("denominacion", "total_escritores")
</code>
<h2>Comparaciones entre Campos con F()</h2>
<p>La clase <code>F</code> permite referenciar el valor actual de un campo en la base de datos sin cargarlo en memoria:</p>
<code>from django.db.models import F
# Filtrar donde comentarios < favoritos
Ejemplar.objects.filter(total_comentarios__lt=F('total_favoritos'))
# Actualización atómica: incrementar precio un 15%
Ejemplar.objects.all().update(costo=F("costo") * 1.15)
</code>
<h2>Combinación Condicional de Criterios con Q()</h2>
<p>Los objetos <code>Q</code> habilitan la construcción de consultas con operadores lógicos complejos:</p>
<code>from django.db.models import Q
# Condición OR
Obra.objects.filter(
Q(escritores__nombre_completo="García Márquez") |
Q(escritores__nombre_completo="Borges")
)
# Negación con ~
Obra.objects.filter(
Q(escritores__nombre_completo="García Márquez") &
~Q(fecha_publicacion__year=2017)
).values_list("denominacion", flat=True)
# Combinación de Q y parámetros nombrados
Obra.objects.filter(
Q(fecha_publicacion__year=2023) | Q(fecha_publicacion__year=2024),
denominacion__icontains="python"
)
</code>
<h2>Tablas Intermedias Personalizadas</h2>
<p>Cuando la relación muchos a muchos requiere atributos adicionales, se define un modelo explícito:</p>
<code>class Estudiante(models.Model):
nombre = models.CharField(max_length=64)
asignaturas = models.ManyToManyField(
"Asignatura",
through="Matricula",
related_name="inscritos"
)
class Asignatura(models.Model):
titulo = models.CharField(max_length=128)
class Matricula(models.Model):
estudiante = models.ForeignKey(Estudiante, on_delete=models.CASCADE)
asignatura = models.ForeignKey(Asignatura, on_delete=models.CASCADE)
calificacion = models.DecimalField(max_digits=4, decimal_places=2)
fecha_inscripcion = models.DateField(auto_now_add=True)
</code>
<h2>Configuración del Entorno Django</h2>
<h3>Paso 1: Definición de Modelos</h3>
<code>from django.db import models
class Producto(models.Model):
codigo = models.AutoField(primary_key=True)
descripcion = models.CharField(max_length=64, verbose_name="Nombre del producto")
activo = models.BooleanField(default=True)
fecha_registro = models.DateField(auto_now_add=True)
valor = models.DecimalField(max_digits=10, decimal_places=2)
proveedor = models.CharField(max_length=64)
class Meta:
app_label = "inventario"
db_table = "productos_catalogo"
unique_together = ["descripcion", "proveedor"]
verbose_name = "Producto comercial"
ordering = ["valor"]
def __str__(self):
return self.descripcion
</code>
<h3>Paso 2: Configuración de Conexiones</h3>
<code>DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'almacen_db',
'USER': 'admin_db',
'PASSWORD': 'clave_segura',
'HOST': '192.168.1.50',
'PORT': '3306',
},
'reportes': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'analytics',
'USER': 'lector',
'PASSWORD': 'lectura',
'HOST': '192.168.1.51',
'PORT': '5432',
}
}
</code>
<h3>Paso 3: Adaptador para Python 3</h3>
<code># archivo __init__.py del proyecto
import pymysql
pymysql.install_as_MySQLdb()
</code>
<h3>Paso 4: Generación de Esquema</h3>
<code>python manage.py makemigrations
python manage.py migrate
</code>
<h3>Paso 5: Registro de Consultas SQL</h3>
<code>LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'consola': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['consola'],
'level': 'DEBUG',
'propagate': True,
},
}
}
</code>
<h2>Inserción de Registros</h2>
<code># Método 1: Creación directa
articulo = Producto.objects.create(
descripcion="Teclado mecánico",
activo=True,
valor=Decimal("899.99"),
proveedor="Periféricos SA"
)
# Método 2: Instanciación y persistencia
articulo = Producto(
descripcion="Mouse inalámbrico",
activo=True,
valor=Decimal("459.50"),
proveedor="Periféricos SA"
)
articulo.save()
</code>
<h2>Tipos de Campos y Parámetros</h2>
| Campo | Uso | Parámetros Destacados |
|---|---|---|
CharField |
Cadenas cortas | max_length (obligatorio) |
TextField |
Texto largo | — |
IntegerField |
Enteros | — |
FloatField |
Decimales | — |
DecimalField |
Moneda/precisión | max_digits, decimal_places |
AutoField |
Clave primaria secuencial | primary_key=True |
BooleanField |
Valores verdadero/falso | — |
DateField |
Fechas | auto_now, auto_now_add |
DateTimeField |
Fecha y hora | auto_now, auto_now_add |
EmailField |
Correos electrónicos | Validación automática |
ImageField |
Imágenes | upload_to, height_field, width_field |
Parámetros Universales de Campos
null=True: Permite valores NULL en la base de datosblank=True: Permite valores vacíos en formularios de administracióndefault=valor: Establece valor por defectoprimary_key=True: Designa como clave primariaunique=True: Restricción de unicidadchoices=lista_tuplas: Opciones para desplegables