Django보기에서 2 개 이상의 쿼리 세트를 결합하는 방법은 무엇입니까?
-
08-07-2019 - |
문제
나는 내가 구축하는 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.11 ㅏ qs.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