¿Cómo obtengo el objeto si existe o Ninguno si no existe?


222

Cuando le pido al administrador de modelos que obtenga un objeto, surge DoesNotExistcuando no hay ningún objeto coincidente.

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

En lugar de DoesNotExist, ¿cómo puedo haber gosido Noneen su lugar?

Respuestas:


332

No hay una forma "integrada" de hacer esto. Django generará la excepción DoesNotExist cada vez. La forma idiomática de manejar esto en python es envolverlo en un intento de captura:

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

Lo que sí hice fue crear subclases de modelos. Administrador, crear un safe_getcódigo similar al anterior y usar ese administrador para mis modelos. De esa manera se puede escribir: SomeModel.objects.safe_get(foo='bar').


77
Buen uso de SomeModel.DoesNotExist en lugar de importar la excepción, también.
Supermitch

195
Esta solución tiene cuatro líneas de largo. Para mí esto es demasiado. Con django 1.6 puede usar SomeModel.objects.filter(foo='bar').first()esto devuelve la primera coincidencia, o Ninguno. No falla si hay varias instancias comoqueryset.get()
guettli

99
Creo que es un mal estilo usar en exceso las excepciones para manejar casos predeterminados. Sí, "es más fácil pedir perdón que permiso". Pero una excepción aún debe usarse, a mis ojos, para excepciones.
Konstantin Schubert

8
Explícito es mejor que implícito. A menos que haya una razón de rendimiento para usar filter().first(), creo que la excepción es el camino a seguir.
christianbundy

66
Usar first () es solo una buena idea si no te importa cuando hay múltiplos. De lo contrario, esta solución es superior, porque aún arrojará una excepción si inesperadamente encuentra varios objetos, que normalmente es lo que le gustaría que ocurriera en ese caso.
rossdavidh

181

Desde django 1.6 puede usar el método first () de esta manera:

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

26
En este caso, no se genera ningún error si hay más de una coincidencia.
Konstantin Schubert

77
'FeroxTL' necesita acreditar a @guettli por esta respuesta, ya que comentó esto en la respuesta aceptada un año antes de su publicación.
colm.anseo

77
@colminator Prefiero decir que guettli debería aprender que una nueva respuesta no pertenece como comentario si quiere aumentar su reputación de stackoverflow :) FeroxTL debería obtener puntos por hacer que algo oculto como comentario sea más claro como respuesta. Su comentario es suficiente crédito para guettli, creo, y no debería agregarse a la respuesta si esa fue su sugerencia.
Joakim

3
@Joakim No tengo ningún problema con publicar una nueva "respuesta", solo para dar crédito donde se debe :-)
colm.anseo

3
¿Qué pasa con el desempeño de este enfoque en comparación con la respuesta aceptada?
MaxCore

36

De django docs

get()genera una DoesNotExistexcepción si no se encuentra un objeto para los parámetros dados. Esta excepción también es un atributo de la clase de modelo. La DoesNotExist excepción hereda dedjango.core.exceptions.ObjectDoesNotExist

Puede atrapar la excepción y asignar Nonepara ir.

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

33

Puede crear una función genérica para esto.

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

Use esto como a continuación:

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

go será None si ninguna entrada coincide con otra, devolverá la entrada de Contenido.

Nota: Se generará una excepción MultipleObjectsReturned si se devuelve más de una entrada para name = "baby"



14

Para facilitar las cosas, aquí hay un fragmento del código que escribí, basado en las entradas de las maravillosas respuestas aquí:

class MyManager(models.Manager):

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

Y luego en tu modelo:

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

Eso es. Ahora tiene MyModel.objects.get () así como MyModel.objetcs.get_or_none ()


77
Además, no olvide importar: desde django.core.exceptions import ObjectDoesNotExist
Moti Radomski

13

podrías usar existscon un filtro:

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

solo una alternativa para si solo quieres saber si existe


44
Eso provocaría una llamada adicional a la base de datos cuando exista. No es una buena idea
Christoffer

@Christoffer no está seguro de por qué sería una llamada de base de datos adicional. Según los documentos :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 Creo que tienes razón. Ahora leo la pregunta nuevamente y el OP realmente quiere que se devuelva el objeto real. Por exists()lo tanto , se usará con una ifcláusula antes de recuperar el objeto, por lo tanto, causará un doble golpe a la base de datos. Todavía mantendré el comentario en caso de que ayude a alguien más.
Anupam

7

Manejar excepciones en diferentes puntos de sus vistas podría ser realmente engorroso ... ¿Qué pasa con la definición de un Model Manager personalizado, en el archivo models.py, como

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

y luego incluirlo en la clase de modelo de contenido

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

De esta manera se puede tratar fácilmente en las vistas, es decir

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

1
Realmente me gusta esta solución, pero no pude hacer que funcione como está cuando uso Python 3.6. Quería dejar una nota que modificara la devolución en el ContentManager para return self.get(**kwargs)que funcione para mí. Por no decir que hay algo mal con la respuesta, solo un consejo para cualquiera que intente usarlo con versiones posteriores (o con cualquier otra cosa que no funcionó para mí).
skagzilla

7

Es una de esas molestas funciones que quizás no quieras volver a implementar:

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

Revisé el código de get_object_or_Nonepero encontré que aún se eleva MultipleObjectsReturnedsi hay más de un objeto. Por lo tanto, el usuario podría considerar rodearse con un try-except(que la función en sí try-exceptya tiene).
John Pang el

4

Si desea una solución simple de una línea que no implique el manejo de excepciones, declaraciones condicionales o un requisito de Django 1.6+, haga esto en su lugar:

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

4

Creo que no es mala idea usar 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)

Este ejemplo es 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.")

Puede leer más sobre get_object_or_404 () en la documentación en línea de django.


2

Desde django 1.7 en adelante, puede hacer como:

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

La ventaja de "MyQuerySet.as_manager ()" es que funcionará lo siguiente:

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

1

Aquí hay una variación en la función auxiliar que le permite pasar opcionalmente una QuerySetinstancia, en caso de que desee obtener el objeto único (si está presente) de un conjunto de consultas que no sea el conjunto de consultas de allobjetos del modelo (por ejemplo, de un subconjunto de elementos secundarios que pertenecen a un instancia principal):

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

Esto se puede usar de dos maneras, por ejemplo:

  1. obj = get_unique_or_none(Model, **kwargs) como se discutió anteriormente
  2. obj = get_unique_or_none(Model, parent.children, **kwargs)

1

Sin excepción:

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

Usando una excepción:

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

Hay una pequeña discusión sobre cuándo se debe usar una excepción en python. Por un lado, "es más fácil pedir perdón que permiso". Si bien estoy de acuerdo con esto, creo que una excepción debería permanecer, bueno, la excepción, y el "caso ideal" debería ejecutarse sin golpear a uno.


1

Podemos usar la excepción incorporada de Django que se adjunta a los modelos nombrados como .DoesNotExist. Por lo tanto, no tenemos que importar ObjectDoesNotExistexcepciones.

En cambio haciendo:

from django.core.exceptions import ObjectDoesNotExist

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

Podemos hacer esto:

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

0

Este es un imitador de get_object_or_404 de Django, excepto que el método devuelve Ninguno. Esto es extremadamente útil cuando tenemos que usar la only()consulta para recuperar ciertos campos solamente. Este método puede aceptar un modelo o un conjunto de consultas.

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

Quizás sea mejor que uses:

User.objects.filter(username=admin_username).exists()
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.