5. Modelos y Tipos de Datos: De Python a SQL 🧱
1. ¿Qué es un Modelo en Django y cómo se conecta a SQL?
En el desarrollo web moderno casi nunca escribes sentencias SQL a mano como CREATE TABLE o INSERT INTO para tus operaciones cotidianas. En su lugar, Django utiliza un ORM (Object-Relational Mapper), una capa que traduce clases y atributos de Python en tablas y columnas de base de datos relacionales (PostgreSQL, SQLite, MySQL o MariaDB).
| Concepto en Python (Django) | Equivalente en Base de Datos (SQL) | Ejemplo Práctico |
|---|---|---|
| class Articulo(models.Model) | Tabla en la base de datos | CREATE TABLE "blog_articulo" (...) |
| titulo = models.CharField(...) | Columna con tipo y restricciones | "titulo" varchar(150) NOT NULL |
| art = Articulo(titulo="Django") | Fila / Registro (Row) | INSERT INTO "blog_articulo" VALUES (...) |
| id (automático) | Clave Primaria (Primary Key) | "id" bigint NOT NULL PRIMARY KEY |
El Puente de las Migraciones: `makemigrations` y `sqlmigrate`
Cuando defines un modelo en models.py, la base de datos aún no sabe nada al respecto. El flujo para materializar tus cambios consta de 3 comandos:
python manage.py makemigrations: Compara tus modelos actuales con el historial previo y genera un archivo de migración en Python (ej:0001_initial.py).python manage.py sqlmigrate <app> <numero>:El comando más educativo de Django. Te muestra el código SQL puro y exacto que Django va a enviar a tu motor de base de datos.python manage.py migrate: Ejecuta las sentencias SQL en la base de datos física para crear o alterar las tablas.
2. El Campo Fundamental: `models.CharField` a Fondo
models.CharField es el campo más utilizado en cualquier proyecto de backend. Representa cadenas de texto de longitud limitada (nombres, títulos, códigos, slugs, emails, etc.).
2.1 El Parámetro Obligatorio: `max_length`
A diferencia de otros campos, CharFieldexige obligatoriamente definir el argumento max_length. Si lo omites, Django arrojará un error system check antes de arrancar.
nombre = models.CharField(max_length=100)
"nombre" varchar(100) NOT NULL
¿Por qué se exige? Los motores relacionales como PostgreSQL y MySQL optimizan el almacenamiento y los índices en disco reservando espacio en base a la longitud máxima VARCHAR(n).
2.2 Opciones Clave de `CharField` y su Impacto en la Base de Datos
A. `null=False` (Por Defecto) vs `null=True`
Controla si la base de datos permite guardar valores NULL en esa columna:
null=False(comportamiento predeterminado): Genera la restricción SQLNOT NULL. Cualquier intento de insertar una fila sin valor fallará a nivel de motor.null=True: Permite que la columna contengaNULLen la base de datos.
B. `blank=False` (Por Defecto) vs `blank=True`
Diferencia crucial:blank no tiene ningún efecto en la base de datos SQL. Es una validación exclusiva a nivel de formularios y del panel de administración de Django:
blank=False: Django exigirá que el usuario rellene el campo en el formulario HTML antes de enviarlo.blank=True: Django permite que el campo quede en blanco (se guardará una cadena vacía""en la BD).
⚠️ La Regla de Oro en Django: Evita `null=True` en campos de texto
La documentación oficial de Django recomienda tajantemente: no utilices null=True en campos basados en texto (CharField y TextField).
Si lo haces, tu base de datos tendrá dos formas distintas de representar la ausencia de datos: el valor NULL y la cadena vacía "". Esto provoca bugs silenciosos en búsquedas y filtros. La convención recomendada es usar simplemente blank=True, default="".
*Única excepción:* cuando el campo de texto tiene unique=True y además es opcional (como un código de socio o ISBN que puede no existir, pero si existe debe ser único).
C. `unique=True` (Restricción de Unicidad)
Garantiza que no existan dos registros con el mismo valor (ej: nombres de usuario, correos o códigos SKU):
-- SQL resultante:
"sku" varchar(30) NOT NULL CONSTRAINT "app_sku_key" UNIQUE
D. `db_index=True` (Índices para Búsqueda Rápida)
Si planeas filtrar constantemente por ese campo (ej: Articulo.objects.filter(slug=...) o titulo__icontains=...), agrega db_index=True. Django creará un índice B-Tree en la base de datos:
CREATE INDEX "blog_articulo_slug_idx" ON "blog_articulo" ("slug");
E. `choices` y `models.TextChoices` (Enums Elegantes)
Permite restringir las opciones seleccionables en formularios y en el Admin, sin requerir tipos ENUM propietarios en la base de datos:
3. Catálogo Completo de Campos de Django y su Traducción a SQL
Django provee una amplia variedad de campos especializados. A continuación se analiza cada grupo, cómo se define en Python y qué columna genera en el motor relacional.
| Campo Django | Tipo en PostgreSQL | Tipo en SQLite | Uso Recomendado |
|---|---|---|---|
| CharField(max_length=n) | varchar(n) | varchar(n) | Textos cortos (nombres, títulos, códigos) |
| TextField() | text | text | Contenido largo, párrafos, notas sin límite |
| IntegerField() | integer | integer | Números enteros (-2.147M a +2.147M) |
| PositiveIntegerField() | integer CHECK ≥ 0 | integer unsigned | Cantidades, stocks, edades (nunca negativos) |
| DecimalField(max_digits, decimal_places) | numeric(p, s) | decimal(p, s) | Precios y dinero (precisión exacta sin redondeo) |
| FloatField() | double precision | real | Cálculos científicos aproximados (no usar para dinero) |
| BooleanField(default=False) | boolean | bool (0 o 1) | Banderas lógicas (activo, verificado, publicado) |
| DateField() | date | date | Fechas sin hora (cumpleaños, vencimiento) |
| DateTimeField() | timestamp with time zone | datetime | Marcas de tiempo exactas (created_at, updated_at) |
| UUIDField(default=uuid.uuid4) | uuid | char(32) | Identificadores únicos no adivinables |
| FileField() / ImageField() | varchar(100) | varchar(100) | Ruta del archivo en el servidor (no el archivo binario) |
| ForeignKey(Target, on_delete=...) | bigint REFERENCES ... | integer REFERENCES ... | Relaciones 1 a Muchos (crea columna _id) |
3.1 Campos Numéricos: ¿Por qué DecimalField y no FloatField para Dinero?
Uno de los errores más peligrosos en desarrollo backend es usar FloatField para almacenar monedas o saldos. Los números de coma flotante en computación utilizan el estándar IEEE 754 de base 2, produciendo micro-errores como 0.1 + 0.2 = 0.30000000000000004.
3.2 Campos de Fecha y Hora: `auto_now_add` vs `auto_now`
Casi todo modelo en producción requiere dos columnas de auditoría temporal:
3.3 Archivos e Imágenes: `FileField` e `ImageField`
Una duda muy común de los principiantes es pensar que la imagen o el PDF se almacenan en una columna binaria (BLOB) dentro de la base de datos. Esto es una pésima práctica de arquitectura.
Django únicamente guarda en la base de datos una cadena de texto con la ruta relativa del archivo:
3.4 Relaciones entre Modelos: Las 3 Grandes Conexiones SQL
En bases de datos relacionales, casi nunca trabajamos con tablas aisladas. La fuerza de Django ORM radica en cómo modela las 3 relaciones cardinales de SQL a través de Python, garantizando integridad referencial mediante claves foráneas y tablas intermedias.
Uno a Muchos — models.ForeignKey
Regla: Un registro de la Tabla A puede estar vinculado con múltiples registros de la Tabla B, pero cada registro de la Tabla B pertenece a un único registro de la Tabla A.Ejemplo: Una Categoría tiene muchos Productos, pero cada Producto pertenece a una sola Categoría.
Políticas de Eliminación obligatorias (on_delete):
models.CASCADE: Si se elimina la categoría, se borran automáticamente todos sus productos vinculados (peligroso para ventas o facturas).models.PROTECT: Impide eliminar la categoría si todavía tiene productos asociados. Lanza la excepciónProtectedError. Ideal para catálogos y clientes con facturas.models.SET_NULL: Si se elimina la categoría, el producto queda concategoria_id = NULL(requiere obligatoriamentenull=Trueen el campo).models.SET_DEFAULT: Reasigna el producto a una categoría por defecto predefinida (requieredefault=...).models.DO_NOTHING: Django no ejecuta nada; delega la integridad exclusivamente al motor de base de datos SQL.
related_name?# Navegación hacia adelante (Producto -> Categoría)
producto = Producto.objects.get(id=1)
print(producto.categoria.nombre) # "Electrónica"
# Navegación inversa (Categoría -> Productos) usando related_name='productos'
cat = Categoria.objects.get(nombre="Electrónica")
todos_los_prods = cat.productos.all() # QuerySet de todos los productos de esa categoríaUno a Uno — models.OneToOneField
Regla: Un registro de la Tabla A se corresponde con exactamente un registro de la Tabla B, y viceversa.Ejemplo: El modelo de usuario oficial de Django y su Perfil extendido con teléfono y biografía.
A diferencia de ForeignKey donde el inverso entrega una lista o QuerySet (user.pedidos.all()), en OneToOneField el inverso retorna directamente la instancia única del modelo (o lanza DoesNotExist):
usuario = User.objects.get(username="ada_lovelace")
# Acceso directo al objeto único, ¡no requiere .all() ni .first()!
print(usuario.perfil.biografia)Muchos a Muchos — models.ManyToManyField
Regla: Un registro de la Tabla A puede asociarse a varios registros de la Tabla B, y a su vez cada registro de la Tabla B puede vincularse a varios de la Tabla A.Ejemplo: Un Artículo de blog tiene múltiples Etiquetas (Tags), y cada Etiqueta clasifica a múltiples Artículos.
⚡ El secreto SQL: ¡Ni la tabla Articulo ni la tabla Etiqueta sufren cambios! Los motores relacionales no pueden almacenar múltiples claves en una sola celda. Por tanto, Django crea silenciosamente una tercera tabla intermedia (Join Table o Tabla Puente):
articulo = Articulo.objects.get(id=1)
tag_python = Etiqueta.objects.get(nombre="Python")
tag_django = Etiqueta.objects.get(nombre="Django")
# 1. Asociar etiquetas (.add)
articulo.etiquetas.add(tag_python, tag_django)
# 2. Desvincular una etiqueta (.remove)
articulo.etiquetas.remove(tag_python)
# 3. Consultar todas las etiquetas del artículo
print(articulo.etiquetas.all())
# 4. Consulta inversa: todos los artículos con el tag 'Django'
print(tag_django.articulos.all())
# 5. Vaciar todas las relaciones del artículo (.clear)
articulo.etiquetas.clear()💡 ¿Qué pasa si la relación Muchos a Muchos requiere datos extra? (Argumento through)
Imagina alumnos inscribiéndose en cursos. No basta con saber qué alumno está en qué curso; también necesitas registrar la fecha de inscripción y su calificación final. Para esto, creas tú mismo el modelo intermedio con through:
| Relación | Campo Django | ¿Qué crea en la BD SQL? | Navegación Directa | Navegación Inversa |
|---|---|---|---|---|
| 1 a N (Uno a Muchos) | models.ForeignKey(B, on_delete=...) | Columna física b_id en tabla A + Índice | a.b (objeto) | b.as.all() (QuerySet) |
| 1 a 1 (Uno a Uno) | models.OneToOneField(B, on_delete=...) | Columna física b_id en tabla A + Restricción UNIQUE | a.b (objeto) | b.a (objeto único) |
| N a M (Muchos a Muchos) | models.ManyToManyField(B) | Tercera tabla intermedia puente con las dos FKs | a.bs.all() (.add, .remove) | b.as.all() (.add, .remove) |
4. Laboratorio Interactivo: Simulador de Campos a SQL
Experimenta ajustando los nombres, tipos y opciones de cada campo para comprobar en tiempo real cómo cambia la sentencia SQL y qué restricciones aplica el motor:
Laboratorio de Campos Django → Generador SQL
Ajusta los parámetros del modelo en Python y observa en tiempo real la definición SQL exacta generada por las migraciones.
⚙️ Configuración del Campo
models.py):Python ORMsqlmigrate):postgresql"nombre" varchar(100) NOT NULLCREATE TABLE:CREATE TABLE "app_articulo" (
"id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
"nombre" varchar(100) NOT NULL
);- NOT NULL forzado: La base de datos rechazará cualquier intento de guardar una fila sin este valor.
- blank=False obligatorio en formularios: El formulario de Django requerirá obligatoriamente rellenar este campo antes de enviar.
5. Práctica Guiada: De `models.py` a la Base de Datos Física
Veamos un ejemplo completo paso a paso creando el modelo Articulo para un blog de tecnología.
Paso 1: Definir el Modelo en `blog/models.py`
Paso 2: Generar e Inspeccionar la Migración SQL
Paso 3: Análisis del SQL Real Emitido por Django
Paso 4: Aplicar Cambios a la Base de Datos
Ponte a prueba
Comprueba que dominas los tipos de campos de Django, las opciones `null` vs `blank`, y su traducción a SQL.


