Django: obtém um objeto do banco de dados, ou 'Nenhum' se nada corresponder

101

Existe alguma função Django que me permite obter um objeto do banco de dados, ou None se nada corresponder?

No momento, estou usando algo como:

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

Mas isso não está muito claro e é complicado ter em todos os lugares.

David Wolever
fonte
2
Você sabe que pode usar foo = foo [0] if foo else None
Visgean Skeloru
2
Python tem um operador ternário, você não precisa usar operadores booleanos. Além disso, len(foo)é ruim : " Observação: não use len () em QuerySets se tudo o que você deseja fazer é determinar o número de registros no conjunto. É muito mais eficiente lidar com uma contagem no nível do banco de dados, usando SELECT COUNT do SQL (), e o Django fornece um método count () precisamente por esse motivo. ". Reescrito:foo = foo[0] if foo.exists() else None
mustafa.0x
1
@ mustafa.0x Não acho que isso se aplique aqui. Aqui, a verificação da contagem executaria outra consulta. Então teríamos uma consulta novamente para obter os dados reais. Este é um caso em que filtrar e contar fariam mais sentido ... mas não mais sentido do que filtrar e chamar first(): P
MicronXD

Respostas:

88

No Django 1.6 você pode usar o first()método Queryset. Ele retorna o primeiro objeto correspondido pelo queryset, ou None se não houver nenhum objeto correspondente.

Uso:

p = Article.objects.order_by('title', 'pub_date').first()
Cesar Canassa
fonte
19
Esta não é uma boa resposta, se vários objetos forem encontrados, então MultipleObjectsReturned()não é gerado, como documentado aqui . Você pode encontrar algumas respostas melhores aqui
sleepycal
25
@sleepycal eu discordo. As first()obras exatamente como supor. Ele retorna o primeiro objeto no resultado da consulta e não se importa se vários resultados forem encontrados. Se você precisar verificar se há vários objetos retornados, você deve usar .get().
Cesar Canassa
7
[editado] Como disse o OP, .get()não é adequado para suas necessidades. A resposta correta para esta pergunta pode ser encontrada abaixo por @kaapstorm e é claramente a resposta mais adequada. Abusar filter()dessa forma pode levar a consequências inesperadas, algo que OP provavelmente não percebeu (a menos que eu esteja faltando alguma coisa aqui)
sleepycal
1
@sleepycal Que consequências não intencionais surgem desta abordagem?
HorseloverFat
12
Se suas restrições de banco de dados não gerenciam corretamente a exclusividade entre os objetos, então você pode atingir casos extremos onde sua lógica espera que haja apenas um único objeto (que é selecionado por first ()), mas na realidade existem vários objetos. Usar get () em vez de first () oferece uma camada extra de proteção, aumentando MultipleObjectsReturned(). Se não se espera que o resultado retornado retorne vários objetos, ele não deve ser tratado como tal. Houve um longo debate sobre isso aqui
sonolento
139

Existem duas maneiras de fazer isso;

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

Ou você pode usar um wrapper:

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

Chame assim

foo = get_or_none(Foo, baz=bar)
Nick Craig-Wood
fonte
8
Não gosto do fato de ele usar exceções para funcionalidade - essa é uma prática ruim na maioria dos idiomas. Como é isso em python?
pcv
18
É assim que a iteração Python funciona (gera uma StopIteration), e essa é uma parte central da linguagem. Não sou um grande fã da funcionalidade try / except, mas parece ser considerada Pythonic.
Matt Luongo
4
@pcv: não existe uma sabedoria convencional confiável sobre a "maioria" das linguagens. Suponho que você pense em alguma linguagem específica que usa, mas tome cuidado com esses quantificadores. As exceções podem ser tão lentas (ou "rápidas o suficiente" quanto a isso), como qualquer coisa em Python. Voltando à questão - é uma lacuna do framework, que eles não forneciam nenhum querysetmétodo para fazer isso antes de 1.6! Mas quanto a esta construção - é simplesmente desajeitada. Não é "mal" porque algum autor de tutorial da sua língua favorita disse isso. Experimente o google: "peça perdão em vez de permissão".
Tomasz Gandor
@TomaszGandor: Sim, é desajeitado e difícil de ler, não importa o que seu tutorial de idioma favorito diga. Quando vejo uma exceção, suponho que algo fora do padrão está acontecendo. A legibilidade conta. Mas concordo quando não há outra opção razoável, você deve tentar e nada de ruim acontecerá.
pcv
@pcv O getmétodo não deve retornar None, portanto, uma exceção faz sentido. Você deve usá-lo em situações em que tem certeza de que o objeto estará lá / o objeto "deveria" estar lá. Também usar exceções como essa é considerado 'bom' porque, bem, faz sentido. Você quer um getcom um contrato diferente, então captura a exceção e a suprime, que é a mudança que você deseja.
Dan Passaro
83

Para adicionar um código de amostra à resposta de sorki (eu adicionaria isso como um comentário, mas esta é minha primeira postagem e não tenho reputação suficiente para deixar comentários), implementei um gerenciador personalizado get_or_none assim:

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

E agora posso fazer isso:

bob_or_none = Person.objects.get_or_none(name='Bob')
Kaapstorm
fonte
Isso é muito elegante.
NotSimon
13

Você também pode tentar usar o django irritante (tem outras funções úteis!)

instale-o com:

pip install django-annoying

from annoying.functions import get_object_or_None
get_object_or_None(Foo, bar=baz)
Llazzaro
fonte
9

Dê a Foo seu gerenciador personalizado . É muito fácil - basta colocar seu código em função no gerenciador personalizado, definir o gerenciador personalizado em seu modelo e chamá-lo com Foo.objects.your_new_func(...).

Se você precisar de uma função genérica (para usá-la em qualquer modelo, não apenas com o gerenciador personalizado), escreva a sua própria e coloque-a em algum lugar do caminho do Python e importe, não bagunce mais.

Sorki
fonte
4

Seja por meio de um gerenciador ou função genérica, você também pode querer capturar 'MultipleObjectsReturned' na instrução TRY, já que a função get () levantará isso se seus kwargs recuperarem mais de um objeto.

Com base na função 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

e no gerente:

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
fonte
Isso parece capturar todos os melhores pontos das outras respostas. Boa :)
Edward Newell
@emispowder, e se eu não quiser retornar None, eu quiser retornar uma mensagem de alerta como "nenhum dado correspondente" exibido na MESMA página?
Héléna
0

Aqui está uma variação da função auxiliar que permite que você opcionalmente passe em uma QuerySetinstância, no caso de você querer obter o objeto único (se presente) de um queryset diferente dos allobjetos queryset do modelo (por exemplo, de um subconjunto de itens filhos pertencentes a um instância pai):

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

Isso pode ser usado de duas maneiras, por exemplo:

  1. obj = get_unique_or_none(Model, *args, **kwargs) como previamente discutido
  2. obj = get_unique_or_none(Model, parent.children, *args, **kwargs)
Gary
fonte
-1

Acho que na maioria dos casos você pode apenas usar:

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

Somente se não for crítico que uma nova entrada seja adicionada na tabela Foo (outras colunas terão os valores Nenhum / padrão)

TomerP
fonte