문제

나는 내가 구축하는 Django 사이트를 검색하려고 노력하고 있으며, 검색에서 나는 3 가지 모델로 검색하고 있습니다. 검색 결과 목록에서 Pagination을 얻으려면 Generic 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 또는 유사합니다.

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

관련, 동일한 모델의 쿼리 세트를 혼합하거나 몇 가지 모델의 유사한 필드에 대해 장고 1.11qs.union() 방법 이용 가능 :

union()

union(*other_qs, all=False)

Django의 새로운 1.11. SQL의 Union 연산자를 사용하여 두 개 이상의 쿼리 세트의 결과를 결합합니다. 예를 들어:

>>> qs1.union(qs2, qs3)

Union Operator는 기본적으로 별개의 값 만 선택합니다. 중복 값을 허용하려면 All = True 인수를 사용하십시오.

인수가 다른 모델의 쿼리 세트 인 경우에도 Union (), intersection () 및 차이 () 첫 번째 쿼리 세트 유형의 리턴 모델 인스턴스. 다른 모델을 전달하는 것은 선택 목록이 모든 쿼리 세트에서 동일하다면 작동합니다 (적어도 유형은 동일한 순서의 유형만큼 길은 이름이 중요하지 않음).

또한 결과 쿼리 세트에서 (예 : 슬라이싱 및 Order_By ())에 의한 제한, 오프셋 및 주문 만 허용됩니다. 더 나아가, 데이터베이스는 결합 된 쿼리에서 허용되는 작업에 대한 제한 제한 사항입니다. 예를 들어, 대부분의 데이터베이스는 결합 된 쿼리에서 제한 또는 오프셋을 허용하지 않습니다.

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

당신은 사용할 수 있습니다 QuerySetChain 아래 수업. Django의 페이지 진전증과 함께 사용할 때는 데이터베이스 만 누르면됩니다. COUNT(*) 모든 쿼리 세트에 대한 쿼리 SELECT() 현재 페이지에 레코드가 표시된 쿼리 세트에 대해서만 쿼리합니다.

지정해야합니다 template_name= A를 사용하는 경우 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 버전에서 사용할 수 있어야합니다.

현재 접근 방식의 큰 단점은 큰 검색 결과 세트와의 비효율성입니다. 결과의 한 페이지 만 표시하더라도 매번 데이터베이스에서 전체 결과 세트를 끌어 내야하므로 매번 데이터베이스에서 전체 결과 세트를 끌어 내야합니다.

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

세 가지 모델 모두 제목과 바디 필드가 있다는 점을 감안할 때 모델 상속? 제목과 본문이있는 공통 조상에서 세 가지 모델을 모두 상속 받고 조상 모델에서 단일 쿼리로 검색을 수행하십시오.

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

from itertools import chain
result = list(chain(*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

여기에 아이디어가 있습니다 ... 세 가지 각각의 결과 한 페이지를 아래로 내린 다음 20 개의 가장 유용한 20 개를 버립니다 ... 이것은 큰 쿼리 세트를 제거하고 그렇게하면 많은 대신에 약간의 성능 만 희생합니다.

요구 사항 : Django==2.0.2, django-querysetsequence==0.8

결합하고 싶은 경우 querysets 그리고 여전히 a QuerySet, 당신은 체크 아웃하고 싶을 수도 있습니다 Django-Queryset-Sequence.

그러나 그것에 대한 하나의 메모. 두 개만 걸립니다 querysets 논쟁으로. 그러나 파이썬과 함께 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})

이것은 두 가지 방법으로 달성 할 수 있습니다.

이 작업을 수행하는 첫 번째 방법

QuerySet에는 Union Operator를 사용하십시오 | 두 개의 쿼리 세트를 결합합니다. 두 쿼리 세트가 동일한 모델 / 단일 모델에 속한 경우 Union 연산자를 사용하여 쿼리 세트를 결합 할 수 있습니다.

예를 들어

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

이것을하는 두 번째 방법

두 쿼리 세트 사이의 결합 작업을 달성하는 또 다른 방법은 사용하는 것입니다. Itertools 체인 기능.

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

이 재귀 함수는 쿼리 세트 배열을 하나의 쿼리 세트로 연결합니다.

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