Django: pobierz obiekt z bazy danych lub 'None' jeśli nic nie pasuje

102

Czy jest jakaś funkcja Django, która pozwoli mi pobrać obiekt z bazy danych lub Brak, jeśli nic nie pasuje?

W tej chwili używam czegoś takiego:

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

Ale to nie jest zbyt jasne, a mieć wszędzie bałagan.

David Wolever
źródło
2
Wiesz, że możesz po prostu użyć foo = foo [0], jeśli foo else. Brak
Visgean Skeloru
2
Python ma operator trójargumentowy, nie musisz używać operatorów logicznych. Ponadto len(foo)jest zły : „ Uwaga: nie używaj len () na QuerySets, jeśli chcesz tylko określić liczbę rekordów w zestawie. O wiele wydajniej jest obsłużyć liczenie na poziomie bazy danych za pomocą funkcji SELECT COUNT w języku SQL (), a Django udostępnia metodę count () właśnie z tego powodu. ". Przepisano:foo = foo[0] if foo.exists() else None
mustafa.0x,
1
@ mustafa.0x Nie sądzę, żeby to miało tutaj zastosowanie. Tutaj sprawdzenie liczby spowodowałoby uruchomienie innego zapytania. Następnie ponownie mielibyśmy zapytanie, aby uzyskać rzeczywiste dane. Jest to przypadek, w którym filtrowanie, a następnie liczenie miałoby większy sens ... ale nie bardziej sensowne niż filtrowanie i wywoływanie first(): P
MicronXD

Odpowiedzi:

88

W Django 1.6 możesz użyć first()metody Queryset. Zwraca pierwszy obiekt dopasowany przez zestaw zapytań lub None, jeśli nie ma pasującego obiektu.

Stosowanie:

p = Article.objects.order_by('title', 'pub_date').first()
Cesar Canassa
źródło
19
To nie jest dobra odpowiedź, jeśli zostanie znalezionych wiele obiektów, MultipleObjectsReturned()nie jest to zgłaszane, jak opisano tutaj . Możesz znaleźć lepsze odpowiedzi tutaj
senny
25
@sleepycal Nie zgadzam się. Te first()prace dokładnie tak, jak przypuszczam. Zwraca pierwszy obiekt w wyniku zapytania i nie przejmuje się, jeśli znaleziono wiele wyników. Jeśli chcesz sprawdzić wiele zwróconych obiektów, użyj .get()zamiast tego.
Cesar Canassa
7
[zredagowano] Jak powiedział PO, .get()nie nadaje się do jego potrzeb. Prawidłową odpowiedź na to pytanie można znaleźć poniżej przez @kaapstorm i jest to zdecydowanie bardziej odpowiednia odpowiedź. Nadużywanie w filter()ten sposób może prowadzić do nieoczekiwanych konsekwencji, z czego OP prawdopodobnie nie zdawał sobie sprawy (chyba że czegoś tu brakuje)
senny
1
@sleepycal Jakie niezamierzone konsekwencje wynikają z takiego podejścia?
HorseloverFat
12
Jeśli ograniczenia bazy danych nie zapewniają prawidłowego zarządzania unikalnością między obiektami, możesz trafić w skrajne przypadki, w których Twoja logika oczekuje, że będzie tylko jeden obiekt (który jest wybierany przez first ()), ale w rzeczywistości istnieje wiele obiektów. Użycie get () zamiast first () daje dodatkową warstwę ochrony poprzez podniesienie MultipleObjectsReturned(). Jeśli zwracany wynik nie powinien zwrócić wielu obiektów, nie powinien być traktowany jako taki. Nastąpiła długa debata na ten temat tutaj
sleepycal
140

Można to zrobić na dwa sposoby;

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

Lub możesz użyć opakowania:

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

Nazwij to tak

foo = get_or_none(Foo, baz=bar)
Nick Craig-Wood
źródło
8
Nie podoba mi się fakt, że używa wyjątków dla funkcjonalności - jest to zła praktyka w większości języków. Jak to jest w Pythonie?
pcv
18
Tak działa iteracja Pythona (podnosi StopIteration) i to jest podstawowa część języka. Nie jestem też wielkim fanem funkcji try / except, ale wydaje się, że jest ona uważana za Pythonic.
Matt Luongo,
4
@pcv: nie ma wiarygodnej konwencjonalnej wiedzy na temat „większości” języków. Przypuszczam, że myślisz o jakimś konkretnym języku, którego używasz, ale bądź ostrożny z takimi kwantyfikatorami. Wyjątki mogą być tak wolne (lub „wystarczająco szybkie”), jak wszystko w Pythonie. Wracając do pytania - to wada frameworka, że ​​nie dostarczyli żadnej querysetmetody na zrobienie tego przed 1.6! Ale jak na ten konstrukt - jest po prostu niezdarny. To nie jest „zło”, ponieważ tak powiedział jakiś autor tutoriala w Twoim ulubionym języku. Spróbuj google: „poproś o wybaczenie zamiast o pozwolenie”.
Tomasz Gandor
@TomaszGandor: Tak, jest niezgrabny i trudny do odczytania, bez względu na to, jak mówi twój ulubiony tutorial. Kiedy widzę wyjątek, przypuszczam, że dzieje się coś niestandardowego. Liczy się czytelność. Ale zgadzam się, że jeśli nie ma innej rozsądnej opcji, powinieneś to zrobić i nic złego się nie stanie.
pcv
@pcv getMetoda nie powinna zwracać None, więc wyjątek ma sens. Powinieneś używać go w przypadkach, gdy jesteś pewien, że obiekt tam będzie / obiekt ma się tam znajdować. Również używanie takich wyjątków jest uważane za „w porządku”, ponieważ ma to sens. Chcesz mieć getinny kontrakt, więc wychwytujesz wyjątek i pomijasz go, czyli zmianę, której szukasz.
Dan Passaro,
83

Aby dodać przykładowy kod do odpowiedzi sorki (dodałbym to jako komentarz, ale to jest mój pierwszy post i nie mam wystarczającej reputacji, aby dodawać komentarze), zaimplementowałem niestandardowego menedżera get_or_none w następujący sposób:

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

A teraz mogę to zrobić:

bob_or_none = Person.objects.get_or_none(name='Bob')
burza śnieżna
źródło
To jest bardzo eleganckie.
NotSimon
13

Możesz także spróbować użyć django irytującego (ma inne przydatne funkcje!)

zainstaluj za pomocą:

pip install django-annoying

from annoying.functions import get_object_or_None
get_object_or_None(Foo, bar=baz)
llazzaro
źródło
9

Daj Foo jego niestandardowego menedżera . To całkiem proste - po prostu umieść kod w funkcji w menedżerze niestandardowym, ustaw menedżera niestandardowego w modelu i wywołaj go za pomocą Foo.objects.your_new_func(...).

Jeśli potrzebujesz funkcji ogólnej (aby używać jej w dowolnym modelu, nie tylko z niestandardowym menedżerem), napisz własną i umieść ją w dowolnym miejscu na ścieżce Pythona i zaimportuj, nie brudząc się już.

sorki
źródło
4

Niezależnie od tego, czy robisz to za pośrednictwem menedżera, czy funkcji ogólnej, możesz również chcieć złapać `` MultipleObjectsReturned '' w instrukcji TRY, ponieważ funkcja get () podniesie to, jeśli twoje kwargs pobiorą więcej niż jeden obiekt.

Opierając się na funkcji ogólnej:

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

aw menedżerze:

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
źródło
Wydaje się, że to oddaje wszystkie najlepsze punkty pozostałych odpowiedzi. Niezły :)
Edward Newell
@emispowder, co jeśli nie chcę zwracać Brak, chcę zwrócić komunikat ostrzegawczy, taki jak „brak pasujących danych”, wyświetlany na TEJ SAMEJ stronie?
Héléna
0

Oto odmiana funkcji pomocniczej, która pozwala opcjonalnie przekazać QuerySetinstancję, na wypadek gdybyś chciał pobrać 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, *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

Można to wykorzystać na dwa sposoby, np .:

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

Myślę, że w większości przypadków możesz po prostu użyć:

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

Tylko jeśli nie jest krytyczne, że nowy wpis zostanie dodany w tabeli Foo (inne kolumny będą miały wartości Brak / domyślne)

TomerP
źródło