Como obtenho o objeto se ele existe ou None se ele não existe?

222

Quando peço ao gerente de modelo que obtenha um objeto, ele aumenta DoesNotExistquando não há nenhum objeto correspondente.

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

Em vez de DoesNotExist, como posso ter gosido None?

TIMEX
fonte

Respostas:

332

Não existe uma maneira 'embutida' de fazer isso. O Django sempre gera a exceção DoesNotExist. A maneira idiomática de lidar com isso em python é envolvê-lo em uma captura de tentativa:

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

O que fiz foi subclassar models.Manager, criar um safe_getcódigo semelhante ao descrito acima e usar esse gerenciador para meus modelos. Dessa forma, você pode escrever: SomeModel.objects.safe_get(foo='bar').

Arthur Debert
fonte
7
Bom uso de SomeModel.DoesNotExist em vez de importar a exceção também.
Supermitch 31/03
195
Esta solução tem quatro linhas. Para mim, isso é demais. Com o Django 1.6, você pode usar SomeModel.objects.filter(foo='bar').first()isso para retornar a primeira correspondência, ou Nenhuma. Ele não falhará se há vários casos comoqueryset.get()
guettli
9
Eu acho que é um estilo ruim usar demais exceções para lidar com casos padrão. Sim, "é mais fácil pedir perdão do que permissão". Mas uma exceção ainda deve ser usada, aos meus olhos, para exceções.
Konstantin Schubert
8
Explícito é melhor que implícito. A menos que haja uma razão de desempenho para usar filter().first(), acho que a exceção é o caminho a percorrer.
214166
6
Usar first () é apenas uma boa idéia se você não se importa quando há múltiplos. Caso contrário, essa solução é superior, porque ainda emitirá uma exceção se você encontrar inesperadamente vários objetos, o que normalmente é o que você gostaria que acontecesse nesse caso.
Rossdavidh 19/0318
181

Desde o django 1.6, você pode usar o método first () assim:

Content.objects.filter(name="baby").first()
FeroxTL
fonte
26
Nesse caso, nenhum erro será gerado se houver mais de uma correspondência.
Konstantin Schubert
7
'FeroxTL', você precisa creditar @guettli por esta resposta, pois ele comentou sobre a resposta aceita um ano antes da sua postagem.
Colm.anseo
7
@colminator Eu prefiro dizer que Guettli deve saber que uma nova resposta não pertence a um comentário, se ele quiser aumentar sua reputação de stackoverflow :) O FeroxTL deve ganhar pontos por tornar algo oculto como um comentário mais claro como resposta. Acho que seu comentário é suficiente para Guettli e não deve ser adicionado à resposta se essa foi sua sugestão.
Joakim
3
@Joakim Não tenho nenhum problema com a publicação de uma nova "resposta" - apenas para dar crédito onde é devido :-)
colm.anseo
3
E o desempenho dessa abordagem em comparação com a resposta aceita?
MaxCore
36

De documentos do django

get()gera uma DoesNotExistexceção se um objeto não for encontrado para os parâmetros fornecidos. Essa exceção também é um atributo da classe de modelo. A DoesNotExist exceção herda dedjango.core.exceptions.ObjectDoesNotExist

Você pode capturar a exceção e atribuir Noneà saída.

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

Você pode criar uma função genérica para isso.

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

Use isto como abaixo:

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

go será None se nenhuma entrada corresponder a outra, retornará a entrada Content.

Nota: Isso gerará a exceção MultipleObjectsReturned se mais de uma entrada for retornada para name = "baby"

Ranju R
fonte
14

Para facilitar, aqui está um trecho do código que escrevi, com base nas entradas das maravilhosas respostas aqui:

class MyManager(models.Manager):

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

E então no seu modelo:

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

É isso aí. Agora você tem MyModel.objects.get () e MyModel.objetcs.get_or_none ()

Moti Radomski
fonte
7
Também, não se esqueça de importar: de django.core.exceptions importar ObjectDoesNotExist
Moti Radomski
13

você poderia usar existscom um filtro:

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

apenas uma alternativa para se você só quer saber se existe

Ryan Saxe
fonte
4
Isso causaria uma chamada extra ao banco de dados quando existir. Não é uma boa ideia
Christoffer
@Christoffer não sabe por que isso seria uma chamada de banco de dados extra. De acordo com os 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 Eu acho que você está certo. Agora, li a pergunta novamente e o OP realmente deseja que o objeto real seja retornado. Então exists()será usado com a ifcláusula antes de buscar o objeto, causando um duplo golpe no banco de dados. Ainda manterei o comentário, caso isso ajude outra pessoa.
Anupam
7

Lidar com exceções em diferentes pontos de suas visualizações pode ser realmente complicado. Que tal definir um Model Manager personalizado, no arquivo models.py, como

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

e depois incluí-lo na classe Model de conteúdo

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

Desta forma, pode ser facilmente tratado nas visualizações, isto é,

post = Content.objects.get_nicely(pk = 1)
if post:
    # Do something
else:
    # This post doesn't exist
Adil Malik
fonte
1
Eu realmente gosto dessa solução, mas não consegui fazê-la funcionar como está ao usar o python 3.6. Queria deixar uma observação que modifica o retorno no ContentManager para que return self.get(**kwargs)ele funcione para mim. Para não dizer que há algo errado com a resposta, apenas uma dica para quem tenta usá-lo com versões posteriores (ou com o que mais o impediu de funcionar para mim).
Skagzilla #
7

É uma daquelas funções irritantes que você talvez não queira reimplementar:

from annoying.functions import get_object_or_None
#...
user = get_object_or_None(Content, name="baby")
mknecht
fonte
Eu verifiquei o código, get_object_or_Nonemas descobri que ele ainda aumenta MultipleObjectsReturnedse houver mais de um objeto. Portanto, o usuário pode considerar cercar com a try-except(que a própria função já possui try-except).
John Pang
4

Se você deseja uma solução simples de uma linha que não envolva manipulação de exceção, instruções condicionais ou um requisito do Django 1.6+, faça o seguinte:

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

Eu acho que não é uma má ideia 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 exemplo é 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.")

Você pode ler mais sobre get_object_or_404 () na documentação online do django.

Mohammad Reza
fonte
2

A partir do django 1.7, você pode fazer o seguinte:

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

A vantagem de "MyQuerySet.as_manager ()" é que ambos os seguintes funcionarão:

MyModel.objects.filter(...).get_or_none()
MyModel.objects.get_or_none()
Vinayak Kaniyarakkal
fonte
1

Aqui está uma variação da função auxiliar que permite passar opcionalmente em uma QuerySetinstância, caso você queira obter o objeto exclusivo (se presente) de um conjunto de consultas que não seja o conjunto de allconsultas de objetos do modelo (por exemplo, de um subconjunto de itens filhos pertencentes a um instância pai):

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

Isso pode ser usado de duas maneiras, por exemplo:

  1. obj = get_unique_or_none(Model, **kwargs) conforme discutido anteriormente
  2. obj = get_unique_or_none(Model, parent.children, **kwargs)
Gary
fonte
1

Sem exceção:

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

Usando uma exceção:

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

Há um pouco de argumento sobre quando alguém deve usar uma exceção em python. Por um lado, "é mais fácil pedir perdão do que permissão". Embora eu concorde com isso, acredito que uma exceção deve permanecer, bem, a exceção e o "caso ideal" deve ser executado sem atingir uma.

Konstantin Schubert
fonte
1

Podemos usar a exceção interna do Django, anexada aos modelos nomeados como .DoesNotExist. Portanto, não precisamos importar ObjectDoesNotExistexceção.

Em vez disso:

from django.core.exceptions import ObjectDoesNotExist

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

Nós podemos fazer isso:

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

Este é um imitador de get_object_or_404 do Django, exceto que o método retorna None. Isso é extremamente útil quando precisamos usar a only()consulta para recuperar apenas determinados campos. Este método pode aceitar um modelo ou um 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
Sandeep Balagopal
fonte
0

Talvez seja melhor você usar:

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