select_related querysets queryset query example combine django search django-queryset django-q

querysets - select_related django example



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

Estoy tratando de 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 usar una vista de lista de objetos genérica para mostrar los resultados. Pero para hacer eso tengo que unir 3 querysets 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 clon.

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


Aquí hay una idea ... simplemente despliegue una página completa de resultados de cada uno de los tres y luego deseche los 20 menos útiles ... esto elimina los querysets grandes y de esa manera solo sacrifica un poco de rendimiento en lugar de muchos.


Concatenar los querysets en una lista es el enfoque más simple. Si la base de datos se verá afectada por todos los conjuntos de consultas de todos modos (por ejemplo, porque el resultado debe ordenarse), esto no agregará más costos.

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

Usar itertools.chain es más rápido que itertools.chain cada lista y agregar elementos uno por uno, ya que se implementa en C. También consume menos memoria que la conversión de 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 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á utilizando 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 un millón de artículos.

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


El gran inconveniente de su enfoque actual es su ineficiencia 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 en realidad corta el QuerySet antes de que se ejecute la consulta, por lo que la consulta SQL usará DESPLAZAMIENTO y LÍMITE para obtener solo los registros que realmente mostrará. Pero no puede hacer esto a menos que pueda agrupar 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 usar la herencia del modelo ? Simplemente haga que los tres modelos se hereden de un ancestro común que tiene título y cuerpo, y realice la búsqueda como una sola consulta en el modelo del antepasado.


En caso de que quiera encadenar muchos querysets, intente esto:

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

donde: docs es una lista de consultas


Para buscar, es mejor usar soluciones dedicadas como Haystack , es muy flexible.



Prueba esto:

matches = pages | articles | posts

Retiene todas las funciones de los querysets, lo que está bien si quiere ordenar por by similar.

Vaya, ten en cuenta que esto no funciona en querysets de dos modelos diferentes ...


Puede utilizar la clase QuerySetChain continuación. Cuando se usa con el paginador de Django, solo debe golpear la base de datos con COUNT(*) consultas para todos los querysets y SELECT() 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 todos los conjuntos de consultas 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 use matches con el paginador como usó result_list en su ejemplo.

El módulo de 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 conjuntos de consultas del mismo modelo, o para campos similares de algunos modelos, a partir de 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 UNION selecciona solo valores distintos de forma predeterminada. 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 SELECCIONAR sea la misma en todos los Conjuntos de consultas (al menos los tipos, los nombres no importan mientras los tipos estén en el mismo orden).

Además, solo se permiten LIMIT, OFFSET y ORDER BY (es decir, slicing y order_by ()) en el 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


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

En caso de que desee combinar querysets y aún salir con QuerySet , es posible que desee revisar la django-queryset-sequence .

Pero una nota al respecto. Sólo toma dos querysets como argumento. Pero con Python queryset siempre puede aplicarlo a múltiples queryset .

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 en la que me topé y cómo django-queryset-sequence list comprehension , la 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})