Django: Holen Sie sich ein Objekt aus der Datenbank oder 'Keine', wenn nichts übereinstimmt

101

Gibt es eine Django-Funktion, mit der ich ein Objekt aus der Datenbank abrufen kann, oder Keine, wenn nichts übereinstimmt?

Im Moment benutze ich so etwas wie:

foo = Foo.objects.filter(bar=baz)
foo = len(foo) > 0 and foo.get() or None

Aber das ist nicht sehr klar und es ist chaotisch, überall zu haben.

David Wolever
quelle
2
Sie wissen, dass Sie nur foo = foo [0] verwenden können, wenn foo sonst keine
Visgean Skeloru
2
Python hat einen ternären Operator, Sie müssen keine booleschen Operatoren verwenden. Auch len(foo)ist schlecht : " Hinweis: Verwenden Sie len () nicht für QuerySets, wenn Sie nur die Anzahl der Datensätze in der Gruppe bestimmen möchten. Es ist viel effizienter, eine Zählung auf Datenbankebene mithilfe von SQLs SELECT COUNT zu verarbeiten (), und Django bietet genau aus diesem Grund eine count () -Methode. " Umgeschrieben:foo = foo[0] if foo.exists() else None
mustafa.0x
1
@ mustafa.0x Ich denke nicht, dass das hier gilt. Wenn Sie hier nach der Anzahl suchen, wird eine weitere Abfrage ausgeführt. Dann hätten wir erneut eine Abfrage, um die tatsächlichen Daten zu erhalten. Dies ist ein Fall, in dem das Filtern und anschließende Zählen sinnvoller wäre ... aber nicht sinnvoller als das Filtern und Aufrufen first(): P
MicronXD

Antworten:

88

In Django 1.6 können Sie die first()Queryset-Methode verwenden. Es gibt das erste Objekt zurück, das mit dem Abfragesatz übereinstimmt, oder None, wenn kein übereinstimmendes Objekt vorhanden ist.

Verwendung:

p = Article.objects.order_by('title', 'pub_date').first()
Cesar Canassa
quelle
19
Dies ist keine gute Antwort. Wenn mehrere Objekte gefunden werden, MultipleObjectsReturned()wird dies nicht ausgelöst, wie hier dokumentiert . Sie können einige bessere Antworten finden hier
sleepycal
25
@sleepycal Ich bin anderer Meinung. Das first()funktioniert genau so, wie es angenommen wird. Es gibt das erste Objekt im Abfrageergebnis zurück und kümmert sich nicht darum, ob mehrere Ergebnisse gefunden werden. Wenn Sie nach mehreren zurückgegebenen Objekten suchen müssen, sollten Sie .get()stattdessen verwenden.
Cesar Canassa
7
[bearbeitet] Wie der OP sagte, .get()ist nicht für seine Bedürfnisse geeignet. Die richtige Antwort auf diese Frage finden Sie unten bei @kaapstorm und ist eindeutig die geeignetere Antwort. Missbrauch auf filter()diese Weise kann zu unerwarteten Konsequenzen führen, was OP wahrscheinlich nicht
erkannt hat
1
@sleepycal Welche unbeabsichtigten Konsequenzen ergeben sich aus diesem Ansatz?
HorseloverFat
12
Wenn Ihre Datenbankeinschränkungen die Eindeutigkeit zwischen Objekten nicht korrekt verwalten, können Sie Randfälle treffen, in denen Ihre Logik erwartet, dass nur ein einziges Objekt vorhanden ist (das von first () ausgewählt wird), in Wirklichkeit jedoch mehrere Objekte vorhanden sind. Wenn Sie get () anstelle von first () verwenden, erhalten Sie durch Erhöhen eine zusätzliche Schutzschicht MultipleObjectsReturned(). Wenn nicht erwartet wird, dass das zurückgegebene Ergebnis mehrere Objekte zurückgibt, sollte es nicht als solches behandelt werden. Es gab eine lange Debatte darüber hier
schläfrig
139

Es gibt zwei Möglichkeiten, dies zu tun.

try:
    foo = Foo.objects.get(bar=baz)
except model.DoesNotExist:
    foo = None

Oder Sie können einen Wrapper verwenden:

def get_or_none(model, *args, **kwargs):
    try:
        return model.objects.get(*args, **kwargs)
    except model.DoesNotExist:
        return None

Nennen Sie es so

foo = get_or_none(Foo, baz=bar)
Nick Craig-Wood
quelle
8
Ich mag die Tatsache nicht, dass Ausnahmen für die Funktionalität verwendet werden - dies ist in den meisten Sprachen eine schlechte Praxis. Wie ist das in Python?
PCV
18
So funktioniert die Python-Iteration (löst eine StopIteration aus), und das ist ein zentraler Bestandteil der Sprache. Ich bin auch kein großer Fan der Try / Except-Funktionalität, aber sie scheint als Pythonic zu gelten.
Matt Luongo
4
@pcv: Es gibt keine verlässliche konventionelle Weisheit über "die meisten" Sprachen. Ich nehme an, Sie denken über eine bestimmte Sprache nach, die Sie gerade verwenden, aber seien Sie vorsichtig mit solchen Quantifizierern. Ausnahmen können so langsam (oder "schnell genug") sein wie alles in Python. Zurück zur Frage - es ist ein Mangel des Frameworks, dass sie querysetvor 1.6 keine Methode dafür bereitgestellt haben! Aber dieses Konstrukt ist einfach ungeschickt. Es ist nicht "böse", weil es ein Tutorial-Autor Ihrer Lieblingssprache gesagt hat. Versuchen Sie es mit Google: "Bitte um Vergebung anstatt um Erlaubnis".
Tomasz Gandor
@TomaszGandor: Ja, es ist ungeschickt und schwer zu lesen, was auch immer Ihr Lieblingssprachen-Tutorial sagt. Wenn ich eine Ausnahme sehe, nehme ich an, dass etwas passiert, das nicht dem Standard entspricht. Lesbarkeit zählt. Aber ich stimme zu, wenn es keine andere vernünftige Option gibt, sollten Sie sich dafür entscheiden und nichts Schlimmes wird passieren.
PCV
@pcv Die getMethode soll nicht zurückkehren None, daher ist eine Ausnahme sinnvoll. Sie sollten es in Fällen verwenden, in denen Sie sicher sind, dass das Objekt dort sein wird / das Objekt "dort sein soll". Auch die Verwendung solcher Ausnahmen wird als "in Ordnung" angesehen, da dies sinnvoll ist. Sie möchten einen Vertrag getmit einem anderen Vertrag, also fangen Sie die Ausnahme ab und unterdrücken sie. Dies ist die Änderung, nach der Sie suchen.
Dan Passaro
83

Um der Antwort von sorki einen Beispielcode hinzuzufügen (ich würde dies als Kommentar hinzufügen, aber dies ist mein erster Beitrag, und ich habe nicht genug Ruf, um Kommentare zu hinterlassen), habe ich einen benutzerdefinierten Manager get_or_none wie folgt implementiert:

from django.db import models

class GetOrNoneManager(models.Manager):
    """Adds get_or_none method to objects
    """
    def get_or_none(self, **kwargs):
        try:
            return self.get(**kwargs)
        except self.model.DoesNotExist:
            return None

class Person(models.Model):
    name = models.CharField(max_length=255)
    objects = GetOrNoneManager()

Und jetzt kann ich das machen:

bob_or_none = Person.objects.get_or_none(name='Bob')
Kaapstorm
quelle
Das ist sehr elegant.
NotSimon
13

Sie können auch versuchen, Django ärgerlich zu verwenden (es hat andere nützliche Funktionen!)

Installieren Sie es mit:

pip install django-annoying

from annoying.functions import get_object_or_None
get_object_or_None(Foo, bar=baz)
llazzaro
quelle
9

Geben Sie Foo seinen benutzerdefinierten Manager . Es ist ziemlich einfach - setzen Sie Ihren Code einfach im benutzerdefinierten Manager in Funktion, legen Sie den benutzerdefinierten Manager in Ihrem Modell fest und rufen Sie ihn mit aufFoo.objects.your_new_func(...) .

Wenn Sie eine generische Funktion benötigen (um sie auf jedem Modell zu verwenden, nicht nur mit dem benutzerdefinierten Manager), schreiben Sie Ihre eigene und platzieren Sie sie irgendwo auf Ihrem Python-Pfad und importieren Sie sie, nicht mehr chaotisch.

Sorki
quelle
4

Unabhängig davon, ob Sie dies über einen Manager oder eine generische Funktion tun, möchten Sie möglicherweise auch 'MultipleObjectsReturned' in der TRY-Anweisung abfangen, da die Funktion get () dies auslöst, wenn Ihre kwargs mehr als ein Objekt abrufen.

Aufbauend auf der generischen Funktion:

def get_unique_or_none(model, *args, **kwargs):
    try:
        return model.objects.get(*args, **kwargs)
    except (model.DoesNotExist, model.MultipleObjectsReturned), err:
        return None

und im Manager:

class GetUniqueOrNoneManager(models.Manager):
    """Adds get_unique_or_none method to objects
    """
    def get_unique_or_none(self, *args, **kwargs):
        try:
            return self.get(*args, **kwargs)
        except (self.model.DoesNotExist, self.model.MultipleObjectsReturned), err:
            return None
Emispowder
quelle
Dies scheint die besten Punkte der anderen Antworten zu erfassen. Netter :)
Edward Newell
@emispowder, was ist, wenn ich None nicht zurückgeben möchte, möchte ich eine Warnmeldung wie "Keine übereinstimmenden Daten" zurückgeben, die auf der GLEICHEN Seite angezeigt wird?
Héléna
0

Hier ist eine Variation der Hilfsfunktion, mit der Sie optional eine QuerySetInstanz übergeben können, falls Sie das eindeutige Objekt (falls vorhanden) aus einem anderen Abfragesatz als dem Objektabfragesatz des Modells allabrufen möchten (z. B. aus einer Teilmenge von untergeordneten Elementen, die zu a gehören übergeordnete Instanz):

def get_unique_or_none(model, queryset=None, *args, **kwargs):
    """
        Performs the query on the specified `queryset`
        (defaulting to the `all` queryset of the `model`'s default manager)
        and returns the unique object matching the given
        keyword arguments.  Returns `None` if no match is found.
        Throws a `model.MultipleObjectsReturned` exception
        if more than one match is found.
    """
    if queryset is None:
        queryset = model.objects.all()
    try:
        return queryset.get(*args, **kwargs)
    except model.DoesNotExist:
        return None

Dies kann auf zwei Arten verwendet werden, z.

  1. obj = get_unique_or_none(Model, *args, **kwargs) wie zuvor besprochen
  2. obj = get_unique_or_none(Model, parent.children, *args, **kwargs)
Gary
quelle
-1

Ich denke, dass Sie in den meisten Fällen nur Folgendes verwenden können:

foo, created = Foo.objects.get_or_create(bar=baz)

Nur wenn es nicht kritisch ist, dass ein neuer Eintrag in die Foo-Tabelle eingefügt wird (andere Spalten haben die Werte Keine / Standard).

TomerP
quelle