Como combinar QuerySets no Django

O ORM é muito poderoso. Hoje vamos explorar os QuerySets e, mais especificamente, como combiná-los ou mesclá-los no Django.
QuerySet ao estilo Django
A documentação do Django é bastante clara sobre a API de QuerySet. Ela explica:
Internamente, um QuerySet pode ser construído, filtrado, fatiado e, em geral, passado de um lugar a outro sem realmente acessar o banco de dados. Nenhuma atividade ocorre no banco de dados até que você faça algo para avaliar o queryset. 1
Em outras palavras, recomenda-se fazer consultas e manipular dados com QuerySets em vez de consultas diretas. Estas são as razões:
Desempenho
Acessar o banco de dados apenas no momento da avaliação permite realizar muitas operações, como fatiamento e filtragem. Isso aumenta a flexibilidade e o desempenho da possível consulta, ao contrário das consultas diretas, que acessam o banco de dados a cada chamada.
Conveniência
É mais fácil filtrar, fatiar, iterar e manipular dados por meio de QuerySet porque você pode gerar uma lista de QuerySets que oferecem vários métodos para facilitar a manipulação. Já com consultas diretas, você precisa lidar com uma estrutura muito rígida que exige trabalho extra para entregar o mesmo resultado.
Segurança
Usar consultas diretas exige cautela, pois sempre é preciso evitar parâmetros controlados pelo usuário para proteger a consulta contra injeção de SQL.
Às vezes, precisamos combinar os resultados de vários QuerySets em um só para agrupá-los e iterar sobre eles em uma lista. Há duas situações possíveis:
Se o QuerySet trabalha com o mesmo modelo
Recomenda-se usar pipes e o operador | para realizar a mesclagem.
Considere os seguintes 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)
Suponha que você queira exibir todos os grupos de um cliente específico junto com o grupo que pertence a um usuário, usando o nome de produto CMS A. Observe que esse User pode ter diferentes 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')
Agora temos dois QuerySets diferentes: um contém todos os grupos do cliente e o outro todos os grupos do usuário com o nome do produto.
groups = django_groups | cdiaz_groups # merge querysets
Você pode examinar a consulta SQL gerada pelo Django usando o atributo query. O comando completo para o caso de groups é print(groups.query). O Django combina automaticamente as duas consultas, django_groups e cdiaz_groups, em uma única consulta SQL.
O processo é muito simples, mas considere que só funciona com QuerySets do mesmo modelo e antes do fatiamento.
Se os QuerySets trabalham com modelos diferentes
Suponha que temos três modelos diferentes, Post, Article e Page, que queremos combinar em um só para realizar operações como filtragem e ordenação. Uma abordagem seria 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))
Agora podemos ordenar a lista resultante, por exemplo por data, usando a função sorted() e fazendo alguns pequenos 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
Dessa forma, a lista pode ser obtida em ordem inversa. É importante observar que o Django executa uma consulta SQL diferente para cada um dos três conjuntos nesse caso, mas elas não são executadas até que a função sorted itere sobre cada um.
Em resumo, mostramos várias abordagens para mesclar QuerySets com o mesmo modelo ou com modelos diferentes no Django sem depender de consultas diretas, permitindo aproveitar suas vantagens de desempenho, flexibilidade e conveniência.
-
"Referência da API de QuerySet," Documentação, Django Project. ↩