Frage

Ich versuche, die Suche nach einer Django-Website Ich bin Gebäude und bei der Suche Ich suche in 3 verschiedenen Modellen zu bauen. Und um die Paginierung auf der Ergebnisliste Suche Ich möchte eine generische Object_List Ansicht verwenden, um die Ergebnisse anzuzeigen. Aber das zu tun, dass ich 3 querysets in eine verschmelzen haben.

Wie kann ich das tun? Ich habe dies versucht:

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

Aber das funktioniert nicht, dass ich einen Fehler, wenn ich versuche, diese Liste in der allgemeinen Ansicht zu verwenden. Die Liste fehlt das Klon-Attribut.

Jeder weiß, wie ich die drei Listen zusammenführen kann, page_list, article_list und post_list?

War es hilfreich?

Lösung

die querysets in eine Liste verketten ist der einfachste Ansatz. Wenn die Datenbank für alle querysets getroffen wird sowieso (zum Beispiel, weil das Ergebnis sortiert werden muss), wird dies nicht weiter Kosten hinzu.

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

itertools.chain Verwendung ist schneller als jede Liste Looping und Elemente nacheinander anhängt, da itertools in C implementiert Es verbraucht auch weniger Speicher als jede queryset in eine Liste konvertieren, bevor verketten.

Jetzt ist es möglich, die resultierende Liste zu sortieren, z nach Datum (wie in hasen j Kommentar zu einer anderen Antwort erbeten). Die sorted() Funktion akzeptiert bequem einen Generator und liefert eine Liste:

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

Wenn Sie mit Python 2.4 oder höher können Sie attrgetter anstelle eines Lambda verwenden. Ich erinnere mich, um es schneller zu sein, aber ich habe nicht eine spürbare Geschwindigkeitsdifferenz für eine Million Artikel Liste.

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

Andere Tipps

Versuchen Sie folgendes:

matches = pages | articles | posts

Es behält alle Funktionen des querysets das ist schön, wenn Sie order_by oder ähnliches mögen.

Bitte beachten Sie:. das funktioniert nicht auf querysets von zwei verschiedenen Modellen

Verwandte, für querysets aus dem gleichen Modell, oder für ähnliche Felder von einigen Modellen mischen, beginnend mit Django 1.11 a qs.union() Methode auch verfügbar:

  

union()

union(*other_qs, all=False)
     

Neu in Django 1.11 . Verwendet SQL des UNION-Operator, um die Ergebnisse von zwei oder mehr QuerySets zu kombinieren. Zum Beispiel:

>>> qs1.union(qs2, qs3)
     

Der UNION-Operator wählt nur unterschiedliche Werte voreingestellt. Damit doppelte Werte, verwenden Sie die alle = True   Argument.

     

union (), Kreuzung () und Differenz () return Modell Instanzen   der Typ des ersten QuerySet auch wenn die Argumente QuerySets von   andere Modelle. verschiedene Modelle Passing funktioniert, solange die SELECT   Liste ist das gleiche in allen QuerySets (zumindest die Art, tun die Namen nicht   egal, solange die Typen in der gleichen Reihenfolge).

     

Zusätzlich nur LIMIT, Offset- und ORDER BY (d.h. Schneiden und   order_by ()) wird auf dem resultierenden QuerySet erlaubt. Ferner Datenbanken   Beschränkungen für welche Operationen werden in der kombinierten erlaubt   Abfragen. Zum Beispiel, die meisten Datenbanken nicht zulassen LIMIT oder OFFSET in   die kombinierten Abfragen.

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

Sie können die QuerySetChain Klasse verwenden unten. Wenn es mit Djangos paginator verwenden, sollte es nur die Datenbank mit COUNT(*) Abfragen schlagen für alle querysets und SELECT() Abfragen nur für die querysets deren Datensätze angezeigt werden auf der aktuellen Seite.

Beachten Sie, dass Sie benötigen template_name= angeben, ob eine QuerySetChain mit generischen Ansichten verwenden, auch wenn die verkettete querysets alle das gleiche Modell verwenden.

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

In Ihrem Beispiel wäre die Nutzung sein:

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)

Dann matches mit dem paginator verwenden, wie Sie result_list in Ihrem Beispiel verwendet wird.

Das itertools Modul wurde in Python 2.3 eingeführt, so sollte es in all Python-Versionen Django läuft auf zur Verfügung.

Der große Nachteil des aktuellen Ansatzes ist seine Ineffizienz mit großen Suchergebnis-Sets, wie Sie das gesamte Ergebnis aus der Datenbank jedes Mal auf Pull-Down haben, auch wenn Sie nur eine Seite Suchergebnisse angezeigt werden möchten.

Um die Objekte nur nach unten ziehen Sie tatsächlich aus der Datenbank benötigen, müssen Sie Paginierung verwenden, um auf einem QuerySet, nicht eine Partei. Wenn Sie dies tun, Scheiben Django tatsächlich die QuerySet, bevor die Abfrage ausgeführt wird, so wird die SQL-Abfrage OFFSET verwenden und LIMIT nur die Datensätze erhalten werden Sie tatsächlich angezeigt werden soll. Aber man kann dies nicht tun, wenn Sie Ihre Suche in einer einzigen Abfrage stopfen irgendwie.

Da alle drei Ihrer Modelle haben Titel und Körperfelder, warum verwenden nicht Modell Vererbung ? habe nur alle drei Modelle von einem gemeinsamen Vorfahren erben, die Titel und Körper, und führen Sie die Suche als eine einzige Abfrage im Vorfahren Modell hat.

Falls Sie eine Menge querysets zu verketten wollen, versuchen Sie dies:

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

Dabei gilt: docs ist eine Liste der querysets

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)

Zitat von https://groups.google.com/forum/#!topic/django -Anwender / 6wUNuJa4jVw . Siehe Alex Gaynor

Hier ist eine Idee ... ziehen nur eine ganze Seite der Ergebnisse aus jeder der drei nach unten und dann die 20 am wenigsten nützlichsten werfen ... dies beseitigt die große querysets und auf diese Weise nur ein wenig Leistung opfern statt viel

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

Falls Sie wollen querysets kombinieren und immer noch mit einem QuerySet kommen, möchten Sie vielleicht django-queryset-Sequenz .

Aber eine Notiz darüber. Es dauert nur zwei querysets wie es Argument ist. Aber mit Python reduce können Sie immer es auf mehr querysets.

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)

Und das ist es. Unten ist eine Situation, die ich lief in und wie ich beschäftigte list comprehension, reduce und 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})

Dies kann entweder durch zwei Wegen erreicht werden.

1. Möglichkeit, dies

zu tun

Mit union Operator für queryset | Vereinigung von zwei queryset zu nehmen. Wenn beide queryset auf gleiches Modell / Einzel Modell gehören, als es möglich ist, querysets zu kombinieren durch Vereinigung Operator.

Für eine Instanz

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

2. Art und Weise dieses

zu tun

Eine andere Möglichkeit, zwischen zwei queryset kombinieren zu erreichen, ist verwenden itertools Kettenfunktion.

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

Diese rekursive Funktion verkettet Array von querysets in ein queryset.

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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top