Comment puis-je obtenir l'objet s'il existe, ou Aucun s'il n'existe pas?

223

Lorsque je demande au gestionnaire de modèle d'obtenir un objet, il augmente DoesNotExistlorsqu'il n'y a pas d'objet correspondant.

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

Au lieu de DoesNotExist, comment puis-je goêtre à la Noneplace?

TIMEX
la source

Réponses:

332

Il n'y a aucun moyen «intégré» de le faire. Django lèvera à chaque fois l'exception DoesNotExist. La façon idiomatique de gérer cela en python est de l'envelopper dans une prise d'essai:

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

Ce que j'ai fait, c'est de sous-classer les modèles.Manager, créer un safe_getcomme le code ci-dessus et utiliser ce gestionnaire pour mes modèles. De cette façon , vous pouvez écrire: SomeModel.objects.safe_get(foo='bar').

Arthur Debert
la source
7
Belle utilisation de SomeModel.DoesNotExist au lieu d'importer l'exception également.
supermitch
197
Cette solution comporte quatre lignes. Pour moi, c'est trop. Avec django 1.6, vous pouvez utiliser SomeModel.objects.filter(foo='bar').first()cela renvoie la première correspondance, ou Aucun. Il n'échoue pas s'il y a plusieurs exemples commequeryset.get()
guettli
9
Je pense que c'est un mauvais style de surexploiter les exceptions pour gérer les cas par défaut. Oui, "il est plus facile de demander pardon que de permission". Mais une exception devrait encore être utilisée, à mes yeux, pour les exceptions.
Konstantin Schubert
8
Explicite vaut mieux qu'implicite. À moins qu'il n'y ait une raison de performance à utiliser, filter().first()je pense que l'exception est la voie à suivre.
christianbundy
6
L'utilisation de first () n'est une bonne idée que si vous ne vous souciez pas des multiples. Sinon, cette solution est supérieure, car elle lèvera toujours une exception si vous trouvez de manière inattendue plusieurs objets, ce qui est normalement ce que vous souhaitez dans ce cas.
rossdavidh
182

Depuis django 1.6, vous pouvez utiliser la méthode first () comme ceci:

Content.objects.filter(name="baby").first()
FeroxTL
la source
26
Dans ce cas, aucune erreur n'est déclenchée s'il y a plus d'une correspondance.
Konstantin Schubert
7
'FeroxTL', vous devez créditer @guettli pour cette réponse, car il a commenté cela sur la réponse acceptée un an avant votre publication.
colm.anseo
7
@colminator Je dirais plutôt que Guettli devrait apprendre qu'une nouvelle réponse n'appartient pas à un commentaire s'il veut augmenter sa réputation de stackoverflow :) FeroxTL devrait obtenir des points pour rendre quelque chose caché comme un commentaire plus clair comme réponse. Votre commentaire est assez crédible pour guettli je pense et ne devrait pas être ajouté à la réponse si telle était votre suggestion.
Joakim
3
@Joakim Je n'ai aucun problème avec la publication d'une nouvelle "réponse" - juste pour rendre
hommage
3
Qu'en est-il des performances de cette approche par rapport à la réponse acceptée?
MaxCore
36

À partir de django docs

get()lève une DoesNotExistexception si aucun objet n'est trouvé pour les paramètres donnés. Cette exception est également un attribut de la classe de modèle. L' DoesNotExist exception hérite dedjango.core.exceptions.ObjectDoesNotExist

Vous pouvez intercepter l'exception et lui attribuer Nonele départ.

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

Vous pouvez créer une fonction générique pour cela.

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

Utilisez ceci comme ci-dessous:

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

go sera None si aucune entrée ne correspondra sinon retournera l'entrée Content.

Remarque: Cela déclenche une exception MultipleObjectsReturned si plusieurs entrées sont retournées pour name = "baby"

Ranju R
la source
15

Pour faciliter les choses, voici un extrait du code que j'ai écrit, basé sur les entrées des merveilleuses réponses ici:

class MyManager(models.Manager):

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

Et puis dans votre modèle:

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

C'est tout. Vous avez maintenant MyModel.objects.get () ainsi que MyModel.objetcs.get_or_none ()

Moti Radomski
la source
7
n'oubliez pas non plus d'importer: à partir de django.core.exceptions import ObjectDoesNotExist
Moti Radomski
13

vous pouvez utiliser existsavec un filtre:

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

juste une alternative pour si vous voulez seulement savoir si elle existe

Ryan Saxe
la source
4
Cela entraînerait un appel de base de données supplémentaire lorsqu'il existe. Pas une bonne idée
Christoffer
@Christoffer ne sais pas pourquoi ce serait un appel supplémentaire à la base de données. Selon les documents :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 Je pense que vous avez raison. J'ai maintenant relu la question et l'OP veut réellement que l'objet réel soit retourné. Donc, exists()sera utilisé avec la ifclause avant de récupérer l'objet, provoquant ainsi un double coup sur la base de données. Je garderai toujours le commentaire au cas où cela aiderait quelqu'un d'autre.
Anupam
7

La gestion des exceptions à différents points de vos vues peut être très compliquée ... Qu'en est-il de la définition d'un gestionnaire de modèles personnalisé, dans le fichier models.py, comme

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

puis l'inclure dans la classe de modèle de contenu

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

De cette façon, il peut être facilement traité dans les vues à savoir

post = Content.objects.get_nicely(pk = 1)
if post:
    # Do something
else:
    # This post doesn't exist
Adil Malik
la source
1
J'aime vraiment cette solution, mais je n'ai pas pu la faire fonctionner telle quelle lors de l'utilisation de python 3.6. Je voulais laisser une note que la modification du retour dans le ContentManager pour return self.get(**kwargs)le faire fonctionner pour moi. Pour ne rien dire de mal avec la réponse, juste un conseil pour quiconque essaie de l'utiliser avec des versions ultérieures (ou avec quoi que ce soit d'autre l'empêchant de fonctionner pour moi).
skagzilla
7

C'est l'une de ces fonctions ennuyeuses que vous ne voudrez peut-être pas réimplémenter:

from annoying.functions import get_object_or_None
#...
user = get_object_or_None(Content, name="baby")
mknecht
la source
J'ai vérifié le code de get_object_or_Nonemais j'ai constaté qu'il augmentait toujours MultipleObjectsReturnedsi plusieurs objets. Ainsi, l'utilisateur pourrait envisager d'entourer d'un try-except(que la fonction elle-même a try-exceptdéjà).
John Pang
4

Si vous voulez une solution simple sur une seule ligne qui n'implique pas de gestion des exceptions, des instructions conditionnelles ou une exigence de Django 1.6+, faites-le à la place:

x = next(iter(SomeModel.objects.filter(foo='bar')), None)
blhsing
la source
4

Je pense que ce n'est pas une mauvaise idée d'utiliser 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)

Cet exemple équivaut à:

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

Vous pouvez en savoir plus sur get_object_or_404 () dans la documentation en ligne de django.

Mohammad Reza
la source
2

À partir de django 1.7, vous pouvez faire comme:

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

L'avantage de "MyQuerySet.as_manager ()" est que les deux éléments suivants fonctionneront:

MyModel.objects.filter(...).get_or_none()
MyModel.objects.get_or_none()
Vinayak Kaniyarakkal
la source
1

Voici une variante de la fonction d'assistance qui vous permet de transmettre éventuellement une QuerySetinstance, au cas où vous souhaitez obtenir l'objet unique (s'il est présent) à partir d'un ensemble de requêtes autre que l'ensemble de allrequêtes des objets du modèle (par exemple à partir d'un sous-ensemble d'éléments enfants appartenant à un instance parent):

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

Cela peut être utilisé de deux manières, par exemple:

  1. obj = get_unique_or_none(Model, **kwargs) tel que discuté précédemment
  2. obj = get_unique_or_none(Model, parent.children, **kwargs)
Gary
la source
1

Sans exception:

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

Utilisation d'une exception:

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

Il y a un peu d'argument sur le moment où il faut utiliser une exception en python. D'une part, "il est plus facile de demander pardon que de permission". Bien que je sois d'accord avec cela, je crois qu'une exception doit rester, eh bien, l'exception, et le «cas idéal» devrait fonctionner sans en toucher un.

Konstantin Schubert
la source
1

Nous pouvons utiliser l'exception intégrée de Django attachée aux modèles nommés comme .DoesNotExist. Nous n'avons donc pas à importer d' ObjectDoesNotExistexception.

Au lieu de cela:

from django.core.exceptions import ObjectDoesNotExist

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

Nous pouvons le faire:

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

Il s'agit d'une copie de get_object_or_404 de Django, sauf que la méthode renvoie None. Ceci est extrêmement utile lorsque nous devons utiliser la only()requête pour récupérer uniquement certains champs. Cette méthode peut accepter un modèle ou un ensemble de requêtes.

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
la source
0

Il vaut peut-être mieux utiliser:

User.objects.filter(username=admin_username).exists()
Jonathan Souza
la source