Café y Código

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 datosCREATE 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:

  1. 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).
  2. 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.
  3. python manage.py migrate: Ejecuta las sentencias SQL en la base de datos física para crear o alterar las tablas.
Inspección de SQL generado con sqlmigrate
CMD
1(venv) C:\proyectos\django\mi_blog> python manage.py makemigrations blog
2Migrations for 'blog':
3 blog/migrations/0001_initial.py
4 - Create model Articulo
5
6(venv) C:\proyectos\django\mi_blog> python manage.py sqlmigrate blog 0001
7--
8-- Create model Articulo
9--
10CREATE TABLE "blog_articulo" (
11 "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
12 "titulo" varchar(150) NOT NULL
13);

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.

# En Python (models.py):
nombre = models.CharField(max_length=100)
# En SQL generado:
"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 SQL NOT NULL. Cualquier intento de insertar una fila sin valor fallará a nivel de motor.
  • null=True: Permite que la columna contenga NULL en 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):

sku = models.CharField(max_length=30,unique=True)
-- 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:

-- SQL ejecutado automáticamente tras crear la tabla:
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:

articulo_choices.py
PYTHON
1class Articulo(models.Model):
2 class Estado(models.TextChoices):
3 BORRADOR = 'BORRADOR', 'Borrador en revisión'
4 PUBLICADO = 'PUBLICADO', 'Público en el blog'
5 ARCHIVADO = 'ARCHIVADO', 'Archivado'
6
7 estado = models.CharField(
8 max_length=15,
9 choices=Estado.choices,
10 default=Estado.BORRADOR
11 )
12
13# En la base de datos se guarda como VARCHAR(15):
14# "estado" varchar(15) DEFAULT 'BORRADOR' NOT NULL
15
16# Y en Python obtienes el texto legible automáticamente:
17# articulo.get_estado_display() -> "Borrador en revisión"

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 DjangoTipo en PostgreSQLTipo en SQLiteUso Recomendado
CharField(max_length=n)varchar(n)varchar(n)Textos cortos (nombres, títulos, códigos)
TextField()texttextContenido largo, párrafos, notas sin límite
IntegerField()integerintegerNúmeros enteros (-2.147M a +2.147M)
PositiveIntegerField()integer CHECK ≥ 0integer unsignedCantidades, 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 precisionrealCálculos científicos aproximados (no usar para dinero)
BooleanField(default=False)booleanbool (0 o 1)Banderas lógicas (activo, verificado, publicado)
DateField()datedateFechas sin hora (cumpleaños, vencimiento)
DateTimeField()timestamp with time zonedatetimeMarcas de tiempo exactas (created_at, updated_at)
UUIDField(default=uuid.uuid4)uuidchar(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.

comparativa_dinero.py
PYTHON
1# ❌ NUNCA PARA DINERO:
2precio = models.FloatField() # Puede perder centavos en sumas y multiplicaciones
3
4# ✔ LA FORMA PROFESIONAL:
5precio = models.DecimalField(
6 max_digits=10, # Cantidad total de dígitos (incluyendo decimales)
7 decimal_places=2, # Dígitos reservados para la parte decimal
8 default=0.00
9)
10# SQL generado: "precio" numeric(10, 2) DEFAULT 0.00 NOT NULL
11# Permite guardar hasta 99,999,999.99 con precisión matemática absoluta

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:

auditoria_model.py
PYTHON
1class AuditoriaBase(models.Model):
2 # auto_now_add=True: Solo se establece 1 vez, al momento de hacer INSERT
3 created_at = models.DateTimeField(auto_now_add=True)
4
5 # auto_now=True: Se refresca automáticamente a la hora actual en cada SAVE / UPDATE
6 updated_at = models.DateTimeField(auto_now=True)
7
8 class Meta:
9 abstract = True # Clase base que no crea tabla física por sí misma

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:

guardar_imagenes.py
PYTHON
1portada = models.ImageField(upload_to='portadas/%Y/%m/', blank=True)
2# En la BD SQL solo se almacena un VARCHAR(100):
3# 'portadas/2026/09/clean_code.jpg'

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.

1 a N

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.

tienda/models.py (1 a N)
PYTHON
1from django.db import models
2
3class Categoria(models.Model):
4 nombre = models.CharField(max_length=50)
5
6 def __str__(self):
7 return self.nombre
8
9
10class Producto(models.Model):
11 nombre = models.CharField(max_length=100)
12 precio = models.DecimalField(max_digits=10, decimal_places=2)
13
14 # 1:N -> La FK siempre se define en el modelo 'Muchos' (el hijo)
15 categoria = models.ForeignKey(
16 Categoria,
17 on_delete=models.CASCADE, # ¿Qué pasa si borran la categoría?
18 related_name='productos', # Acceso inverso desde la categoría
19 help_text="Categoría a la que pertenece el producto"
20 )
21
22 def __str__(self):
23 return self.nombre
⚡ SQL generado físicamente en la Base de Datos (1 a N)
SQL
1-- Tabla Padre
2CREATE TABLE "tienda_categoria" (
3 "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
4 "nombre" varchar(50) NOT NULL
5);
6
7-- Tabla Hija: Django añade automáticamente el sufijo '_id' y un ÍNDICE
8CREATE TABLE "tienda_producto" (
9 "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
10 "nombre" varchar(100) NOT NULL,
11 "precio" numeric(10, 2) NOT NULL,
12 "categoria_id" bigint NOT NULL REFERENCES "tienda_categoria" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
13);
14
15-- Django crea un índice para búsquedas ultra rápidas por FK
16CREATE INDEX "tienda_producto_categoria_id_idx" ON "tienda_producto" ("categoria_id");

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ón ProtectedError. Ideal para catálogos y clientes con facturas.
  • models.SET_NULL: Si se elimina la categoría, el producto queda con categoria_id = NULL (requiere obligatoriamentenull=True en el campo).
  • models.SET_DEFAULT: Reasigna el producto a una categoría por defecto predefinida (requiere default=...).
  • models.DO_NOTHING: Django no ejecuta nada; delega la integridad exclusivamente al motor de base de datos SQL.
¿Cómo se navega en Python gracias a 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ía
1 a 1

Uno 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.

usuarios/models.py (1 a 1)
PYTHON
1from django.db import models
2from django.contrib.auth.models import User
3
4class PerfilUsuario(models.Model):
5 # Relación 1:1 estricta con el usuario de Django
6 user = models.OneToOneField(
7 User,
8 on_delete=models.CASCADE,
9 related_name='perfil'
10 )
11 biografia = models.TextField(blank=True)
12 telefono = models.CharField(max_length=20, blank=True)
13 avatar = models.ImageField(upload_to='avatares/', blank=True)
14
15 def __str__(self):
16 return f"Perfil de {self.user.username}"
⚡ SQL generado en BD: Diferencia frente a ForeignKey (1 a 1)
SQL
1-- Django crea exactamente una clave foránea, pero le agrega la restricción UNIQUE:
2CREATE TABLE "usuarios_perfilusuario" (
3 "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
4 "biografia" text NOT NULL,
5 "telefono" varchar(20) NOT NULL,
6 "avatar" varchar(100) NOT NULL,
7 "user_id" integer NOT NULL UNIQUE REFERENCES "auth_user" ("id") ON DELETE CASCADE
8);
Diferencia fundamental de acceso en Python:

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)
N a M

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.

blog/models.py (N a M)
PYTHON
1from django.db import models
2
3class Etiqueta(models.Model):
4 nombre = models.CharField(max_length=30, unique=True)
5 slug = models.SlugField(max_length=30, unique=True)
6
7 def __str__(self):
8 return self.nombre
9
10
11class Articulo(models.Model):
12 titulo = models.CharField(max_length=150)
13 cuerpo = models.TextField()
14
15 # Relación N:M -> Puede definirse en cualquiera de los dos modelos
16 etiquetas = models.ManyToManyField(
17 Etiqueta,
18 related_name='articulos',
19 blank=True # Permite guardar artículos sin etiquetas al inicio
20 )
21
22 def __str__(self):
23 return self.titulo

⚡ 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):

⚡ SQL generado: Tabla Intermedia (ManyToMany N a M)
SQL
1-- Tabla intermedia generada automáticamente: "blog_articulo_etiquetas"
2CREATE TABLE "blog_articulo_etiquetas" (
3 "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
4 "articulo_id" bigint NOT NULL REFERENCES "blog_articulo" ("id") ON DELETE CASCADE,
5 "etiqueta_id" bigint NOT NULL REFERENCES "blog_etiqueta" ("id") ON DELETE CASCADE,
6 CONSTRAINT "blog_articulo_etiquetas_uniq" UNIQUE ("articulo_id", "etiqueta_id")
7);
Operaciones ORM especiales para Muchos a Muchos:
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:

academia/models.py (ManyToMany con through)
PYTHON
1class Curso(models.Model):
2 nombre = models.CharField(max_length=100)
3 estudiantes = models.ManyToManyField('Estudiante', through='Inscripcion')
4
5class Estudiante(models.Model):
6 nombre = models.CharField(max_length=100)
7
8# Modelo Intermedio Personalizado
9class Inscripcion(models.Model):
10 curso = models.ForeignKey(Curso, on_delete=models.CASCADE)
11 estudiante = models.ForeignKey(Estudiante, on_delete=models.CASCADE)
12 fecha_inscripcion = models.DateField(auto_now_add=True)
13 nota_final = models.DecimalField(max_digits=4, decimal_places=2, null=True, blank=True)
14
15 class Meta:
16 unique_together = ('curso', 'estudiante') # Un estudiante no se inscribe dos veces al mismo curso
RelaciónCampo Django¿Qué crea en la BD SQL?Navegación DirectaNavegación Inversa
1 a N (Uno a Muchos)models.ForeignKey(B, on_delete=...)Columna física b_id en tabla A + Índicea.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 UNIQUEa.b (objeto)b.a (objeto único)
N a M (Muchos a Muchos)models.ManyToManyField(B)Tercera tabla intermedia puente con las dos FKsa.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:

SIMULADOR INTERACTIVO

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.

Motor SQL:
Casos Típicos:

⚙️ Configuración del Campo

max_length (Obligatorio en CharField):100
5 (ej: código)100 (nombre)255 (título)500 (resumen)
Modificadores y Restricciones:
🐍 Código en Django (models.py):Python ORM
class Articulo(models.Model):
nombre = models.CharField(max_length=100)
⚡ Definición de Columna SQL (sqlmigrate):postgresql
"nombre" varchar(100) NOT NULL
🏗️ Instrucción Completa CREATE TABLE:
CREATE TABLE "app_articulo" (
    "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    "nombre" varchar(100) NOT NULL
);
🔍 ¿Cómo se interpreta en la Base de Datos?
  • 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`

blog/models.py
PYTHON
1from django.db import models
2from django.utils.text import slugify
3
4class Categoria(models.Model):
5 nombre = models.CharField(max_length=50, unique=True)
6
7 def __str__(self):
8 return self.nombre
9
10
11class Articulo(models.Model):
12 class Estado(models.TextChoices):
13 BORRADOR = 'BORRADOR', 'Borrador'
14 PUBLICADO = 'PUBLICADO', 'Publicado'
15
16 # Campos de texto
17 titulo = models.CharField(max_length=200, db_index=True)
18 slug = models.SlugField(max_length=220, unique=True, blank=True)
19 contenido = models.TextField(help_text="Cuerpo del artículo en formato Markdown")
20
21 # Números y booleano
22 visitas = models.PositiveIntegerField(default=0)
23 destacado = models.BooleanField(default=False)
24
25 # Opciones y Enums
26 estado = models.CharField(max_length=15, choices=Estado.choices, default=Estado.BORRADOR)
27
28 # Fechas
29 created_at = models.DateTimeField(auto_now_add=True)
30 updated_at = models.DateTimeField(auto_now=True)
31
32 # Clave Foránea (1:N)
33 categoria = models.ForeignKey(Categoria, on_delete=models.CASCADE, related_name='articulos')
34
35 def save(self, *args, **kwargs):
36 if not self.slug:
37 self.slug = slugify(self.titulo)
38 super().save(*args, **kwargs)
39
40 def __str__(self):
41 return self.titulo

Paso 2: Generar e Inspeccionar la Migración SQL

Generar e inspeccionar migración
CMD
1(venv) C:\proyectos\django\blog> python manage.py makemigrations blog
2Migrations for 'blog':
3 blog/migrations/0001_initial.py
4 - Create model Categoria
5 - Create model Articulo
6
7(venv) C:\proyectos\django\blog> python manage.py sqlmigrate blog 0001

Paso 3: Análisis del SQL Real Emitido por Django

SQL Generado por PostgreSQL / SQLite
SQL
1-- 1. Tabla de Categorías
2CREATE TABLE "blog_categoria" (
3 "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
4 "nombre" varchar(50) NOT NULL UNIQUE
5);
6
7-- 2. Tabla de Artículos
8CREATE TABLE "blog_articulo" (
9 "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
10 "titulo" varchar(200) NOT NULL,
11 "slug" varchar(220) NOT NULL UNIQUE,
12 "contenido" text NOT NULL,
13 "visitas" integer NOT NULL CHECK ("visitas" >= 0),
14 "destacado" boolean NOT NULL,
15 "estado" varchar(15) NOT NULL,
16 "created_at" timestamp with time zone NOT NULL,
17 "updated_at" timestamp with time zone NOT NULL,
18 "categoria_id" bigint NOT NULL REFERENCES "blog_categoria" ("id") DEFERRABLE INITIALLY DEFERRED
19);
20
21-- 3. Índices B-Tree automáticos para acelerar búsquedas
22CREATE INDEX "blog_articulo_titulo_idx" ON "blog_articulo" ("titulo");
23CREATE INDEX "blog_articulo_categoria_id_idx" ON "blog_articulo" ("categoria_id");

Paso 4: Aplicar Cambios a la Base de Datos

Aplicar la migración definitiva
CMD
1(venv) C:\proyectos\django\blog> python manage.py migrate
2Operations to perform:
3 Apply all migrations: admin, auth, blog, contenttypes, sessions
4Running migrations:
5 Applying blog.0001_initial... OK

Ponte a prueba

Comprueba que dominas los tipos de campos de Django, las opciones `null` vs `blank`, y su traducción a SQL.

Ko-fi
Donaciones
Apoyá cafeycodigo con un café en Ko-fi. Colaboradores: insignia, muro y zona exclusiva.