Ver índice de contenidos del libro

5.11. Seleccionar objetos

La creación y actualización de datos seguro es divertida, pero también es inútil sin una forma de tamizar los datos. Ya hemos visto una forma de examinar todos los datos de un determinado modelo:

>>> Publisher.objects.all()
[<Publisher: Addison-Wesley>, <Publisher: O'Reilly>, <Publisher: Apress Publishing>]

Eso se traslada a esto en SQL:

SELECT
    id, name, address, city, state_province, country, website
FROM book_publisher;

Nota Nota que Django no usa SELECT * cuando busca datos y en cambio lista todos los campos explícitamente. Esto es una decisión de diseño: en determinadas circunstancias SELECT * puede ser lento, y (más importante) listar los campos sigue el principio del Zen de Python: "Explícito es mejor que implícito".

Para más sobre el Zen de Python, intenta escribiendo import this en el prompt de Python.

Echemos un vistazo a cada parte de esta linea Publisher.objects.all():

  • En primer lugar, tenemos nuestro modelo definido, Publisher. Aquí no hay nada extraño: cuando quieras buscar datos, usa el modelo para esto.
  • Luego, tenemos objects. Técnicamente, esto es un administrador (manager). Los administradores son discutidos en el Apéndice B. Por ahora, todo lo que necesitas saber es que los administradores se encargan de todas las operaciones a "nivel de tablas" sobre los datos incluidos, y lo más importante, las consultas.

    Todos los modelos automáticamente obtienen un administrador objects; debes usar el mismo cada vez que quieras consultar sobre una instancia del modelo.

  • Finalmente, tenemos all(). Este es un método del administrador objects que retorna todas las filas de la base de datos. Aunque este objeto se parece a una lista, es realmente un QuerySet — un objeto que representa algún conjunto de filas de la base de datos. El Apéndice C describe QuerySets en detalle. Para el resto de este capítulo, sólo trataremos estos como listas emuladas.

Cualquier búsqueda en base de datos va a seguir esta pauta general — llamaremos métodos del administrador adjunto al modelo en el cual queremos hacer nuestra consulta.

5.11.1. Filtrar datos

Aunque obtener todos los objetos es algo que ciertamente tiene su utilidad, la mayoría de las veces lo que vamos a necesitar es manejarnos sólo con un subconjunto de los datos. Para ello usaremos el método filter():

>>> Publisher.objects.filter(name="Apress Publishing")
[<Publisher: Apress Publishing>]

filter() toma argumentos de palabra clave que son traducidos en las cláusulas SQL WHERE apropiadas. El ejemplo anterior sería traducido en algo como:

SELECT
    id, name, address, city, state_province, country, website
FROM book_publisher
WHERE name = 'Apress Publishing';

Puedes pasarle a filter() múltiples argumentos para reducir las cosas aún más:

>>> Publisher.objects.filter(country="U.S.A.", state_province="CA")
[<Publisher: Apress Publishing>]

Esos múltiples argumentos son traducidos a cláusulas SQL AND. Por lo tanto el ejemplo en el fragmento de código se traduce a lo siguiente:

SELECT
    id, name, address, city, state_province, country, website
FROM book_publisher
WHERE country = 'U.S.A.' AND state_province = 'CA';

Notar que por omisión la búsqueda usa el operador SQL = para realizar búsquedas exactas. Existen también otros tipos de búsquedas:

>>> Publisher.objects.filter(name__contains="press")
[<Publisher: Apress Publishing>]

Notar el doble guión bajo entre name y contains. Del mismo modo que Python, Django usa el doble guión bajo para indicar que algo "mágico" está sucediendo — aquí la parte __contains es traducida por Django en una sentencia SQL LIKE:

SELECT
    id, name, address, city, state_province, country, website
FROM book_publisher
WHERE name LIKE '%press%';

Hay disponibles varios otos tipos de búsqueda, incluyendo icontains (LIKE no sensible a diferencias de mayúsculas/minúsculas), startswith y endswith, y range (consultas SQL BETWEEN). El Apéndice C describe en detalle todos esos tipos de búsqueda.

5.11.2. Obtener objetos individuales

En ocasiones desearás obtener un único objeto. Para esto existe el método get():

>>> Publisher.objects.get(name="Apress Publishing")
<Publisher: Apress Publishing>

En lugar de una lista (o más bien, un QuerySet), este método retorna un objeto individual. Debido a eso, una consulta cuyo resultado sean múltiples objetos causará una excepción:

>>> Publisher.objects.get(country="U.S.A.")
Traceback (most recent call last):
    ...
AssertionError: get() returned more than one Publisher -- it returned 2!

Una consulta que no retorne objeto alguno también causará una excepción:

>>> Publisher.objects.get(name="Penguin")
Traceback (most recent call last):
    ...
DoesNotExist: Publisher matching query does not exist.

5.11.3. Ordenar datos

A medida que juegas con los ejemplos anteriores, podrías descubrir que los objetos son devueltos en lo que parece ser un orden aleatorio. No estás imaginándote cosas, hasta ahora no le hemos indicado a la base de datos cómo ordenar sus resultados, de manera que simplemente estamos recibiendo datos con algún orden arbitrario seleccionado por la base de datos.

Eso es, obviamente, un poco ingenuo. No quisiéramos que una página Web que muestra una lista de editores estuviera ordenada aleatoriamente. Así que, en la práctica, probablemente querremos usar order_by() para reordenar nuestros datos en listas más útiles:

>>> Publisher.objects.order_by("name")
[<Publisher: Addison-Wesley>, <Publisher: Apress Publishing>, <Publisher: O'Reilly>]

Esto no se ve muy diferente del ejemplo de all() anterior, pero el SQL incluye ahora un ordenamiento específico:

SELECT
    id, name, address, city, state_province, country, website
FROM book_publisher
ORDER BY name;

Podemos ordenar por cualquier campo que deseemos:

>>> Publisher.objects.order_by("address")
[<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher: Addison-Wesley>]
 
>>> Publisher.objects.order_by("state_province")
[<Publisher: Apress Publishing>, <Publisher: Addison-Wesley>, <Publisher: O'Reilly>]

y por múltiples campos:

>>> Publisher.objects.order_by("state_provice", "address")
 [<Publisher: Apress Publishing>, <Publisher: O'Reilly>, <Publisher: Addison-Wesley>]

También podemos especificar un ordenamiento inverso antecediendo al nombre del campo un prefijo - (el símbolo menos):

>>> Publisher.objects.order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher: Addison-Wesley>]

Aunque esta flexibilidad es útil, usar order_by() todo el tiempo puede ser demasiado repetitivo. La mayor parte del tiempo querrás ordenar por un determinado campo. Es esos casos Django te permite anexar al modelo un ordenamiento por omisión para el mismo:

class Publisher(models.Model):
    name = models.CharField(maxlength=30)
    address = models.CharField(maxlength=50)
    city = models.CharField(maxlength=60)
    state_province = models.CharField(maxlength=30)
    country = models.CharField(maxlength=50)
    website = models.URLField()
 
    def __str__(self):
        return self.name
 
    class Meta:
        ordering = ["name"]

Este fragmento ordering = ["name"] le indica a Django que a menos que se proporcione un ordenamiento mediante order_by(), todos los editores deberán ser ordenados por su nombre.

Nota Django usa esta class Meta interna como un lugar en el cual se pueden especificar metadatos adicionales acerca de un modelo. Es completamente opcional, pero puede realizar algunas cosas muy útiles. Examina el Apéndice B para conocer las opciones que puede poner bajo Meta.

5.11.4. Encadenar búsquedas

Has visto cómo puedes filtrar datos y has visto cómo ordenarlos. En ocasiones, por supuesto, vas a desear realizar ambas cosas. En esos casos simplemente "encadenas" las búsquedas entre sí:

>>> Publisher.objects.filter(country="U.S.A.").order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher: Addison-Wesley>]

Como podrías esperar, esto se traduce a una consulta SQL conteniendo tanto un WHERE como un ORDER BY:

SELECT
    id, name, address, city, state_province, .. code-block:: sqlcountry, website
FROM book_publisher
WHERE country = 'U.S.A.'
ORDER BY name DESC;

Puedes encadenar consultas en forma consecutiva tantas veces como desees. No existe un límite para esto.