質問

私が構築している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_list article_list 、および post_list の3つのリストをマージする方法は誰でも知っていますか?

役に立ちましたか?

解決

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

from itertools import chain
result_list = list(chain(page_list, article_list, post_list))
itertools はCで実装されているため、 itertools.chain を使用すると、各リストをループして要素を1つずつ追加するよりも高速になります。また、各クエリセットを変換するよりも少ないメモリを消費します連結する前にリストに入れます。

今、結果のリストをソートすることが可能です。日付で(別の回答に対する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'))

他のヒント

これを試してください:

matches = pages | articles | posts

クエリセットのすべての機能を保持しているので、 order_by などにしたい場合に便利です。

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

関連、同じモデルのクエリセットを混合するため、またはいくつかのモデルの類似フィールドの場合、 Django 1.11 から qs.union()メソッドも利用できます:

  

union()

union(*other_qs, all=False)
     

Django 1.11の新機能。 SQLのUNION演算子を使用して、2つ以上のクエリセットの結果を結合します。例:

>>> qs1.union(qs2, qs3)
     

UNION演算子は、デフォルトで個別の値のみを選択します。重複する値を許可するには、all = Trueを使用します   引数。

     

union()、intersection()、difference()は、   引数が次のQuerySetである場合でも、最初のQuerySetのタイプ   他のモデル。異なるモデルを渡すことは、SELECT   リストはすべてのQuerySetsで同じです(少なくともタイプ、名前はありません’ t   型が同じ順序である限り重要です。

     

さらに、LIMIT、OFFSET、ORDER BYのみ(つまり、スライスと   order_by())は、結果のQuerySetで許可されます。さらに、データベース   組み合わせで許可される操作に制限を設ける   たとえば、ほとんどのデータベースはLIMITまたはOFFSETを許可しません   複合クエリ。

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

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

一般的なビューで QuerySetChain を使用する場合は、チェーンされたクエリセットがすべて同じモデルを使用している場合でも、 template_name = を指定する必要があることに注意してください。

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)

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

itertools モジュールはPython 2.3で導入されたため、Djangoが実行されるすべてのPythonバージョンで使用可能になります。

現在のアプローチの大きな欠点は、検索結果セットが1ページしか表示されない場合でも、毎回データベースから結果セット全体をプルダウンする必要があるため、検索結果セットが大きいことの非効率性です。

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

3つすべてのモデルにタイトルフィールドと本文フィールドがあることを考えると、モデルの継承? 3つのモデルすべてに、タイトルと本文を持つ共通の祖先を継承させ、祖先モデルで単一のクエリとして検索を実行するだけです。

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

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

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

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)

https://groups.google.com/forum/#!topic/djangoから引用-users / 6wUNuJa4jVw Alex Gaynor を参照してください

ここにアイデアがあります... 3つのそれぞれから結果の完全なページを1つプルダウンし、20の最も有用でないものを捨てます...これにより、大きなクエリセットがなくなり、その代わりに、パフォーマンスを少し犠牲にするだけです。たくさん

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

querysets を組み合わせて、引き続き QuerySet を使用したい場合は、 django-queryset-sequence

しかし、それについての1つのメモ。引数として2つの querysets のみを取ります。ただし、Python reduce を使用すると、いつでも複数の queryset に適用できます。

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)

これで終わりです。以下は、私が遭遇した状況と、 list comprehension reduce 、および 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})

これは、2つの方法のいずれかで実現できます。

これを行う最初の方法

クエリセット | にユニオン演算子を使用して、2つのクエリセットを結合します。両方のクエリセットが同じモデル/単一モデルに属している場合、ユニオン演算子を使用してクエリセットを結合することができます。

インスタンス用

pagelist1 = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
pagelist2 = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
combined_list = pagelist1 | pagelist2 # this would take union of two querysets

これを行う2番目の方法

2つのクエリセット間で結合操作を実現するもう1つの方法は、 itertools チェーン関数を使用することです。

from itertools import chain
combined_results = list(chain(pagelist1, pagelist2))

この再帰関数は、クエリセットの配列を1つのクエリセットに連結します。

def merge_query(ar):
    if len(ar) ==0:
        return [ar]
    while len(ar)>1:
        tmp=ar[0] | ar[1]
        ar[0]=tmp
        ar.pop(1)
        return ar
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top