Módulo 4 · Lección 11 de 45
El intérprete de Django y el ORM
Un sitio donde probar consultas sin montar una página, y la pieza que traduce entre objetos y SQL.
Tienes una tabla con datos y todavía no hay ninguna página que los enseñe. Antes de escribir vistas y plantillas conviene pasar un rato probando el ORM en un sitio donde el ciclo entre escribir algo y ver el resultado sea inmediato, y ese sitio es el intérprete de Django.
Es la consola interactiva de Python de la lección 6 del módulo 0, con el proyecto cargado: los ajustes leídos, la conexión con la base de datos abierta y los modelos disponibles. Todo lo que pruebes ahí funcionará igual dentro de una vista.
Abrirlo#
python manage.py shell13 objects imported automatically (use -v 2 for details).Esa línea es lo primero que lo distingue de python3 a secas. Django importa por ti lo que casi siempre vas a querer, y con -v 2 dice exactamente qué:
from django.conf import settings
from django.db import connection, models, reset_queries
from django.db.models import functions
from django.utils import timezone
from contactos.models import Contacto
from django.contrib.auth.models import User, Group, Permission
...Todos tus modelos entran solos, y también los de las aplicaciones de Django. Es una comodidad reciente, así que en material un poco antiguo verás siempre un from contactos.models import Contacto al principio de cada ejemplo; ya no hace falta.
Lo importante es lo que hace por debajo: llama a django.setup(), que lee los ajustes, carga las aplicaciones y deja todo en el mismo estado en el que está cuando atiende una petición. Por eso no se puede abrir python3 y empezar a importar modelos: sale un ImproperlyConfigured quejándose de que los ajustes no están cargados.
Crear#
Dos formas, y las dos se usan:
>>> Contacto.objects.create(nombre='Ana Ruiz', correo='ana@ejemplo.es', grupo='trabajo')
<Contacto: Ana Ruiz>>>> c = Contacto(nombre='Luis Pardo', correo='luis@ejemplo.es')
>>> c.pk
>>> c.save()
>>> c.pk
2create() construye y guarda en un paso. La segunda forma construye el objeto en memoria y lo guarda cuando tú digas, lo que permite tocarlo por el medio. Fíjate en el pk: antes de save() no hay ninguno, porque lo asigna la base de datos al insertar la fila.
objects es el gestor del modelo. Es el atributo por el que se entra a todo, y su nombre es una convención que Django pone solo en cada modelo. Un detalle que confunde: objects es del modelo, no de las instancias. Contacto.objects sí; mi_contacto.objects, no.
Leer#
>>> Contacto.objects.all()
<QuerySet [<Contacto: Ana Ruiz>, <Contacto: Luis Pardo>]>
>>> Contacto.objects.count()
2
>>> Contacto.objects.exists()
True
>>> Contacto.objects.first()
<Contacto: Ana Ruiz>count() y exists() merecen una nota desde ya: hacen su trabajo en la base de datos, con un SELECT COUNT(*) y un LIMIT 1. Lo que se ve muchas veces escrito es len(Contacto.objects.all()) y if Contacto.objects.all():, que traen las diez mil filas a memoria para contarlas o para preguntar si hay alguna. Con dos filas da lo mismo; con diez mil, no.
get frente a filter#
Esta distinción se equivoca constantemente y merece su apartado.
>>> Contacto.objects.get(correo='ana@ejemplo.es')
<Contacto: Ana Ruiz>
>>> Contacto.objects.filter(grupo='trabajo')
<QuerySet [<Contacto: Ana Ruiz>]>get devuelve un objeto. filter devuelve un QuerySet, aunque solo encaje uno, aunque no encaje ninguno.
La diferencia de verdad es qué pasa cuando no hay exactamente un resultado:
>>> Contacto.objects.get(nombre='No existe')
Contacto.DoesNotExist: Contacto matching query does not exist.
>>> Contacto.objects.get(nombre='Ana Ruiz')
Contacto.MultipleObjectsReturned: get() returned more than one Contacto -- it returned 2!>>> Contacto.objects.filter(nombre='No existe')
<QuerySet []>
>>> Contacto.objects.filter(nombre='No existe').first()get lanza. filter devuelve una lista vacía, y first() sobre ella devuelve None sin quejarse.
Esas dos excepciones son atributos del propio modelo, Contacto.DoesNotExist y Contacto.MultipleObjectsReturned, así que se capturan por modelo y no hay riesgo de confundirse de uno.
La regla práctica: get solo con algo que sea único, como la clave primaria o un campo con unique=True. Con cualquier otra cosa, filter. Y el MultipleObjectsReturned de ahí arriba es justo lo que pasa por usar get con un nombre, que se puede repetir: el código funciona meses hasta el día que entran dos personas que se llaman igual.
Modificar y borrar#
Sobre un objeto, cambiar y guardar:
>>> c = Contacto.objects.get(correo='ana@ejemplo.es')
>>> c.telefono = '600 11 22 33'
>>> c.save()Con un detalle que conviene conocer. Ese save() genera esto:
UPDATE "contactos_contacto" SET "nombre" = 'Ana Ruiz', "correo" = 'ana@ejemplo.es',
"telefono" = '600 11 22 33', "grupo" = 'trabajo', ... WHERE "id" = 1Reescribe todas las columnas, no solo la que has tocado. Normalmente da igual. Cuando importa —porque hay dos procesos escribiendo, o porque la fila es grande— se le dice qué ha cambiado:
>>> c.save(update_fields=['telefono'])UPDATE "contactos_contacto" SET "telefono" = '600 11 22 33' WHERE "id" = 1Para cambiar muchas filas de golpe no se hace un bucle, se usa update() sobre el QuerySet:
>>> Contacto.objects.filter(grupo='sin').update(grupo='amigos')
1Devuelve cuántas filas ha cambiado, y es una sola consulta en lugar de una por fila.
Borrar, sobre un objeto o sobre un conjunto:
>>> c.delete()
(1, {'contactos.Contacto': 1})
>>> Contacto.objects.filter(grupo='amigos').delete()
(3, {'contactos.Contacto': 3})Devuelve el total borrado y el desglose por modelo. Ese desglose no es adorno: cuando haya relaciones, borrar un objeto puede llevarse por delante los que dependen de él, y ahí es donde se ve.
El QuerySet es perezoso#
Esta es la idea que más cambia la forma de escribir consultas, y se mide:
>>> qs = Contacto.objects.all()
>>> len(connection.queries)
0
>>> lista = list(qs)
>>> len(connection.queries)
1
>>> otra = list(qs)
>>> len(connection.queries)
1Tres cosas ahí:
- Crear el
QuerySetno consulta nada. Cero consultas. Es una descripción de lo que se quiere, no el resultado. - Se ejecuta cuando alguien necesita los datos: al recorrerlo, al hacer
list(), al pedirlen(), al imprimirlo. - Después guarda el resultado. El segundo
list()no vuelve a la base de datos.
Lo primero es lo que permite construir consultas por partes, que es la lección siguiente. Se puede escribir qs = qs.filter(...) cinco veces y se ejecuta una sola consulta al final, con todas las condiciones juntas.
Se puede ver el SQL de cualquier QuerySet sin ejecutarlo:
>>> print(Contacto.objects.filter(grupo='trabajo').query)
SELECT "contactos_contacto"."id", "contactos_contacto"."nombre", ...
FROM "contactos_contacto" WHERE "contactos_contacto"."grupo" = trabajoEs la mejor herramienta para entender qué está haciendo el ORM, y en la lección de consultas se usa constantemente.