Artículos

Cómo combinar QuerySets en Django

El ORM es muy potente. Hoy exploraremos los QuerySets y, en particular, cómo combinarlos o fusionarlos en Django.

QuerySet al estilo Django

La documentación de Django es bastante clara sobre la API de QuerySet. Explica:

Internamente, un QuerySet puede construirse, filtrarse, segmentarse y, en general, pasarse de un lugar a otro sin acceder realmente a la base de datos. No ocurre ninguna actividad en la base de datos hasta que haces algo para evaluar el queryset. 1

En otras palabras, se recomienda hacer consultas y manipular datos con QuerySets en lugar de consultas directas. Estas son las razones:

Rendimiento

Acceder a la base de datos solo al evaluar permite realizar muchas operaciones, como segmentar y filtrar. Esto aumenta la flexibilidad y el rendimiento de la posible consulta, a diferencia de las consultas directas, que acceden a la base de datos cada vez que las llamas.

Comodidad

Es más fácil filtrar, segmentar, iterar y manipular datos mediante QuerySet porque puedes generar una lista de QuerySets que ofrecen varios métodos para facilitar la manipulación. En cambio, con consultas directas debes lidiar con una estructura muy rígida que requiere trabajo adicional para entregar el mismo resultado.

Seguridad

Usar consultas directas requiere precaución, porque siempre hay que evitar parámetros controlados por el usuario para proteger la consulta de inyecciones SQL.

A veces necesitamos combinar los resultados de varios QuerySets en uno para agruparlos e iterar sobre ellos en una lista. Hay dos situaciones posibles:

Si el QuerySet trabaja con el mismo modelo

Se recomienda usar pipes y el operador | para realizar la fusión.

Considera los siguientes modelos:

class Group(models.Model):

   code = models.CharField(max_length=80, unique=True)
   name = models.CharField(max_length=128)
   users = models.ManyToManyField(User, related_name='groups')
   roles = models.ManyToManyField(Role, related_name='groups', blank=True)
   products = models.ManyToManyField(Product, related_name='groups', blank=True)


class Client(models.Model):

   name = models.CharField(max_length=30, unique=True)
   groups = models.ManyToManyField(Group)

Supongamos que quieres mostrar todos los grupos de un cliente específico junto con el grupo que pertenece a un usuario, usando el nombre de producto CMS A. Ten en cuenta que este User puede tener distintos grupos.

client = Client.objects.get(name='Django Client Name')
user = User.objects.get(username='cdiaz')
django_groups = client.groups.all()
cdiaz_groups = user.client.filter(products__name='CMS A')

Ahora tenemos dos QuerySets diferentes: uno contiene todos los grupos del cliente y el otro todos los grupos del usuario con el nombre del producto.

groups = django_groups | cdiaz_groups  # merge querysets

Puedes examinar la consulta SQL que genera Django usando el atributo query. El comando completo para el caso de groups es print(groups.query). Django combina automáticamente ambas consultas, django_groups y cdiaz_groups, en una sola consulta SQL.

El proceso es muy sencillo, pero ten en cuenta que solo funcionará con QuerySets del mismo modelo y antes de segmentarlos.

Si los QuerySets trabajan con modelos diferentes

Supongamos que tenemos tres modelos diferentes, Post, Article y Page, que queremos combinar en uno para realizar operaciones como filtrar y ordenar. Un enfoque sería usar chain de itertools:

from itertools import chain

def get_all_documents():
   articles = Article.objects.all()
   posts = Post.objects.all()
   pages = Page.objects.all()
   return list(chain(pages, articles, posts))

Ahora podemos ordenar la lista resultante, por ejemplo por fecha, usando la función sorted() y haciendo algunos pequeños ajustes.

from itertools import chain

def get_all_documents_sorted():
   articles = Article.objects.all()
   posts = Post.objects.all()
   pages = Page.objects.all()
   result_list = sorted(
       chain(pages, articles, posts),
       key=attrgetter('date_created'), reverse=True)
   return result_list

De esta manera, la lista puede obtenerse en orden inverso. Es importante señalar que Django ejecuta una consulta SQL diferente para cada uno de los tres conjuntos en este caso, pero no se ejecutan hasta que la función sorted itera sobre cada uno.


En resumen, mostramos varios enfoques para fusionar QuerySets con el mismo modelo o con modelos diferentes en Django sin depender de consultas directas, lo que permite aprovechar sus ventajas de rendimiento, flexibilidad y comodidad.


  1. "Referencia de la API de QuerySet," Documentación, Django Project. ↩

“Cómo combinar QuerySets en Django” de Cesar Diaz está bajo la licencia CC BY SA. Los ejemplos de código fuente están bajo la licencia MIT.

Foto de Kelly Sikkema.

Clasificado en Investigación y aprendizaje.

Lecturas relacionadas