Come ottengo l'oggetto se esiste o Nessuno se non esiste?


250

Quando chiedo al gestore modelli di ottenere un oggetto, si solleva DoesNotExistquando non ci sono oggetti corrispondenti.

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

Invece di DoesNotExist, come posso avere goessere Noneinvece?

Risposte:


358

Non esiste un modo "integrato" per farlo. Django solleverà l'eccezione DoesNotExist ogni volta. Il modo idiomatico per gestire questo in Python è avvolgerlo in un tentativo di cattura:

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

Quello che ho fatto è creare una sottoclasse di modelli Gestore, creare un safe_getcodice simile al precedente e utilizzare quel gestore per i miei modelli. In questo modo è possibile scrivere: SomeModel.objects.safe_get(foo='bar').


7
Buon uso di SomeModel.DoesNotExist invece di importare anche l'eccezione.
supermitch

211
Questa soluzione è lunga quattro righe. Per me questo è troppo. Con django 1.6 puoi usare SomeModel.objects.filter(foo='bar').first()questo restituisce la prima corrispondenza o Nessuno. Non fallisce se ci sono diversi casi comequeryset.get()
guettli

12
Penso che sia un cattivo stile usare eccessivamente le eccezioni per la gestione dei casi predefiniti. Sì, "è più facile chiedere perdono che permesso". Ma un'eccezione dovrebbe ancora essere usata, ai miei occhi, per le eccezioni.
Konstantin Schubert

8
Esplicito è meglio che implicito. A meno che non ci sia un motivo per le prestazioni da utilizzare, filter().first()penso che l'eccezione sia la strada da percorrere.
christianbundy

7
Usare first () è solo una buona idea se non ti interessa quando ci sono multipli. Altrimenti, questa soluzione è superiore, perché genererà comunque un'eccezione se trovi inaspettatamente più oggetti, che normalmente è ciò che vorresti che accadesse in quel caso.
rossdavidh

205

Da django 1.6 puoi usare il metodo first () in questo modo:

Content.objects.filter(name="baby").first()

30
In questo caso, non viene generato alcun errore se è presente più di una corrispondenza.
Konstantin Schubert

7
"FeroxTL" devi dare credito a @guettli per questa risposta, poiché ha commentato la risposta accettata un anno prima del tuo post.
colm.anseo

7
@colminator Preferirei dire che guettli dovrebbe imparare che una nuova risposta non appartiene a un commento se vuole aumentare la sua reputaiton di stackoverflow :) FeroxTL dovrebbe ottenere punti per aver reso qualcosa di nascosto come commento più chiaro come risposta. Il tuo commento è abbastanza merito per guettli penso e non dovrebbe essere aggiunto alla risposta se questo fosse il tuo suggerimento.
Joakim,

3
@Joakim non ho problemi a postare una nuova "risposta" - solo per dare credito a dove è dovuta :-)
colm.anseo

3
Che dire delle prestazioni di questo approccio rispetto alla risposta accettata?
MaxCore

37

Da django docs

get()solleva DoesNotExistun'eccezione se un oggetto non viene trovato per i parametri dati. Questa eccezione è anche un attributo della classe del modello. L' DoesNotExist eccezione eredita dadjango.core.exceptions.ObjectDoesNotExist

Puoi catturare l'eccezione e assegnare Noneper andare.

from django.core.exceptions import ObjectDoesNotExist
try:
    go  = Content.objects.get(name="baby")
except ObjectDoesNotExist:
    go = None

36

È possibile creare una funzione generica per questo.

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

Usa questo come di seguito:

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

gosarà Nonese nessuna voce corrisponde altrimenti restituirà la voce Contenuto.

Nota: solleverà un'eccezione MultipleObjectsReturnedse viene restituita più di una voce per name="baby".

Dovresti gestirlo sul modello dati per evitare questo tipo di errore, ma potresti preferirlo registrarlo in fase di esecuzione in questo modo:

def get_or_none(classmodel, **kwargs):
    try:
        return classmodel.objects.get(**kwargs)
    except classmodel.MultipleObjectsReturned as e:
        print('ERR====>', e)

    except classmodel.DoesNotExist:
        return None

Grande! se si tratta di un errore lato client: da django.shortcuts import get_object_or_404 altrimenti è un problema lato server quindi get_or_none è il migliore.
F.Tamy


15

Per rendere le cose più facili, ecco uno snippet del codice che ho scritto, basato sugli input dalle meravigliose risposte qui:

class MyManager(models.Manager):

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

E poi nel tuo modello:

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

Questo è tutto. Ora hai MyModel.objects.get () e MyModel.objetcs.get_or_none ()


7
inoltre, non dimenticare di importare: da django.core.exceptions import ObjectDoesNotExist
Moti Radomski

15

potresti usare existscon un filtro:

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

solo un'alternativa per se vuoi solo sapere se esiste


4
Ciò causerebbe una chiamata extra al database quando esiste. Non è una buona idea
Christoffer

@Christoffer non è sicuro del motivo per cui dovrebbe essere una chiamata db extra. Come da documenti :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 Penso che tu abbia ragione. Ora leggo di nuovo la domanda e l'OP vuole effettivamente che l'oggetto reale venga restituito. Quindi exists()verrà usato con la ifclausola prima di recuperare l'oggetto, causando quindi un doppio colpo al db. Terrò comunque il commento in giro nel caso in cui aiuti qualcun altro.
Anupam

7

Gestire le eccezioni in diversi punti delle tue viste potrebbe essere davvero complicato ... Che ne dici di definire un gestore modelli personalizzato, nel file models.py, come

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

e quindi includerlo nella classe del modello di contenuto

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

In questo modo può essere facilmente affrontato nelle viste es

post = Content.objects.get_nicely(pk = 1)
if post:
    # Do something
else:
    # This post doesn't exist

1
Mi piace molto questa soluzione, ma non sono riuscito a farlo funzionare come quando si utilizza Python 3.6. Volevo lasciare una nota che modificasse il ritorno nel ContentManager per return self.get(**kwargs)farlo funzionare per me. Per non dire che c'è qualcosa di sbagliato nella risposta, solo un suggerimento per chiunque cerchi di usarlo con le versioni successive (o con qualsiasi altra cosa gli abbia impedito di funzionare per me).
skagzilla

7

È una di quelle funzioni fastidiose che potresti non voler reimplementare:

from annoying.functions import get_object_or_None
#...
user = get_object_or_None(Content, name="baby")

Ho controllato il codice di get_object_or_Nonema ho scoperto che si solleva ancora MultipleObjectsReturnedse più di un oggetto. Quindi, l'utente potrebbe considerare di circondare con a try-except(che la funzione stessa ha try-exceptgià).
John Pang

5

Penso che non sia una cattiva idea da usare 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)

Questo esempio è equivalente a:

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

Puoi leggere di più su get_object_or_404 () nella documentazione in linea di django.


4

Se desideri una semplice soluzione su una riga che non implichi la gestione delle eccezioni, istruzioni condizionali o un requisito di Django 1.6+, fallo invece:

x = next(iter(SomeModel.objects.filter(foo='bar')), None)

2

Da django 1.7 in poi puoi fare come:

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):
    ...

Il vantaggio di "MyQuerySet.as_manager ()" è che entrambi i seguenti elementi funzioneranno:

MyModel.objects.filter(...).get_or_none()
MyModel.objects.get_or_none()

1

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 allquery degli oggetti del modello (ad esempio da un sottoinsieme di elementi figlio appartenenti a un istanza genitore):

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

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

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

1

Senza eccezioni:

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

Utilizzando un'eccezione:

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

C'è un po 'di discussione su quando si dovrebbe usare un'eccezione in Python. Da un lato, "è più facile chiedere perdono che permesso". Anche se sono d'accordo con questo, credo che dovrebbe rimanere un'eccezione, beh, l'eccezione e il "caso ideale" dovrebbe funzionare senza colpirne uno.


1

Possiamo usare l'eccezione incorporata Django che è collegata ai modelli denominati come .DoesNotExist. Quindi, non dobbiamo importare ObjectDoesNotExisteccezioni.

Invece facendo:

from django.core.exceptions import ObjectDoesNotExist

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

Possiamo farcela:

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

1

Forse è meglio che usi:

User.objects.filter(username=admin_username).exists()

0

Questo è un copione da get_object_or_404 di Django tranne per il fatto che il metodo restituisce None. Ciò è estremamente utile quando dobbiamo utilizzare la only()query per recuperare solo determinati campi. Questo metodo può accettare un modello o un set di query.

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

0

Anch'io stavo affrontando lo stesso problema. È difficile scrivere e leggere try-exceptogni volta che vuoi ottenere un elemento dal tuo modello come nella risposta di @Arthur Debert. Quindi, la mia soluzione è creare una Getterclasse ereditata dai modelli:

class Getter:
    @classmethod
    def try_to_get(cls, *args, **kwargs):
        try:
            return cls.objects.get(**kwargs)
        except Exception as e:
            return None

class MyActualModel(models.Model, Getter):
    pk_id = models.AutoField(primary_key=True)
    ...

In questo modo, posso ottenere l'elemento effettivo di MyActualModelo None:

MyActualModel.try_to_get(pk_id=1)

0

Uso Django 2.2.16. Ed è così che risolvo questo problema:

from typing import Any

from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.db.models.base import ModelBase
from django.db.models.manager import Manager


class SManager(Manager):
    def get_if_exist(self, *args: Any, **kwargs: Any):
        try:
            return self.get(*args, **kwargs)
        except ObjectDoesNotExist:
            return None


class SModelBase(ModelBase):
    def _prepare(cls):
        manager = SManager()
        manager.auto_created = True
        cls.add_to_class("objects", manager)

        super()._prepare()

    class Meta:
        abstract = True


class SModel(models.Model, metaclass=SModelBase):
    managers = False

    class Meta:
        abstract = True

Dopodiché, in ogni modello, devi solo importare in:

from custom.models import SModel


class SUser(SModel):
    pass

E in views, puoi chiamare in questo modo:

SUser.objects.get_if_exist(id=1)
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.