Introducción a Modelos en Django

Objetivo de aprendizaje: Al finalizar este módulo, entenderás cómo diseñar y trabajar con modelos en Django, establecer relaciones entre ellos y realizar operaciones básicas en la base de datos.

Los modelos son el corazón de Django y definen la estructura de tus datos. Son clases de Python que representan tablas en tu base de datos y proporcionan métodos para interactuar con ellas de forma segura y eficiente.

¿Qué son los modelos en Django?

Un modelo en Django es una clase de Python que hereda de django.db.models.Model. Cada atributo de la clase representa un campo en la tabla de la base de datos.

Django proporciona un sistema de mapeo objeto-relacional (ORM) que te permite interactuar con tu base de datos usando código Python en lugar de SQL directo.

Ventajas de usar modelos

Seguridad

El ORM de Django te protege contra ataques comunes como inyección SQL, ya que escapa automáticamente los parámetros de consulta.

Portabilidad

Puedes cambiar de sistema de base de datos (MySQL, PostgreSQL, SQLite, etc.) sin cambiar tu código Python.

Validación

Los modelos incluyen un sistema de validación que verifica los datos antes de guardarlos en la base de datos.

API Intuitiva

El ORM proporciona una API Python clara y consistente para realizar consultas complejas sin escribir SQL.

Definición de Modelos

Para crear un modelo en Django, defines una clase que hereda de django.db.models.Model en el archivo models.py de tu aplicación.

Estructura básica de un modelo

# En models.py
from django.db import models

class Producto(models.Model):
    nombre = models.CharField(max_length=100)
    descripcion = models.TextField(blank=True)
    precio = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.IntegerField(default=0)
    fecha_creacion = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return self.nombre

Convenciones de nomenclatura

Django sigue ciertas convenciones para traducir tus modelos a tablas en la base de datos:

Elemento Convención Ejemplo
Nombre de tabla [app_name]_[model_name] tienda_producto
Clave primaria Campo id automático id
Relación ForeignKey [campo]_id categoria_id
Nota: Puedes personalizar el nombre de la tabla usando la clase Meta dentro de tu modelo:
class Producto(models.Model):
    # campos del modelo...
    
    class Meta:
        db_table = 'productos'
        verbose_name = 'Producto'
        verbose_name_plural = 'Productos'

Campos y Tipos de Datos

Django proporciona muchos tipos de campos para representar diferentes tipos de datos en tus modelos.

Campos comunes

Tipo de Campo Descripción Ejemplo
CharField Campo de texto de longitud limitada nombre = models.CharField(max_length=100)
TextField Campo de texto largo sin límite descripcion = models.TextField()
IntegerField Número entero edad = models.IntegerField()
FloatField Número decimal de precisión variable altura = models.FloatField()
DecimalField Número decimal de precisión fija precio = models.DecimalField(max_digits=10, decimal_places=2)
BooleanField Valor booleano (verdadero/falso) activo = models.BooleanField(default=True)
DateField Fecha (año, mes, día) fecha_nacimiento = models.DateField()
DateTimeField Fecha y hora creado = models.DateTimeField(auto_now_add=True)
EmailField Campo para direcciones de correo email = models.EmailField()
FileField Campo para archivos archivo = models.FileField(upload_to='archivos/')
ImageField Campo para imágenes imagen = models.ImageField(upload_to='imagenes/')
URLField Campo para URLs sitio_web = models.URLField()

Opciones comunes para campos

Opción Descripción Ejemplo
null Si se permite NULL en la base de datos fecha = models.DateField(null=True)
blank Si se permite valor vacío en formularios descripcion = models.TextField(blank=True)
default Valor predeterminado activo = models.BooleanField(default=True)
choices Lista de opciones para el campo status = models.CharField(choices=[('A', 'Activo'), ('I', 'Inactivo')], max_length=1)
primary_key Define el campo como clave primaria codigo = models.CharField(max_length=10, primary_key=True)
unique El valor debe ser único email = models.EmailField(unique=True)
help_text Texto de ayuda para formularios nombre = models.CharField(help_text="Ingrese el nombre del producto")
verbose_name Nombre amigable para el campo fecha_creacion = models.DateField(verbose_name="Fecha de creación")
Importante: La diferencia entre null=True y blank=True es que null afecta a la base de datos, mientras que blank afecta a la validación en formularios. Para campos de texto, se recomienda usar blank=True, default="" en lugar de null=True.

Relaciones entre Modelos

Django permite establecer relaciones entre modelos para representar estructuras de datos complejas.

One-to-Many (1:N)

Una categoría puede tener muchos productos, pero un producto pertenece a una sola categoría.

class Categoria(models.Model):
    nombre = models.CharField(max_length=50)
    
    def __str__(self):
        return self.nombre

class Producto(models.Model):
    # otros campos...
    categoria = models.ForeignKey(
        Categoria, 
        on_delete=models.CASCADE,
        related_name='productos'
    )
Many-to-Many (N:M)

Un producto puede tener varias etiquetas, y una etiqueta puede aplicarse a varios productos.

class Etiqueta(models.Model):
    nombre = models.CharField(max_length=30)
    
    def __str__(self):
        return self.nombre

class Producto(models.Model):
    # otros campos...
    etiquetas = models.ManyToManyField(
        Etiqueta, 
        related_name='productos'
    )
One-to-One (1:1)

Un producto tiene exactamente un detalle técnico y viceversa.

class DetalleTecnico(models.Model):
    especificaciones = models.TextField()
    garantia = models.IntegerField()
    
    def __str__(self):
        return f"Detalle del producto {self.producto}"

class Producto(models.Model):
    # otros campos...
    detalle = models.OneToOneField(
        DetalleTecnico,
        on_delete=models.CASCADE,
        related_name='producto'
    )

Opciones de on_delete

Al definir una relación ForeignKey o OneToOneField, es obligatorio especificar qué ocurre cuando se elimina el objeto relacionado:

Opción Descripción
models.CASCADE Elimina en cascada los objetos relacionados
models.PROTECT Impide la eliminación si existen objetos relacionados
models.SET_NULL Establece el campo en NULL (requiere null=True)
models.SET_DEFAULT Establece el campo en su valor predeterminado
models.DO_NOTHING No hace nada (puede causar inconsistencias)
Consejo: El parámetro related_name define el nombre con el que se accederá a los objetos relacionados desde el objeto relacionado. Si no se especifica, Django usa [model_name]_set.

Migraciones

Las migraciones son la forma en que Django propaga los cambios en los modelos a la base de datos. Son como un sistema de control de versiones para tu esquema de base de datos.

Flujo de trabajo con migraciones

  1. Crear o modificar modelos en el archivo models.py
  2. Generar las migraciones basadas en los cambios detectados:
    python manage.py makemigrations
  3. Aplicar las migraciones a la base de datos:
    python manage.py migrate

Comandos útiles para migraciones

Comando Descripción
python manage.py showmigrations Muestra el estado de las migraciones (aplicadas o pendientes)
python manage.py sqlmigrate app_name 0001 Muestra el SQL que generará la migración
python manage.py migrate app_name 0002 Migra a una versión específica
python manage.py migrate app_name zero Revierte todas las migraciones de una aplicación
Precaución: Las migraciones pueden ser complejas cuando:
  • Cambias de un tipo de campo a otro incompatible
  • Eliminas campos que tienen datos
  • Tienes dependencias circulares entre modelos

En estos casos, puede ser necesario crear migraciones manuales o utilizar estrategias específicas.

Consultas con ORM

El ORM (Object-Relational Mapping) de Django te permite realizar consultas a la base de datos utilizando código Python en lugar de SQL directo.

Operaciones básicas de CRUD

Create (Crear)
# Método 1: Crear y guardar
producto = Producto()
producto.nombre = "Laptop Dell XPS 13"
producto.precio = 1299.99
producto.stock = 10
producto.save()

# Método 2: Crear con argumentos nombrados
producto = Producto(
    nombre="MacBook Pro", 
    precio=1999.99, 
    stock=5
)
producto.save()

# Método 3: Crear directamente con create()
Producto.objects.create(
    nombre="Surface Pro", 
    precio=1499.99, 
    stock=8
)
Read (Leer)
# Obtener todos los productos
productos = Producto.objects.all()

# Obtener un producto específico por ID
try:
    producto = Producto.objects.get(id=1)
except Producto.DoesNotExist:
    print("El producto no existe")

# Filtrar productos
productos_caros = Producto.objects.filter(precio__gt=1000)
laptops = Producto.objects.filter(nombre__contains="Laptop")

# Ordenar productos
productos_ordenados = Producto.objects.order_by('-precio')  # Descendente
productos_baratos_primero = Producto.objects.order_by('precio')  # Ascendente

# Limitar resultados
cinco_productos = Producto.objects.all()[:5]

# Contar
total_productos = Producto.objects.count()

# Consultas complejas con Q
from django.db.models import Q
productos_query = Producto.objects.filter(
    Q(nombre__contains="Laptop") | Q(nombre__contains="Surface")
)
Update (Actualizar)
# Método 1: Actualizar un objeto individual
producto = Producto.objects.get(id=1)
producto.precio = 1399.99
producto.save()

# Método 2: Actualizar varios objetos a la vez
Producto.objects.filter(stock=0).update(disponible=False)

# Método 3: Actualización F() para operaciones atómicas
from django.db.models import F
Producto.objects.update(stock=F('stock') - 1)  # Reduce el stock de todos los productos
Delete (Eliminar)
# Método 1: Eliminar un objeto individual
producto = Producto.objects.get(id=1)
producto.delete()

# Método 2: Eliminar varios objetos a la vez
Producto.objects.filter(stock=0).delete()

# Método 3: Eliminar todos los objetos
Producto.objects.all().delete()

Consultas avanzadas

# Agregaciones
from django.db.models import Avg, Sum, Min, Max, Count
promedio_precio = Producto.objects.aggregate(Avg('precio'))
total_stock = Producto.objects.aggregate(Sum('stock'))

# Anotaciones
from django.db.models import F, ExpressionWrapper, DecimalField
productos_con_valor = Producto.objects.annotate(
    valor_inventario=ExpressionWrapper(
        F('precio') * F('stock'),
        output_field=DecimalField()
    )
)

# Relaciones
# Productos de una categoría específica
productos_electronica = Categoria.objects.get(nombre='Electrónica').productos.all()

# Categorías con más de 5 productos
categorias_populares = Categoria.objects.annotate(
    num_productos=Count('productos')
).filter(num_productos__gt=5)

# Seleccionar campos específicos
nombres_productos = Producto.objects.values_list('nombre', flat=True)
info_productos = Producto.objects.values('nombre', 'precio')
Consejo: Para depurar consultas SQL generadas por el ORM, puedes usar:
print(Producto.objects.filter(precio__gt=1000).query)

Ejercicio práctico

Tarea: Crear un conjunto de modelos relacionados para un blog y realizar operaciones básicas.
Paso 1: Crear una aplicación blog

Asumiendo que ya tienes un proyecto Django, crea una aplicación para el blog:

python manage.py startapp blog
Paso 2: Definir los modelos del blog

Edita el archivo blog/models.py:

from django.db import models
from django.contrib.auth.models import User

class Categoria(models.Model):
    nombre = models.CharField(max_length=50, unique=True)
    descripcion = models.TextField(blank=True)
    
    def __str__(self):
        return self.nombre
    
    class Meta:
        verbose_name_plural = "Categorías"

class Articulo(models.Model):
    titulo = models.CharField(max_length=200)
    contenido = models.TextField()
    fecha_publicacion = models.DateTimeField(auto_now_add=True)
    fecha_actualizacion = models.DateTimeField(auto_now=True)
    autor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articulos')
    categorias = models.ManyToManyField(Categoria, related_name='articulos')
    publicado = models.BooleanField(default=False)
    
    def __str__(self):
        return self.titulo
    
    class Meta:
        ordering = ['-fecha_publicacion']

class Comentario(models.Model):
    articulo = models.ForeignKey(Articulo, on_delete=models.CASCADE, related_name='comentarios')
    autor = models.ForeignKey(User, on_delete=models.CASCADE, related_name='comentarios')
    contenido = models.TextField()
    fecha = models.DateTimeField(auto_now_add=True)
    aprobado = models.BooleanField(default=False)
    
    def __str__(self):
        return f"Comentario de {self.autor.username} en {self.articulo.titulo}"
    
    class Meta:
        ordering = ['fecha']
Paso 3: Registrar la aplicación en settings.py

Agrega 'blog' a la lista INSTALLED_APPS en settings.py:

INSTALLED_APPS = [
    # Otras aplicaciones...
    'blog',
]
Paso 4: Crear y aplicar las migraciones
python manage.py makemigrations
python manage.py migrate
Paso 5: Usar el shell de Django para probar el ORM

Abre el shell de Django y realiza algunas operaciones:

python manage.py shell
# Importar los modelos
from blog.models import Categoria, Articulo, Comentario
from django.contrib.auth.models import User

# Crear categorías
tecnologia = Categoria.objects.create(nombre="Tecnología", descripcion="Artículos sobre tecnología")
programacion = Categoria.objects.create(nombre="Programación", descripcion="Tutoriales de programación")

# Crear un usuario (o usar uno existente)
usuario, created = User.objects.get_or_create(username="admin", email="admin@example.com")
if created:
    usuario.set_password("password")
    usuario.save()

# Crear artículos
articulo1 = Articulo.objects.create(
    titulo="Introducción a Django",
    contenido="Django es un framework web de alto nivel...",
    autor=usuario,
    publicado=True
)

# Agregar categorías al artículo
articulo1.categorias.add(tecnologia, programacion)

# Crear un comentario
comentario = Comentario.objects.create(
    articulo=articulo1,
    autor=usuario,
    contenido="¡Gran artículo!",
    aprobado=True
)

# Realizar consultas
print("\nArtículos publicados:")
for articulo in Articulo.objects.filter(publicado=True):
    print(f"- {articulo.titulo} por {articulo.autor.username}")
    print(f"  Categorías: {', '.join([c.nombre for c in articulo.categorias.all()])}")
    print(f"  Comentarios: {articulo.comentarios.count()}")

Has creado un conjunto completo de modelos relacionados para un blog y has realizado operaciones básicas con el ORM de Django. Esto te da una base sólida para trabajar con modelos en tus proyectos.

Quiz: Modelos y Base de Datos

Comprueba tus conocimientos sobre este módulo respondiendo las siguientes preguntas:

1. ¿Qué clase base deben heredar todos los modelos en Django?

2. ¿Qué tipo de campo usarías para almacenar una cantidad monetaria con precisión fija?

3. ¿Qué opción de on_delete impide que se elimine un objeto si existen objetos relacionados?

4. ¿Qué comando se utiliza para generar archivos de migración basados en los cambios en los modelos?

5. ¿Qué método se utiliza para obtener un objeto único de la base de datos, generando una excepción si no existe?