761

私が構築している Django サイトの検索を構築しようとしています。その検索では、3 つの異なるモデルで検索しています。また、検索結果リストのページネーションを取得するには、一般的な object_list ビューを使用して結果を表示したいと考えています。しかし、そのためには、3 つのクエリセットを 1 つにマージする必要があります。

どうやってやるの?私はこれを試しました:

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

しかし、これはうまくいきません。汎用ビューでそのリストを使用しようとすると、エラーが発生します。リストにクローン属性がありません。

page_listarticle_listおよびの 3 つのリストをマージするにはどうすればよいpost_listですか?

4

14 に答える 14

1169

クエリセットをリストに連結するのが最も簡単な方法です。とにかくすべてのクエリセットでデータベースがヒットする場合 (たとえば、結果を並べ替える必要があるため)、これ以上のコストは追加されません。

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

を使用すると、C で実装されitertools.chainているため、各リストをループして要素を 1 つずつ追加するよりも高速ですitertools。また、連結する前に各クエリセットをリストに変換するよりも消費メモリが少なくなります。

結果のリストを日付などで並べ替えることができるようになりました (別の回答に対する hasen j のコメントで要求されているように)。このsorted()関数は便利にジェネレーターを受け取り、リストを返します。

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

Python 2.4 以降を使用している場合attrgetterは、ラムダの代わりに使用できます。より高速であるという記事を読んだことを覚えていますが、100 万個のアイテム リストの場合、顕著な速度の違いは見られませんでした。

from operator import attrgetter
result_list = sorted(
    chain(page_list, article_list, post_list),
    key=attrgetter('date_created'))
于 2009-01-12T08:00:46.823 に答える
537

これを試して:

matches = pages | articles | posts

クエリセットのすべての機能を保持します。これは、必要に応じて便利ですorder_by

注意:これは、2 つの異なるモデルのクエリセットでは機能しません。

于 2009-04-28T05:48:33.827 に答える
83

QuerySetChain以下のクラスを使用できます。Django のページネーターで使用する場合、COUNT(*)すべてのクエリセットのクエリとSELECT()、現在のページにレコードが表示されているクエリセットのみのクエリでデータベースにヒットする必要があります。

連鎖クエリセットがすべて同じモデルを使用している場合でも、ジェネリック ビューでtemplate_name=を使用するかどうかを指定する必要があることに注意してください。QuerySetChain

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

あなたの例では、使用法は次のようになります。

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)

次に、例でmatches使用したように、ページネーターで使用result_listします。

このitertoolsモジュールは Python 2.3 で導入されたので、Django が実行されているすべての Python バージョンで利用できるはずです。

于 2009-01-11T09:51:23.013 に答える
37

多くのクエリセットを連鎖させたい場合は、これを試してください:

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

ここで: docs はクエリセットのリストです

于 2012-11-26T21:42:21.987 に答える
30

現在のアプローチの大きな欠点は、1 ページの結果のみを表示するつもりであっても、毎回データベースから結果セット全体を取得する必要があるため、大規模な検索結果セットでは非効率であることです。

データベースから実際に必要なオブジェクトのみを取得するには、リストではなくクエリセットでページネーションを使用する必要があります。これを行うと、Django はクエリが実行される前に QuerySet を実際にスライスするため、SQL クエリは OFFSET と LIMIT を使用して、実際に表示するレコードのみを取得します。しかし、どうにかして検索を 1 つのクエリに詰め込むことができない限り、これを行うことはできません。

3 つのモデルすべてに title フィールドと body フィールドがあることを考えると、モデルの継承を使用しない理由はありません。タイトルと本文を持つ共通の祖先から 3 つのモデルすべてを継承し、祖先モデルで単一のクエリとして検索を実行するだけです。

于 2009-01-10T22:43:25.420 に答える
9

要件: Django==2.0.2django-querysetsequence==0.8

結合querysetsしてまだ を出力したい場合は、 django-queryset-sequenceQuerySetをチェックアウトすることをお勧めします。

しかし、それについて1つ注意してください。querysets引数として2 つしか必要としません。しかし、pythonreduceを使用すると、いつでも複数の に適用できますqueryset

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)

以上です。以下は、私が遭遇した状況と私がどのように採用したかですlist comprehensionreducedjango-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})
于 2018-02-12T12:49:46.787 に答える
6

ここにアイデアがあります... 3 つのそれぞれから結果の 1 ページ全体を取り出してから、20 個の最も役に立たないものを捨てるだけです... これにより、大きなクエリセットが排除され、多くの代わりに少しのパフォーマンスを犠牲にするだけです。

于 2009-01-13T17:12:01.033 に答える