내가 구축하고있는 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 에서 인용했습니다 . 알렉스 게이너 참조