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.
Parece que t_rybik ha creado una solución integral en http://www.djangosnippets.org/snippets/1933/
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})
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 en https://groups.google.com/forum/#!topic/django-users/6wUNuJa4jVw . Ver a Alex Gaynor