729 votes

Comment combiner 2 ou plusieurs jeux de requête dans une vue Django?

Je suis en train de construire la recherche d'un site Django je suis bâtiment, et dans la recherche je suis à la recherche de 3 modèles différents. Et pour obtenir la pagination de la liste des résultats de recherche, je voudrais utiliser un générique object_list pour afficher les résultats. Mais pour ça, j'ai fusionner les 3 querysets en un seul.

Comment puis-je le faire? J'ai essayé ceci:

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")

Mais cela ne fonctionne pas, j'obtiens une erreur lorsque je tente d'utiliser cette liste dans la vue générique. La liste est manquant le clone de l'attribut.

Quelqu'un sait comment je peux fusionner les trois listes, page_list, article_list et post_list?

1140voto

akaihola Points 10007

La concaténation de la querysets dans une liste est l'approche la plus simple. Si la base de données sera frappé à toutes les querysets de toute façon (par exemple, parce que le résultat doit être triée), ce ne sera pas ajouter des coûts supplémentaires.

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

À l'aide de itertools.chain est plus rapide que la boucle de chaque liste et en ajoutant des éléments un par un, depuis itertools est mis en œuvre dans C. Il consomme moins de mémoire que la conversion de chaque queryset dans une liste avant de la concaténation.

Maintenant, il est possible de trier la liste par exemple par date (comme demandé dans la hasen j commentaire à une autre réponse). L' sorted() fonction idéalement accepte un générateur et renvoie une liste:

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

Si vous êtes à l'aide de Python 2.4 ou version ultérieure, vous pouvez utiliser attrgetter au lieu d'une lambda. Je me souviens de la lecture de ce sujet étant plus rapide, mais je n'ai pas vu une notable différence de vitesse pour un million d'élément de liste.

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

79voto

akaihola Points 10007

Vous pouvez utiliser l' QuerySetChain de la classe ci-dessous. Lors de l'utilisation de Django paginator, il ne devrait frapper la base de données avec COUNT(*) des requêtes pour tous les querysets et SELECT() des requêtes uniquement pour ceux querysets dont les enregistrements sont affichés sur la page.

Notez que vous devez spécifier template_name= si vous utilisez un QuerySetChain générique de points de vue, même si l'enchaînement querysets utilisent tous le même modèle.

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()

Dans votre exemple, l'utilisation serait:

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)

Ensuite, utilisez matches avec le paginator comme vous avez utilisé result_list dans votre exemple.

L' itertools module a été introduit en Python 2.3, il devrait donc être disponible dans toutes les versions de Python Django fonctionne sur.

27voto

Carl Meyer Points 30736

Le gros point noir de votre approche actuelle est son inefficacité avec de grands jeux de résultats de recherche, que vous avez à tirer vers le bas l'ensemble de l'ensemble de résultats de la base de données à chaque fois, même si vous avez l'intention d'afficher une page de résultats.

Afin de ne tirer vers le bas les objets dont vous avez réellement besoin de la base de données, vous devez utiliser la pagination sur un QuerySet, et non une liste. Si vous faites cela, Django en fait des tranches de la QuerySet avant l'exécution de la requête, de sorte que la requête SQL va utiliser l'OFFSET et à la LIMITE pour obtenir uniquement les enregistrements que vous affichera en fait. Mais vous ne pouvez pas faire cela, sauf si vous pouvez fourrer votre recherche en une seule requête en quelque sorte.

Étant donné que tous les trois de vos modèles ont le titre et le corps des champs, pourquoi ne pas utiliser le modèle de l'héritage? Juste tous les trois modèles héritent à partir d'un ancêtre commun qui a le titre et le corps, et d'effectuer la recherche que d'une seule requête sur l'ancêtre du modèle.

16voto

ray6080 Points 130
<pre><code></code><p><a href="https://groups.google.com/forum/#!topic/django-users/6wUNuJa4jVw" rel="nofollow">https://groups.google.com/forum/# ! sujet/django-utilisateurs/6wUNuJa4jVw</a> <strong>Alex Gaynor</strong></p></pre>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X