[django] 장고 뷰에서 둘 이상의 쿼리 세트를 결합하는 방법은 무엇입니까?

내가 구축하고있는 Django 사이트에 대한 검색을 구축하려고하고 있는데,이 검색에서 3 가지 모델을 검색하고 있습니다. 검색 결과 목록에서 페이지 매김을 얻으려면 일반 object_list보기를 사용하여 결과를 표시하고 싶습니다. 그러나 그렇게하려면 3 개의 쿼리 세트를 하나로 병합해야합니다.

어떻게해야합니까? 나는 이것을 시도했다 :

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?



답변

쿼리 세트를 목록으로 연결하는 것이 가장 간단한 방법입니다. 어쨌든 결과가 정렬되어야하기 때문에 모든 쿼리 세트에 대해 데이터베이스에 도달하면 추가 비용이 발생하지 않습니다.

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

사용하기 itertools.chain때문에, 각 속도에서 반복 및 요소 하나씩 부가보다 itertools또한 연접 전에 각각의 검색어 목록으로 전환보다 적은 메모리 소모 C. 구현된다.

이제 결과 목록을 날짜별로 정렬 할 수 있습니다 (다른 답변에 대한 hasen j의 의견에서 요청한대로). 이 sorted()함수는 생성기를 편리하게 받아들이고 목록을 반환합니다.

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

Python 2.4 이상을 사용 attrgetter하는 경우 람다 대신 사용할 수 있습니다 . 나는 그것이 더 빠르다는 것에 대해 읽은 것을 기억하지만, 백만 개의 항목 목록에서 눈에 띄는 속도 차이는 보지 못했습니다.

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


답변

이 시도:

matches = pages | articles | posts

그것은 당신이 원 order_by하거나 유사한 경우 좋은 쿼리 세트의 모든 기능을 유지합니다 .

참고 : 두 가지 모델의 쿼리 세트에서는 작동하지 않습니다.


답변

Django 1.11 부터는 동일한 모델의 쿼리 집합을 혼합하거나 몇 가지 모델의 유사한 필드를 혼합 하는 데 사용할 수 있는 qs.union()방법 도 있습니다.

union()

union(*other_qs, all=False)

장고 1.11의 새로운 기능 . SQL의 UNION 연산자를 사용하여 둘 이상의 QuerySet 결과를 결합합니다. 예를 들면 다음과 같습니다.

>>> qs1.union(qs2, qs3)

UNION 연산자는 기본적으로 고유 한 값만 선택합니다. 중복 값을 허용하려면 all = True 인수를 사용하십시오.

union (), interaction () 및 difference ()는 인수가 다른 모델의 QuerySet 인 경우에도 첫 번째 QuerySet 유형의 모델 인스턴스를 반환합니다. SELECT 목록이 모든 QuerySet에서 동일하면 다른 모델을 전달할 수 있습니다 (적어도 유형은 동일한 순서의 유형 인 경우 이름이 중요하지 않음).

또한 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의 paginator와 함께 사용하면 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 버전에서 사용할 수 있어야합니다.


답변

현재 접근 방식의 큰 단점은 한 페이지의 결과 만 표시하려는 경우에도 매번 데이터베이스에서 전체 결과 세트를 풀다운해야하기 때문에 검색 결과 세트가 크면 비효율적입니다.

데이터베이스에서 실제로 필요한 객체 만 풀다운하려면 목록이 아닌 QuerySet에서 페이지 매김을 사용해야합니다. 이렇게하면 Django는 실제로 쿼리가 실행되기 전에 QuerySet을 슬라이스하므로 SQL 쿼리는 OFFSET 및 LIMIT를 사용하여 실제로 표시 할 레코드 만 가져옵니다. 그러나 어떻게 든 검색을 단일 쿼리로 구성 할 수 없다면이 작업을 수행 할 수 없습니다.

세 모델 모두에 제목 및 본문 필드가 있으므로 모델 상속을 사용하지 않겠습니까? 세 모델 모두 제목과 본문이있는 공통 조상에서 상속하고 조상 모델에 대한 단일 쿼리로 검색을 수행하십시오.


답변

많은 쿼리 세트를 연결하려면 다음을 시도하십시오.

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

여기서 : 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 에서 인용했습니다 . 알렉스 게이너 참조