¿Cómo combinar 2 o más querysets en una vista de Django?

Estoy intentando construir la búsqueda de un sitio de Django que estoy construyendo, y en la búsqueda estoy buscando en 3 modelos diferentes. Y para obtener la paginación en la lista de resultados de búsqueda, me gustaría utilizar una vista genérica object_list para mostrar los resultados. Pero para hacer eso tengo que fusionar 3 conjuntos de consultas en uno.

¿Cómo puedo hacer eso? He intentado 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 trato de usar esa lista en la vista genérica. A la lista le falta el atributo de clonación.

¿Alguien sabe cómo puedo unir las tres listas, page_list , article_list y post_list ?

Concatenar los conjuntos de consultas en una lista es el enfoque más simple. Si la base de datos se aplicará a todos los conjuntos de consultas de todos modos (p. Ej., Porque el resultado se debe ordenar), esto no agregará un costo adicional.

 from itertools import chain result_list = list(chain(page_list, article_list, post_list)) 

El uso de itertools.chain es más rápido que hacer un bucle en cada lista y agregar elementos uno a uno, ya que itertools se implementa en C. También consume menos memoria que convertir cada conjunto de consulta en una lista antes de concatenar.

Ahora es posible ordenar la lista resultante, por ejemplo, por fecha (como se solicitó en el comentario de hasen j a otra respuesta). La función sorted() acepta convenientemente un generador y devuelve una lista:

 result_list = sorted( chain(page_list, article_list, post_list), key=lambda instance: instance.date_created) 

Si está usando Python 2.4 o posterior, puede usar attrgetter lugar de un lambda. Recuerdo haber leído que era más rápido, pero no vi una diferencia de velocidad notable para una lista de millones de elementos.

 from operator import attrgetter result_list = sorted( chain(page_list, article_list, post_list), key=attrgetter('date_created')) 

Prueba esto:

 matches = pages | articles | posts 

Conserva todas las funciones de los querysets, lo que es bueno si quieres order_by o similar.

Vaya, tenga en cuenta que esto no funciona en los conjuntos de consulta de dos modelos diferentes …

Puede usar la clase QuerySetChain continuación. Al usarlo con el paginador de Django, solo debería llegar a la base de datos con COUNT(*) consultas para todos los querysets y SELECT() consultas solo para aquellos querysets cuyos registros se muestran en la página actual.

Tenga en cuenta que debe especificar template_name= si usa un QuerySetChain con vistas genéricas, incluso si los querysets encadenados usan el mismo modelo.

 from itertools import islice, chain class QuerySetChain(object): """ Chains multiple subquerysets (possibly of different models) and behaves as one queryset. Supports minimal methods needed for use with django.core.paginator. """ def __init__(self, *subquerysets): self.querysets = subquerysets def count(self): """ Performs a .count() for all subquerysets and returns the number of records as an integer. """ return sum(qs.count() for qs in self.querysets) def _clone(self): "Returns a clone of this queryset chain" return self.__class__(*self.querysets) def _all(self): "Iterates records in all subquerysets" return chain(*self.querysets) def __getitem__(self, ndx): """ Retrieves an item or slice from the chained set of results from all subquerysets. """ if type(ndx) is slice: return list(islice(self._all(), ndx.start, ndx.stop, ndx.step or 1)) else: return islice(self._all(), ndx, ndx+1).next() 

En su ejemplo, el uso sería:

 pages = Page.objects.filter(Q(title__icontains=cleaned_search_term) | Q(body__icontains=cleaned_search_term)) articles = Article.objects.filter(Q(title__icontains=cleaned_search_term) | Q(body__icontains=cleaned_search_term) | Q(tags__icontains=cleaned_search_term)) posts = Post.objects.filter(Q(title__icontains=cleaned_search_term) | Q(body__icontains=cleaned_search_term) | Q(tags__icontains=cleaned_search_term)) matches = QuerySetChain(pages, articles, posts) 

Luego usa matches con el paginador como lo result_list con la result_list en tu ejemplo.

El módulo itertools se introdujo en Python 2.3, por lo que debería estar disponible en todas las versiones de Python en las que se ejecuta Django.

Relacionado, para mezclar querysets del mismo modelo, o para campos similares de algunos modelos. Comenzando con Django 1.11 también está disponible un método qs.union() :

union()

 union(*other_qs, all=False) 

Nuevo en Django 1.11 . Utiliza el operador UNION de SQL para combinar los resultados de dos o más QuerySets. Por ejemplo:

 >>> qs1.union(qs2, qs3) 

El operador de UNION selecciona solo valores distintos por defecto. Para permitir valores duplicados, use el argumento all = True.

union (), intersection () y difference () devuelven instancias de modelo del tipo del primer QuerySet incluso si los argumentos son QuerySets de otros modelos. Pasar diferentes modelos funciona siempre que la lista SELECT sea la misma en todos los QuerySets (al menos los tipos, los nombres no importan mientras los tipos estén en el mismo orden).

Además, solo LIMIT, OFFSET y ORDER BY (es decir, slicing y order_by ()) están permitidos en QuerySet resultante. Además, las bases de datos imponen restricciones sobre qué operaciones están permitidas en las consultas combinadas. Por ejemplo, la mayoría de las bases de datos no permiten LIMIT o OFFSET en las consultas combinadas.

https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union

La gran desventaja de su enfoque actual es su ineficacia con grandes conjuntos de resultados de búsqueda, ya que tiene que desplegar todo el conjunto de resultados de la base de datos cada vez, aunque solo tenga la intención de mostrar una página de resultados.

Para poder desplegar solo los objetos que realmente necesita de la base de datos, debe usar la paginación en un QuerySet, no en una lista. Si hace esto, Django realmente corta el QuerySet antes de que se ejecute la consulta, por lo que la consulta SQL usará OFFSET y LIMIT para obtener solo los registros que realmente mostrará. Pero no puede hacer esto a menos que pueda meter su búsqueda en una sola consulta de alguna manera.

Dado que los tres modelos tienen campos de título y cuerpo, ¿por qué no utilizar la herencia del modelo ? Simplemente haga que los tres modelos hereden de un ancestro común que tenga título y cuerpo, y realice la búsqueda como una única consulta en el modelo antepasado.

En caso de que quiera encadenar una gran cantidad de conjuntos de consulta, intente esto:

 from itertools import chain result = list(chain(*docs)) 

donde: docs es una lista de conjuntos de consulta

 DATE_FIELD_MAPPING = { Model1: 'date', Model2: 'pubdate', } def my_key_func(obj): return getattr(obj, DATE_FIELD_MAPPING[type(obj)]) And then sorted(chain(Model1.objects.all(), Model2.objects.all()), key=my_key_func) 

Citado de https://groups.google.com/forum/#!topic/django-users/6wUNuJa4jVw . Ver Alex Gaynor

Parece que t_rybik ha creado una solución integral en http://www.djangosnippets.org/snippets/1933/

Para buscar es mejor usar soluciones específicas como Haystack , es muy flexible.

he aquí una idea … simplemente despliegue una página completa de resultados de cada uno de los tres y luego elimine los 20 menos útiles … esto elimina los grandes conjuntos de consulta y de esa manera solo sacrifica un poco de rendimiento en lugar de un montón

Requisitos: Django==2.0.2 , django-querysetsequence==0.8

En caso de que desee combinar los conjuntos de querysets y seguir querysets con un QuerySet , es posible que desee verificar django-queryset-sequence .

Pero una nota al respecto. Solo toma dos querysets como argumento. Pero con python reduce , siempre puedes aplicarlo a múltiples queryset s.

 from functools import reduce from queryset_sequence import QuerySetSequence combined_queryset = reduce(QuerySetSequence, list_of_queryset) 

Y eso es. A continuación se muestra una situación con la que me encontré y cómo django-queryset-sequence list comprehension , reduce y django-queryset-sequence

 from functools import reduce from django.shortcuts import render from queryset_sequence import QuerySetSequence class People(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) mentor = models.ForeignKey('self', null=True, on_delete=models.SET_NULL, related_name='my_mentees') class Book(models.Model): name = models.CharField(max_length=20) owner = models.ForeignKey(Student, on_delete=models.CASCADE) # as a mentor, I want to see all the books owned by all my mentees in one view. def mentee_books(request): template = "my_mentee_books.html" mentor = People.objects.get(user=request.user) my_mentees = mentor.my_mentees.all() # returns QuerySet of all my mentees mentee_books = reduce(QuerySetSequence, [each.book_set.all() for each in my_mentees]) return render(request, template, {'mentee_books' : mentee_books})