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


J'essaie de construire la recherche d'un site Django que je construis, et dans la recherche je recherche dans 3 modèles différents. Et pour obtenir la pagination sur la liste des résultats de recherche, je voudrais utiliser une vue générique object_list pour afficher les résultats. Mais pour ce faire, je dois fusionner 3 querysets en un seul.

Comment puis je faire ça? 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 Je reçois une erreur lorsque j'essaie d'utiliser cette liste dans la vue générique. La liste ne contient pas l'attribut clone.

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


520
2018-01-10 19:51


origine


Réponses:


Concaténer les queries en une liste est l'approche la plus simple. Si la base de données est de toute façon touchée pour tous les ensembles de requêtes (par exemple parce que le résultat doit être trié), cela n'ajoutera pas de coût supplémentaire.

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

En utilisant itertools.chain est plus rapide que de boucler chaque liste et ajouter des éléments un par un, itertools est implémenté en C. Il consomme également moins de mémoire que de convertir chaque ensemble de requêtes en liste avant de les concaténer.

Il est maintenant possible de trier la liste résultante, par ex. par date (comme demandé dans le commentaire de hasen j à une autre réponse). le sorted() fonction accepte commodément 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 utilisez Python 2.4 ou version ultérieure, vous pouvez utiliser attrgetter au lieu d'un lambda. Je me souviens avoir lu à ce sujet être plus rapide, mais je n'ai pas vu une différence de vitesse notable pour un million de liste d'articles.

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

886
2018-01-12 08:00



Essaye ça:

matches = pages | articles | posts

Conserve toutes les fonctions des groupes de requêtes, ce qui est bien si vous voulez order_by ou similaire.

Oups, s'il vous plaît noter que cela ne fonctionne pas sur les querysets de deux modèles différents ...


387
2018-04-28 05:48



Vous pouvez utiliser le QuerySetChain classe ci-dessous. Lorsque vous l'utilisez avec le paginateur de Django, il ne devrait frapper la base de données avec COUNT(*) requêtes pour tous les querysets et SELECT() interroge uniquement les sous-ensembles dont les enregistrements sont affichés sur la page en cours.

Notez que vous devez spécifier template_name= si vous utilisez un QuerySetChain avec des vues génériques, même si les sous-ensembles chaînés 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 paginateur comme vous avez utilisé result_list dans votre exemple.

le itertools Le module a été introduit dans Python 2.3, il devrait donc être disponible dans toutes les versions de Python sur lesquelles s'exécute Django.


67
2018-01-11 09:51



Relatif, pour mélanger des ensembles de requêtes du même modèle, ou pour des champs similaires de quelques modèles, à partir de Django 1.11 une qs.union() méthode est également disponible:

union()

union(*other_qs, all=False)

Nouveau dans Django 1.11. Utilise l'opérateur UNION de SQL pour combiner les résultats de deux ou plusieurs QuerySets. Par exemple:

>>> qs1.union(qs2, qs3)

L'opérateur UNION sélectionne uniquement des valeurs distinctes par défaut. Pour autoriser les valeurs en double, utilisez all = True   argument.

union (), intersection () et difference () renvoyer des instances de modèle de   le type du premier QuerySet même si les arguments sont QuerySets de   autres modèles Passer différents modèles fonctionne aussi longtemps que le SELECT   liste est la même dans tous les QuerySets (au moins les types, les noms ne   importe aussi longtemps que les types dans le même ordre).

En outre, seuls les paramètres LIMIT, OFFSET et ORDER BY (c'est-à-dire   order_by ()) sont autorisés sur le QuerySet résultant. Plus loin, des bases   placer des restrictions sur ce que les opérations sont autorisées dans le combiné   requêtes. Par exemple, la plupart des bases de données n'autorisent pas LIMIT ou OFFSET dans   les requêtes combinées.

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


62
2018-02-12 11:23



Le gros inconvénient de votre approche actuelle est son inefficacité avec de grands ensembles de résultats de recherche, car vous devez à chaque fois retirer l'ensemble des résultats de la base de données, même si vous avez seulement l'intention d'afficher une page de résultats.

Pour ne retirer que les objets dont vous avez réellement besoin dans la base de données, vous devez utiliser la pagination sur un QuerySet, pas une liste. Si vous faites cela, Django découpe réellement le QuerySet avant que la requête ne soit exécutée, ainsi la requête SQL utilisera OFFSET et LIMIT pour n'obtenir que les enregistrements que vous allez réellement afficher. Mais vous ne pouvez pas le faire à moins que vous ne puissiez entasser votre recherche dans une seule requête en quelque sorte.

Étant donné que tous les trois de vos modèles ont des champs de titre et de corps, pourquoi ne pas utiliser héritage du modèle? Il suffit que les trois modèles héritent d'un ancêtre commun ayant un titre et un corps, et effectuent la recherche sous forme de requête unique sur le modèle ancêtre.


24
2018-01-10 22:43



Dans le cas où vous voulez enchaîner beaucoup de jeux de requête, essayez ceci:

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

où: docs est une liste de querysets


18
2017-11-26 21:42



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)

Cité de https://groups.google.com/forum/#!topic/django-users/6wUNuJa4jVw. Voir Alex Gaynor 


13
2017-12-23 12:42