Django: Ottieni un oggetto dal DB, o "Nessuno" se nulla corrisponde


101

Esiste una funzione Django che mi consentirà di ottenere un oggetto dal database o Nessuno se nulla corrisponde?

In questo momento sto usando qualcosa del tipo:

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

Ma non è molto chiaro ed è complicato avere ovunque.


2
Sai che puoi semplicemente usare foo = foo [0] if foo else None
Visgean Skeloru

2
Python ha un operatore ternario, non devi usare operatori booleani. Inoltre, len(foo)è negativo : " Nota: non utilizzare len () su QuerySet se tutto ciò che vuoi fare è determinare il numero di record nel set. È molto più efficiente gestire un conteggio a livello di database, utilizzando SELECT COUNT di SQL (), e Django fornisce un metodo count () proprio per questo motivo. ". Riscritto:foo = foo[0] if foo.exists() else None
mustafa.0x


1
@ mustafa.0x Non credo che si applichi qui. Qui, il controllo del conteggio eseguirà un'altra query. Quindi avremmo una nuova query per ottenere i dati effettivi. Questo è un caso in cui filtrare e quindi contare avrebbe più senso ... ma non più senso del filtraggio e della chiamata first(): P
MicronXD

Risposte:


88

In Django 1.6 puoi usare il first()metodo Queryset. Restituisce il primo oggetto abbinato dal set di query o Nessuno se non esiste un oggetto corrispondente.

Uso:

p = Article.objects.order_by('title', 'pub_date').first()

19
Questa non è una buona risposta, se vengono trovati più oggetti, MultipleObjectsReturned()non viene sollevato, come documentato qui . Puoi trovare alcune risposte migliori qui
sleepycal

25
@sleepycal Non sono d'accordo. I first()lavori esattamente come supponiamo di. Restituisce il primo oggetto nel risultato della query e non si preoccupa se vengono trovati più risultati. Se è necessario verificare la presenza di più oggetti restituiti, utilizzare .get()invece.
Cesar Canassa

7
[modificato] Come ha detto l'OP, .get()non è adatto alle sue esigenze. La risposta corretta a questa domanda può essere trovata di seguito da @kaapstorm, ed è chiaramente la risposta più adatta. Abusare in filter()questo modo può portare a conseguenze inaspettate, qualcosa di cui OP probabilmente non si è reso conto (a meno che non mi manchi qualcosa qui)
sleepycal

1
@sleepycal Quali conseguenze indesiderate derivano da questo approccio?
HorseloverFat

12
Se i vincoli del database non gestiscono correttamente l'unicità tra gli oggetti, è possibile raggiungere casi limite in cui la logica si aspetta che ci sia solo un singolo oggetto (che è selezionato da first ()), ma in realtà esistono più oggetti. L'uso di get () invece di first () offre un ulteriore livello di protezione, aumentando MultipleObjectsReturned(). Se il risultato restituito non dovrebbe restituire più oggetti, non dovrebbe essere considerato come tale. C'è stato un lungo dibattito su questo qui
sleepycal

139

Ci sono due modi per farlo;

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

Oppure puoi usare un wrapper:

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

Chiamalo così

foo = get_or_none(Foo, baz=bar)

8
Non mi piace il fatto che utilizzi eccezioni per la funzionalità: questa è una cattiva pratica nella maggior parte delle lingue. Com'è in Python?
pcv

18
È così che funziona l'iterazione di Python (solleva una StopIteration), e questa è una parte fondamentale del linguaggio. Non sono nemmeno un grande fan della funzionalità try / tranne, ma sembra essere considerata Pythonic.
Matt Luongo

4
@pcv: non esiste una saggezza convenzionale affidabile sulla "maggior parte" delle lingue. Suppongo che tu pensi a qualche linguaggio specifico che usi, ma fai attenzione con questi quantificatori. Le eccezioni possono essere lente (o "abbastanza veloci" per quella materia), come qualsiasi cosa in Python. Tornando alla domanda - è un difetto del framework, che non fornivano alcun querysetmetodo per farlo prima della 1.6! Ma per quanto riguarda questo costrutto, è semplicemente goffo. Non è "cattivo" perché lo ha detto qualche autore di tutorial nella tua lingua preferita. Prova google: "chiedi perdono piuttosto che permesso".
Tomasz Gandor

@TomaszGandor: Sì, è goffo e difficile da leggere, qualunque cosa dica il tuo tutorial di lingua preferito. Quando vedo un'eccezione, suppongo che stia accadendo qualcosa di non standard. La leggibilità conta. Ma sono d'accordo quando non c'è altra opzione ragionevole, dovresti provarci e non succederà niente di male.
PCV

@pcv Il getmetodo non dovrebbe restituire None, quindi un'eccezione ha senso. Dovresti usarlo nei casi in cui sei sicuro che l'oggetto sarà lì / che l'oggetto dovrebbe essere lì. Anche l'utilizzo di eccezioni come questa è considerato "ok" perché, beh, ha senso. Vuoi un getcon un contratto diverso, quindi prendi l'eccezione e la sopprimi, che è il cambiamento che stai cercando.
Dan Passaro

83

Per aggiungere un codice di esempio alla risposta di sorki (lo aggiungerei come commento, ma questo è il mio primo post e non ho abbastanza reputazione per lasciare commenti), ho implementato un gestore personalizzato get_or_none in questo modo:

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

E ora posso farlo:

bob_or_none = Person.objects.get_or_none(name='Bob')

Questo è molto elegante.
NotSimon

13

Puoi anche provare a usare django fastidioso (ha altre utili funzioni!)

installalo con:

pip install django-annoying

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

9

Dai a Foo il suo gestore personalizzato . È abbastanza semplice: metti il ​​tuo codice in funzione nel gestore personalizzato, imposta il gestore personalizzato nel tuo modello e chiamalo conFoo.objects.your_new_func(...) .

Se hai bisogno di una funzione generica (per usarla su qualsiasi modello non solo con il gestore personalizzato) scrivi la tua e posizionala da qualche parte nel tuo percorso python e importala, non più disordinato.


4

Che lo si faccia tramite un gestore o una funzione generica, potresti anche voler catturare 'MultipleObjectsReturned' nell'istruzione TRY, poiché la funzione get () lo solleverà se i tuoi kwarg recuperano più di un oggetto.

Basandosi sulla funzione generica:

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

e nel gestore:

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

Questo sembra catturare tutti i punti migliori delle altre risposte. Bella :)
Edward Newell

@emispowder, cosa succede se non voglio restituire Nessuno, voglio restituire un messaggio di avviso come "nessun dato corrispondente" visualizzato nella STESSA pagina?
Héléna

0

Ecco una variazione sulla funzione di supporto che ti consente di passare facoltativamente QuerySetun'istanza, nel caso in cui desideri ottenere l'oggetto univoco (se presente) da un set di query diverso dal set di query di alloggetti del modello (ad esempio da un sottoinsieme di elementi figlio appartenenti a un istanza genitore):

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

Questo può essere utilizzato in due modi, ad esempio:

  1. obj = get_unique_or_none(Model, *args, **kwargs) come discusso in precedenza
  2. obj = get_unique_or_none(Model, parent.children, *args, **kwargs)

-1

Penso che nella maggior parte dei casi puoi semplicemente usare:

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

Solo se non è fondamentale che una nuova voce venga aggiunta nella tabella Foo (le altre colonne avranno i valori Nessuno / predefinito)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.