Django: obtenga un objeto de la base de datos, o 'Ninguno' si nada coincide

101

¿Hay alguna función de Django que me permita obtener un objeto de la base de datos, o Ninguno si nada coincide?

Ahora mismo estoy usando algo como:

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

Pero eso no está muy claro y es complicado tenerlo en todas partes.

David Wolever
fuente
2
Sabes que puedes usar foo = foo [0] si foo else None
Visgean Skeloru
2
Python tiene un operador ternario, no es necesario utilizar operadores booleanos. Además, len(foo)es malo : " Nota: No use len () en QuerySets si todo lo que quiere hacer es determinar la cantidad de registros en el conjunto. Es mucho más eficiente manejar un recuento a nivel de base de datos, usando SELECT COUNT de SQL (), y Django proporciona un método count () precisamente por esta razón. ". Reescrito:foo = foo[0] if foo.exists() else None
mustafa.0x
1
@ mustafa.0x No creo que eso se aplique aquí. Aquí, la verificación del recuento ejecutaría otra consulta. Luego tendríamos una consulta nuevamente para obtener los datos reales. Este es un caso en el que filtrar y luego contar tendría más sentido ... pero no más sentido que filtrar y llamar first(): P
MicronXD

Respuestas:

88

En Django 1.6 puedes usar el first()método Queryset. Devuelve el primer objeto que coincide con el conjunto de consultas, o Ninguno si no hay ningún objeto que coincida.

Uso:

p = Article.objects.order_by('title', 'pub_date').first()
Cesar Canassa
fuente
19
Esta no es una buena respuesta, si se encuentran varios objetos, MultipleObjectsReturned()no se genera, como se documenta aquí . Puede encontrar algunas respuestas mejores aquí
sleepycal
25
@sleepycal No estoy de acuerdo. Las first()obras exactamente como se supone que debe. Devuelve el primer objeto en el resultado de la consulta y no le importa si se encuentran varios resultados. Si necesita verificar varios objetos devueltos, debe usar .get()en su lugar.
Cesar Canassa
7
[editado] Como dijo el OP, .get()no es adecuado para sus necesidades. La respuesta correcta a esta pregunta se puede encontrar a continuación en @kaapstorm, y es claramente la respuesta más adecuada. Abusar de filter()esta manera puede llevar a consecuencias inesperadas, algo de lo que OP probablemente no se dio cuenta (a menos que me esté perdiendo algo aquí)
sleepycal
1
@sleepycal ¿Qué consecuencias no deseadas surgen de este enfoque?
HorseloverFat
12
Si las restricciones de su base de datos no administran correctamente la unicidad entre los objetos, entonces puede llegar a casos extremos en los que su lógica espera que haya un solo objeto (que es seleccionado por first ()), pero en realidad existen múltiples objetos. Usar get () en lugar de first () le brinda una capa adicional de protección al aumentar MultipleObjectsReturned(). Si no se espera que el resultado devuelto devuelva varios objetos, no debe tratarse como tal. Hubo un largo debate sobre esto aquí
sleepycal
139

Hay dos maneras de hacer esto;

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

O puede usar una envoltura:

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

Llámalo así

foo = get_or_none(Foo, baz=bar)
Nick Craig-Wood
fuente
8
No me gusta el hecho de que esté usando excepciones para la funcionalidad; esta es una mala práctica en la mayoría de los idiomas. ¿Cómo es eso en Python?
pcv
18
Así es como funciona la iteración de Python (genera una StopIteration), y esa es una parte central del lenguaje. Tampoco soy un gran admirador de la funcionalidad try / except, pero parece que se considera Pythonic.
Matt Luongo
4
@pcv: no existe una sabiduría convencional confiable sobre "la mayoría" de los idiomas. Supongo que piensas en algún lenguaje específico que usas, pero ten cuidado con esos cuantificadores. Las excepciones pueden ser tan lentas (o "lo suficientemente rápidas" para el caso), como cualquier cosa en Python. Volviendo a la pregunta, es una deficiencia del marco, ¡que no proporcionaron ningún querysetmétodo para hacer esto antes de 1.6! Pero en cuanto a esta construcción, es simplemente torpe. No es "malvado" porque así lo dijo algún autor de tutoriales de tu idioma favorito. Prueba google: "pide perdón en lugar de permiso".
Tomasz Gandor
@TomaszGandor: Sí, es torpe y difícil de leer, diga lo que diga tu tutorial de idioma favorito. Cuando veo una excepción, supongo que está sucediendo algo no estándar. La legibilidad cuenta. Pero estoy de acuerdo cuando no hay otra opción razonable, debes hacerlo y no pasará nada malo.
pcv
@pcv Se supone que el getmétodo no regresa None, por lo que una excepción tiene sentido. Se supone que debe usarlo en casos en los que esté seguro de que el objeto estará allí / se "supone" que el objeto está allí. También el uso de excepciones como esta se considera "correcto" porque, bueno, tiene sentido. Quiere un getcontrato con un contrato diferente, por lo que detecta la excepción y la suprime, que es el cambio que busca.
Dan Passaro
83

Para agregar un código de muestra a la respuesta de Sorki (agregaría esto como un comentario, pero esta es mi primera publicación y no tengo suficiente reputación para dejar comentarios), implementé un administrador personalizado get_or_none así:

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

Y ahora puedo hacer esto:

bob_or_none = Person.objects.get_or_none(name='Bob')
Kaapstorm
fuente
Esto es muy elegante.
NotSimon
13

También puede intentar usar django molesto (¡tiene otras funciones útiles!)

instalarlo con:

pip install django-annoying

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

Dale a Foo su administrador personalizado . Es bastante fácil: simplemente ponga su código en función en el administrador personalizado, configure el administrador personalizado en su modelo y llámelo con Foo.objects.your_new_func(...).

Si necesita una función genérica (para usarla en cualquier modelo, no solo en el administrador personalizado), escriba la suya y colóquela en algún lugar de su ruta de Python e importe, ya no se ensucie.

Sorki
fuente
4

Ya sea que lo haga a través de un administrador o una función genérica, es posible que también desee capturar 'MultipleObjectsReturned' en la instrucción TRY, ya que la función get () generará esto si sus kwargs recuperan más de un objeto.

Sobre la base de la función genérica:

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

y en el administrador:

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
fuente
Esto parece capturar todos los mejores puntos de las otras respuestas. Bonito :)
Edward Newell
@emispowder, ¿qué pasa si no quiero devolver Ninguno, quiero devolver un mensaje de alerta como "sin datos coincidentes" que se muestra en la MISMA página?
Héléna
0

Aquí hay una variación de 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, *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

Esto se puede utilizar de dos formas, por ejemplo:

  1. obj = get_unique_or_none(Model, *args, **kwargs) como se discutió previamente
  2. obj = get_unique_or_none(Model, parent.children, *args, **kwargs)
Gary
fuente
-1

Creo que en la mayoría de los casos puedes usar:

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

Solo si no es crítico que se agregue una nueva entrada en la tabla Foo (otras columnas tendrán los valores Ninguno / predeterminado)

TomerP
fuente