Jak uzyskać obiekt, jeśli istnieje, lub Brak, jeśli nie istnieje?

223

Kiedy proszę menedżera modelu o uzyskanie obiektu, podnosi się, DoesNotExistgdy nie ma pasującego obiektu.

go = Content.objects.get(name="baby")

Zamiast tego DoesNotExist, jak mogę gobyć Nonezamiast tego?

TIMEX
źródło

Odpowiedzi:

332

Nie ma „wbudowanego” sposobu, aby to zrobić. Django będzie zgłaszał wyjątek DoesNotExist za każdym razem. Idiomatycznym sposobem radzenia sobie z tym w pythonie jest zawinięcie go w try catch:

try:
    go = SomeModel.objects.get(foo='bar')
except SomeModel.DoesNotExist:
    go = None

To, co zrobiłem, to podklasowanie safe_getmodeli. Menedżer , stwórz podobny kod powyżej i użyj tego menedżera dla moich modeli. W ten sposób można napisać: SomeModel.objects.safe_get(foo='bar').

Arthur Debert
źródło
7
Ładne użycie SomeModel.DoesNotExist zamiast importowania wyjątku.
supermitch
196
To rozwiązanie ma cztery linie. Dla mnie to za dużo. W przypadku django 1.6 możesz użyć SomeModel.objects.filter(foo='bar').first()tej wartości, zwracając pierwsze dopasowanie lub Brak. Nie zawiedzie, jeśli istnieje kilka takich przypadkówqueryset.get()
guettli
9
Myślę, że niewłaściwym sposobem jest nadużywanie wyjątków do obsługi domyślnych przypadków. Tak, „łatwiej prosić o wybaczenie niż o pozwolenie”. Jednak moim zdaniem wyjątek powinien być nadal stosowany.
Konstantin Schubert
8
Jawne jest lepsze niż niejawne. Chyba, że ​​istnieje powód, dla którego warto użyć wydajności filter().first(), myślę, że wyjątek jest najlepszym rozwiązaniem.
christianbundy
6
Używanie first () jest dobrym pomysłem, jeśli nie obchodzi Cię, kiedy jest wiele. W przeciwnym razie to rozwiązanie jest lepsze, ponieważ nadal generuje wyjątek, jeśli nieoczekiwanie znajdziesz wiele obiektów, co zwykle by się zdarzyło w takim przypadku.
rossdavidh
182

Od wersji django 1.6 możesz użyć metody first () w następujący sposób:

Content.objects.filter(name="baby").first()
FeroxTL
źródło
26
W takim przypadku nie występuje błąd, jeśli występuje więcej niż jedno dopasowanie.
Konstantin Schubert
7
„FeroxTL” należy przypisać @ guettli za tę odpowiedź, ponieważ skomentował to w odpowiedzi zaakceptowanej na rok przed opublikowaniem posta.
colm.anseo
7
@colminator Wolę powiedzieć, że guettli powinien dowiedzieć się, że nowa odpowiedź nie należy do komentarza, jeśli chce podnieść swoją reputację w przepełnieniu stosu :) FeroxTL powinien zdobyć punkty za uczynienie czegoś ukrytego komentarzem bardziej przejrzystym jako odpowiedź. Twój komentarz jest wystarczający dla guettli i myślę, że nie powinien być dodawany do odpowiedzi, jeśli taka była twoja sugestia.
Joakim
3
@Joakim Nie mam problemu z opublikowaniem nowej „odpowiedzi” - aby wyrazić uznanie w odpowiednim miejscu :-)
colm.anseo
3
A co z wydajnością tego podejścia w porównaniu do zaakceptowanej odpowiedzi?
MaxCore
36

Z dokumentów django

get()zgłasza DoesNotExistwyjątek, jeśli nie można znaleźć obiektu dla podanych parametrów. Ten wyjątek jest również atrybutem klasy modelu. W DoesNotExist dziedziczy wyjątek oddjango.core.exceptions.ObjectDoesNotExist

Możesz złapać wyjątek i przypisać Nonego.

from django.core.exceptions import ObjectDoesNotExist
try:
    go  = Content.objects.get(name="baby")
except ObjectDoesNotExist:
    go = None
Amarghosh
źródło
33

Możesz w tym celu utworzyć funkcję ogólną.

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

Użyj tego jak poniżej:

go = get_or_none(Content,name="baby")

go będzie Brak, jeśli żaden wpis pasujący inny nie zwróci wpisu Treść.

Uwaga: Zgłasza wyjątek MultipleObjectsReturned, jeśli zwrócono więcej niż jeden wpis dla name = "baby"

Ranju R.
źródło
14

Aby to ułatwić, oto fragment kodu, który napisałem, oparty na danych z cudownych odpowiedzi tutaj:

class MyManager(models.Manager):

    def get_or_none(self, **kwargs):
        try:
            return self.get(**kwargs)
        except ObjectDoesNotExist:
            return None

A potem w twoim modelu:

class MyModel(models.Model):
    objects = MyManager()

Otóż ​​to. Teraz masz MyModel.objects.get () oraz MyModel.objetcs.get_or_none ()

Moti Radomski
źródło
7
również nie zapomnij zaimportować: z django.core.exceptions import ObjectDoesNotExist
Moti Radomski
13

możesz użyć existsz filtrem:

Content.objects.filter(name="baby").exists()
#returns False or True depending on if there is anything in the QS

tylko alternatywa, jeśli chcesz tylko wiedzieć, czy istnieje

Ryan Saxe
źródło
4
Spowoduje to dodatkowe wywołanie bazy danych, jeśli istnieje. Niezbyt dobry pomysł
Christoffer
@ Christoffer nie jestem pewien, dlaczego miałoby to być dodatkowe wywołanie db. Zgodnie z dokumentami :Note: If you only want to determine if at least one result exists (and don’t need the actual objects), it’s more efficient to use exists().
Anupam
2
@Christoffer Myślę, że masz rację. Ponownie przeczytałem pytanie i OP faktycznie chce zwrócić rzeczywisty obiekt. exists()Będzie więc używany z ifklauzulą ​​przed pobraniem obiektu, powodując podwójne trafienie do bazy danych. Nadal będę trzymał komentarz na wypadek, gdyby pomógł komuś innemu.
Anupam
7

Obsługa wyjątków w różnych punktach twoich widoków może być naprawdę kłopotliwa. Co z definiowaniem niestandardowego Menedżera modeli w pliku models.py, np.

class ContentManager(model.Manager):
    def get_nicely(self, **kwargs):
        try:
            return self.get(kwargs)
        except(KeyError, Content.DoesNotExist):
            return None

a następnie uwzględnienie go w klasie Model treści

class Content(model.Model):
    ...
    objects = ContentManager()

W ten sposób można łatwo sobie z tym poradzić w widokach, tj

post = Content.objects.get_nicely(pk = 1)
if post:
    # Do something
else:
    # This post doesn't exist
Adil Malik
źródło
1
Naprawdę podoba mi się to rozwiązanie, ale nie byłem w stanie sprawić, by działało tak, jak w przypadku używania Pythona 3.6. Chciałem zostawić notatkę, że modyfikuję zwrot w ContentManager return self.get(**kwargs), aby działał dla mnie. Nie mówię, że coś jest nie tak z odpowiedzią, tylko wskazówka dla każdego, kto próbuje użyć jej w późniejszych wersjach (lub z czymkolwiek innym, co powstrzymuje mnie od pracy).
skagzilla
7

Jest to jedna z tych irytujących funkcji, których możesz nie chcieć ponownie wdrożyć:

from annoying.functions import get_object_or_None
#...
user = get_object_or_None(Content, name="baby")
mknecht
źródło
Sprawdziłem kod, get_object_or_Noneale stwierdziłem, że wciąż się podnosi, MultipleObjectsReturnedjeśli więcej niż jeden obiekt. Tak więc użytkownik może rozważyć otoczenie za pomocą try-except(które sama funkcja try-exceptjuż ma).
John Pang,
4

Jeśli potrzebujesz prostego jednoliniowego rozwiązania, które nie wymaga obsługi wyjątków, instrukcji warunkowych ani wymagań Django 1.6+, zrób to zamiast tego:

x = next(iter(SomeModel.objects.filter(foo='bar')), None)
blhsing
źródło
4

Myślę, że nie jest to zły pomysł get_object_or_404()

from django.shortcuts import get_object_or_404

def my_view(request):
    my_object = get_object_or_404(MyModel, pk=1)

Ten przykład jest równoważny z:

from django.http import Http404

def my_view(request):
    try:
        my_object = MyModel.objects.get(pk=1)
    except MyModel.DoesNotExist:
        raise Http404("No MyModel matches the given query.")

Możesz przeczytać więcej o get_object_or_404 () w dokumentacji online django.

Mohammad Reza
źródło
2

Od wersji django 1.7 możesz wykonywać następujące czynności:

class MyQuerySet(models.QuerySet):

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


class MyBaseModel(models.Model):

    objects = MyQuerySet.as_manager()


class MyModel(MyBaseModel):
    ...

class AnotherMyModel(MyBaseModel):
    ...

Zaletą „MyQuerySet.as_manager ()” jest to, że oba poniższe elementy będą działać:

MyModel.objects.filter(...).get_or_none()
MyModel.objects.get_or_none()
Vinayak Kaniyarakkal
źródło
1

Oto odmiana funkcji pomocnika, która pozwala opcjonalnie przekazać w QuerySetinstancji, na wypadek, gdybyś chciał uzyskać unikalny obiekt (jeśli jest obecny) z zestawu zapytań innego niż allzestaw zapytań obiektów modelu (np. Z podzbioru elementów potomnych należących do instancja nadrzędna):

def get_unique_or_none(model, queryset=None, **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(**kwargs)
    except model.DoesNotExist:
        return None

Można tego użyć na dwa sposoby, np .:

  1. obj = get_unique_or_none(Model, **kwargs) jak wcześniej omówiono
  2. obj = get_unique_or_none(Model, parent.children, **kwargs)
Gary
źródło
1

Bez wyjątku:

if SomeModel.objects.filter(foo='bar').exists():
    x = SomeModel.objects.get(foo='bar')
else:
    x = None

Korzystanie z wyjątku:

try:
   x = SomeModel.objects.get(foo='bar')
except SomeModel.DoesNotExist:
   x = None

Jest trochę kłótni na temat tego, kiedy należy użyć wyjątku w Pythonie. Z jednej strony „łatwiej prosić o wybaczenie niż o pozwolenie”. Chociaż zgadzam się z tym, uważam, że wyjątek powinien pozostać, no cóż, wyjątek, a „idealna sprawa” powinna przebiegać bez trafienia w jeden.

Konstantin Schubert
źródło
1

Możemy użyć wyjątku wbudowanego Django, który jest dołączony do modeli o nazwie as .DoesNotExist. Więc nie musimy importować ObjectDoesNotExistwyjątku.

Zamiast tego:

from django.core.exceptions import ObjectDoesNotExist

try:
    content = Content.objects.get(name="baby")
except ObjectDoesNotExist:
    content = None

Możemy to zrobić:

try:
    content = Content.objects.get(name="baby")
except Content.DoesNotExist:
    content = None
zakiakhmad
źródło
0

Jest to kopia naśladująca metodę get_object_or_404 Django, z wyjątkiem tego, że metoda zwraca None. Jest to niezwykle przydatne, gdy musimy użyć only()zapytania, aby pobrać tylko niektóre pola. Ta metoda może zaakceptować model lub zestaw zapytań.

from django.shortcuts import _get_queryset


def get_object_or_none(klass, *args, **kwargs):
    """
    Use get() to return an object, or return None if object
    does not exist.
    klass may be a Model, Manager, or QuerySet object. All other passed
    arguments and keyword arguments are used in the get() query.
    Like with QuerySet.get(), MultipleObjectsReturned is raised if more than
    one object is found.
    """
    queryset = _get_queryset(klass)
    if not hasattr(queryset, 'get'):
        klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
        raise ValueError(
            "First argument to get_object_or_none() must be a Model, Manager, "
            "or QuerySet, not '%s'." % klass__name
        )
    try:
        return queryset.get(*args, **kwargs)
    except queryset.model.DoesNotExist:
        return None
Sandeep Balagopal
źródło
0

Może lepiej użyć:

User.objects.filter(username=admin_username).exists()
Jonathan Souza
źródło