Módulo 4 · Lección 8 de 45
Modelos: la primera tabla
Una clase de Python que resulta ser una tabla, el SQL que Django escribe por ti, y la columna que aparece sin que la pidas.
En la lección 47 del módulo 3 escribiste esto a mano:
CREATE TABLE IF NOT EXISTS contactos (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL,
correo TEXT NOT NULL UNIQUE,
telefono TEXT NOT NULL DEFAULT '',
grupo TEXT NOT NULL DEFAULT 'Sin grupo',
notas TEXT NOT NULL DEFAULT ''
);Y después, cada consulta con su cadena de SQL, sus interrogantes para los parámetros y su conversión de filas a diccionarios. Funcionaba bien. Lo que no aguanta es el cambio: añadir una columna obligaba a tocar el CREATE TABLE, todos los INSERT, todos los SELECT y el código que leía las filas, y no había nada que avisara si se te olvidaba uno.
Un modelo es esa misma tabla escrita como una clase de Python. De esa clase salen el SQL que la crea, las consultas, la conversión a objetos y los formularios. Esta lección escribe el primero.
La correspondencia#
Es de tres líneas y conviene tenerla clara desde el principio, porque todo el bloque cuelga de ella:
| En Python | En la base de datos |
|---|---|
Una clase que hereda de models.Model | Una tabla |
| Un atributo de clase que es un campo | Una columna |
| Una instancia de esa clase | Una fila |
Eso es un ORM, de object-relational mapper: la pieza que traduce entre objetos y tablas en los dos sentidos. Lo que ganas no es escribir menos SQL, que también: lo que ganas es que hay un solo sitio donde está definido lo que es un contacto. El día que añadas un campo, lo añades ahí y el resto se entera.
El modelo#
En contactos/models.py, que startapp dejó casi vacío:
from django.db import models
class Contacto(models.Model):
nombre = models.CharField(max_length=100)
correo = models.EmailField(unique=True)
telefono = models.CharField(max_length=20, blank=True)
grupo = models.CharField(max_length=30, default='Sin grupo')
notas = models.TextField(blank=True)
creado = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.nombreCompáralo con el CREATE TABLE de arriba: son las mismas cinco columnas, con la misma información, y una nueva. Punto por punto:
El nombre de la clase va en singular. Contacto, no Contactos. La clase representa un contacto, y la tabla la nombra Django. Es la convención universal en Django y conviene seguirla porque el panel de administración y los mensajes de error la dan por hecha al construir plurales.
Hereda de models.Model. De ahí le vienen el save(), el delete(), el objects con el que se consulta y la maquinaria que detecta los campos.
Cada campo es una clase distinta, no un tipo con opciones. CharField para texto corto, TextField para texto largo, EmailField para un correo, DateTimeField para una fecha con hora. Hay unos veinticinco y son la lección siguiente; aquí basta con cinco.
max_length es obligatorio en CharField y no lo es en TextField. No es un capricho: CharField se traduce a un VARCHAR(n) y ese n hay que decirlo. Si lo olvidas, makemigrations falla antes de tocar nada.
creado no estaba en la versión de Tkinter. auto_now_add=True hace que Django ponga la fecha actual al crear la fila y no la toque nunca más. Es de esas cosas que en SQL a mano se olvidan y luego se echan de menos.
__str__ no es decorativo. Es el método del módulo 1 de toda la vida, y aquí es lo que se ve en el panel de administración, en los desplegables de los formularios y al imprimir un objeto. Sin él, Django enseña Contacto object (1), que no dice nada. Se pone siempre, en todos los modelos, desde el primer día.
Convertirlo en una tabla#
El modelo por sí solo no crea nada. Son dos órdenes, y tienen su lección entera más adelante:
python manage.py makemigrationsMigrations for 'contactos':
contactos/migrations/0001_initial.py
+ Create model Contactopython manage.py migrateApplying contactos.0001_initial... OKLa primera escribe un fichero que describe el cambio; no toca la base de datos. La segunda lo aplica. Están separadas a propósito, porque ese fichero se guarda en el repositorio y es lo que permite que la base de datos de otra persona, o la del servidor, acabe exactamente igual que la tuya.
Ese SQL tiene tres cosas que no has escrito tú y merecen explicación.
La columna id#
No la has declarado y está ahí, como clave primaria y autoincremental. Django añade una a todo modelo que no declare la suya, porque toda fila necesita algo que la identifique. Se puede usar de dos formas:
contacto.id
contacto.pkpk es un alias de "clave primaria" que apunta a la que haya. Conviene usar pk en el código general, porque si algún día el modelo declara su propia clave primaria con otro nombre, el código que dice pk sigue funcionando.
El nombre de la tabla#
contactos_contacto. Django lo construye juntando el nombre de la aplicación y el de la clase en minúsculas, y así dos aplicaciones pueden tener cada una su Contacto sin chocar.
Se puede cambiar, y es lo que hay que hacer cuando el modelo se apoya en una tabla que ya existe:
class Contacto(models.Model):
...
class Meta:
db_table = 'contactos'Esa clase Meta de dentro guarda los ajustes que son de la tabla entera y no de un campo. Tiene su lección, la 15, con el orden por defecto, el nombre en plural y las restricciones.
El NOT NULL de notas#
Aquí está el detalle que más despista del modelo. notas tiene blank=True y su columna ha salido NOT NULL. Parece una contradicción y no lo es, porque blank y null son dos cosas distintas y solo una es de la base de datos:
| Opción | De quién es | Qué significa |
|---|---|---|
null=True | De la base de datos | La columna admite NULL |
blank=True | De los formularios | El campo se puede dejar vacío al rellenarlo |
notas con blank=True y sin null=True significa: en el formulario no es obligatorio, y cuando no se rellena se guarda la cadena vacía. No un NULL, una cadena vacía.
Y eso es lo que se quiere. La regla que evita disgustos:
En los campos de texto,
blank=Truesí ynull=Trueno.
El motivo es que con los dos puestos habría dos maneras de decir "no hay teléfono": la cadena vacía y el NULL. Entonces telefono = '' y telefono = None son cosas distintas para la base de datos, las consultas tienen que preguntar por las dos, y tarde o temprano una se olvida. En campos que no son de texto —un número, una fecha— no hay cadena vacía que valga, y ahí null=True sí es la forma correcta de decir que no hay valor. La lección siguiente entra en esto campo por campo.
Probarlo#
El intérprete de Django tiene su lección, la 11. Un adelanto para ver que la tabla existe y funciona:
python manage.py shell>>> Contacto.objects.create(nombre='Ana Ruiz', correo='ana@ejemplo.es', grupo='Trabajo')
<Contacto: Ana Ruiz>
>>> c = Contacto.objects.get(correo='ana@ejemplo.es')
>>> c.pk
1
>>> c.notas
''
>>> c.creado
datetime.datetime(2026, 9, 22, 16, 16, 47, 992908, tzinfo=datetime.timezone.utc)Cuatro cosas en esas líneas. El <Contacto: Ana Ruiz> sale de tu __str__. El pk lo ha puesto la base de datos al insertar. notas, que no se ha dado, es la cadena vacía y no None, como acaba de explicarse. Y creado viene en UTC, con su tzinfo, porque USE_TZ está activado: Django guarda siempre en UTC y convierte a TIME_ZONE solo al mostrar.