¿Cómo combinar múltiples QuerySets en Django?
Estoy intentando crear la búsqueda para un sitio Django que estoy creando y, en esa búsqueda, estoy buscando en tres modelos diferentes. Y para obtener paginación en la lista de resultados de búsqueda, me gustaría usar una vista genérica de lista de objetos para mostrar los resultados. Pero para hacer eso, tengo que fusionar tres QuerySets en uno.
¿Cómo puedo hacer eso? He probado esto:
result_list = []
page_list = Page.objects.filter(
Q(title__icontains=cleaned_search_term) |
Q(body__icontains=cleaned_search_term))
article_list = Article.objects.filter(
Q(title__icontains=cleaned_search_term) |
Q(body__icontains=cleaned_search_term) |
Q(tags__icontains=cleaned_search_term))
post_list = Post.objects.filter(
Q(title__icontains=cleaned_search_term) |
Q(body__icontains=cleaned_search_term) |
Q(tags__icontains=cleaned_search_term))
for x in page_list:
result_list.append(x)
for x in article_list:
result_list.append(x)
for x in post_list:
result_list.append(x)
return object_list(
request,
queryset=result_list,
template_object_name='result',
paginate_by=10,
extra_context={
'search_term': search_term},
template_name="search/result_list.html")
Pero esto no funciona. Recibo un error cuando intento usar esa lista en la vista genérica. A la lista le falta el atributo de clonación.
¿Cómo puedo fusionar las tres listas page_list
, article_list
y post_list
?
Concatenar los conjuntos de consultas en una lista es el enfoque más sencillo. Si la base de datos se verá afectada por todos los conjuntos de consultas de todos modos (por ejemplo, porque es necesario ordenar el resultado), esto no agregará costos adicionales.
from itertools import chain
result_list = list(chain(page_list, article_list, post_list))
Usar itertools.chain
es más rápido que hacer un bucle en cada lista y agregar elementos uno por uno, ya que itertools
está implementado en C. También consume menos memoria que convertir cada conjunto de consultas en una lista antes de concatenar.
Ahora es posible ordenar la lista resultante, por ejemplo, por fecha (como se solicita en el comentario de hasen j en otra respuesta). La sorted()
función acepta convenientemente un generador y devuelve una lista:
from operator import attrgetter
result_list = sorted(
chain(page_list, article_list, post_list),
key=attrgetter('date_created')
)
Puede invertir el orden de clasificación:
result_list = sorted(
chain(page_list, article_list, post_list),
key=attrgetter('date_created'),
reverse=True,
)
attrgetter
es equivalente a lo siguiente lambda
(así era como debía hacerse antes de Python 2.4):
result_list = sorted(
chain(page_list, article_list, post_list),
key=lambda instance: instance.date_created,
)
Prueba esto:
matches = pages | articles | posts
Conserva todas las funciones de los conjuntos de consultas, lo cual es bueno si lo desea order_by
o similar.
Tenga en cuenta: esto no funciona en conjuntos de consultas de dos modelos diferentes.