Jak połączyć 2 lub więcej querysetów w widoku Django?

Próbuję zbudować wyszukiwarkę dla strony Django, którą buduję, a w poszukiwaniu Szukam w 3 różnych modelach. Aby uzyskać paginację na liście wyników wyszukiwania, chciałbym użyć ogólnego widoku object_list do wyświetlania wyników. Ale aby to zrobić, muszę połączyć 3 querysets w jeden.

Jak mogę to zrobić? Próbowałem tego:
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")

Ale to nie działa dostaję błąd, gdy próbuję użyć tej listy w widoku ogólnym. Na liście brakuje klona atrybut.

Ktoś wie jak Mogę połączyć trzy listy, page_list, article_list i post_list?

Author: Bill Armstrong, 2009-01-10

11 answers

Łączenie zestawów zapytań w Listę jest najprostszym podejściem. Jeśli baza danych i tak zostanie trafiona dla wszystkich zestawów zapytań (np. ponieważ wynik musi być sortowany), nie spowoduje to dodatkowych kosztów.

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

Użycie itertools.chain jest szybsze niż zapętlanie każdej listy i dodawanie elementów jeden po drugim, ponieważ itertools jest zaimplementowane w C. zużywa również mniej pamięci niż konwersja każdego zestawu zapytań do listy przed połączeniem.

Teraz można posortować listę wynikową np. według daty (jako Prośba w komentarzu hasena j do innej odpowiedzi). Funkcjasorted() wygodnie przyjmuje generator i zwraca listę:

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

Jeśli używasz Pythona 2.4 lub nowszego, możesz użyć attrgetter zamiast lambda. Pamiętam, że czytałem o tym, że jest szybszy, ale nie zauważyłem zauważalnej różnicy prędkości na liście milionów przedmiotów.

from operator import attrgetter
result_list = sorted(
    chain(page_list, article_list, post_list),
    key=attrgetter('date_created'))
 900
Author: akaihola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-12 08:00:46

Spróbuj tego:

matches = pages | articles | posts

Zachowuje wszystkie funkcje querysets, co jest miłe, jeśli chcesz order_by lub podobne.

UPS, zauważ, że to nie działa na zestawach zapytań z dwóch różnych modeli...

 393
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-01 02:45:19

Możesz użyć klasy QuerySetChain poniżej. Kiedy używasz go z Paginatorem Django, powinien trafić do bazy danych tylko zapytaniami COUNT(*) dla wszystkich zestawów zapytań i SELECT() tylko dla tych zestawów zapytań, których rekordy są wyświetlane na bieżącej stronie.

Zauważ, że musisz określić template_name= Jeśli używasz QuerySetChain z ogólnymi widokami, nawet jeśli łańcuchowe zestawy zapytań używają tego samego modelu.

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()

W twoim przykładzie użycie będzie następujące:

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)

Następnie użyj {[7] } z paginatorem jak użyłeś result_list w swoim przykładzie.

Moduł itertools został wprowadzony w Pythonie 2.3, więc powinien być dostępny we wszystkich wersjach Pythona, na których działa Django.

 67
Author: akaihola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-14 12:57:22

Related, do mieszania zestawów zapytań z tego samego modelu lub dla podobnych pól z kilku modeli, zaczynając od Django 1.11 a qs.union() metoda jest również dostępna:

union()

union(*other_qs, all=False)

Nowość w Django 1.11. Używa operatora SQL UNION do łączenia wyników dwóch lub więcej zapytań. Na przykład:

>>> qs1.union(qs2, qs3)

Operator Unii domyślnie wybiera tylko różne wartości. Aby zezwolić na zduplikowane wartości, użyj all = True kłótnia.

Union (), intersection () i difference () zwracają instancje modelu Typ pierwszego QuerySet nawet jeśli argumenty są QuerySets of inne modele. Przechodzenie różnych modeli działa tak długo, jak wybór lista jest taka sama we wszystkich zapytaniach (przynajmniej typy, nazwy nie matter tak długo, jak typy w tej samej kolejności).

Dodatkowo, tylko LIMIT, OFFSET i ORDER BY (czyli krojenie i order_by ()) są dozwolone w wynikowym zestawie zapytań. Dalej, bazy danych wprowadzić ograniczenia co do tego, jakie operacje są dozwolone w połączeniu zapytania. na przykład, większość baz danych nie zezwala na LIMIT lub OFFSET w połączone zapytania.

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

 67
Author: Udi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-25 07:47:47

Dużym minusem Twojego obecnego podejścia jest jego nieefektywność z dużymi zestawami wyników wyszukiwania, ponieważ za każdym razem musisz wyciągnąć cały zestaw wyników z bazy danych, nawet jeśli zamierzasz wyświetlić tylko jedną stronę wyników.

Aby ściągnąć z bazy tylko te obiekty, których faktycznie potrzebujesz, musisz użyć paginacji w Querysecie, a nie liście. Jeśli to zrobisz, Django faktycznie rozcina zestaw zapytań przed wykonaniem zapytania, więc zapytanie SQL użyje OFFSET i Ogranicz, aby uzyskać tylko te rekordy, które faktycznie wyświetlasz. Ale nie możesz tego zrobić, chyba że możesz jakoś zawęzić swoje wyszukiwanie do jednego zapytania.

Biorąc pod uwagę, że wszystkie trzy modele mają pola title I body, dlaczego nie użyć dziedziczenia modelu ? Wystarczy, że wszystkie trzy modele odziedziczą po wspólnym przodku, który ma tytuł i ciało, i wykonaj wyszukiwanie jako pojedyncze zapytanie dotyczące modelu przodka.

 24
Author: Carl Meyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-10 22:43:25

Jeśli chcesz połączyć wiele zestawów zapytań, spróbuj tego:

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

Gdzie: docs jest listą zapytań

 18
Author: vutran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-26 21:42:21
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)

Cytat z https://groups.google.com/forum/#! topic / django-users / 6wUNuJa4jVw. Zobacz Alex Gaynor

 13
Author: ray6080,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-23 12:42:00

Wygląda na to, że t_rybik stworzył kompleksowe rozwiązanie w http://www.djangosnippets.org/snippets/1933/

 8
Author: akaihola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-21 18:17:21

Do wyszukiwania lepiej zastosować dedykowane rozwiązania jak stóg siana - jest bardzo elastyczny.

 8
Author: minder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-09 08:52:07

Mam pomysł... po prostu wyciągnij jedną pełną stronę wyników z każdego z trzech, a następnie wyrzuć 20 najmniej przydatnych... to eliminuje Duże kwerysety i w ten sposób poświęcasz tylko trochę wydajności zamiast dużo

 4
Author: Jiaaro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-13 17:12:01

Wymagania: Django==2.0.2, django-querysetsequence==0.8

Jeśli chcesz połączyć querysets i nadal wyjść z QuerySet, możesz sprawdzić django-queryset-sequence.

Ale jedna uwaga o tym. Jako argument potrzeba tylko dwóch querysets. Ale z Pythonem reduce zawsze możesz zastosować go do wielu queryset s.

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)

I to wszystko. Poniżej sytuacja, na którą wpadłem i jak zatrudniłem list comprehension, reduce i 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})
 4
Author: Parousia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-23 17:48:27