Como você pega essa exceção?

162

Este código está no django / db / models / fields.py Ele cria / define uma exceção?

class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescriptorMethods)):
    # This class provides the functionality that makes the related-object
    # managers available as attributes on a model class, for fields that have
    # a single "remote" value, on the class that defines the related field.
    # In the example "choice.poll", the poll attribute is a
    # ReverseSingleRelatedObjectDescriptor instance.
    def __init__(self, field_with_rel):
        self.field = field_with_rel
        self.cache_name = self.field.get_cache_name()

    @cached_property
    def RelatedObjectDoesNotExist(self):
        # The exception can't be created at initialization time since the
        # related model might not be resolved yet; `rel.to` might still be
        # a string model reference.
        return type(
            str('RelatedObjectDoesNotExist'),
            (self.field.rel.to.DoesNotExist, AttributeError),
            {}
        )

Isso está no django / db / models / fields / related.py e gera a referida exceção acima:

def __get__(self, instance, instance_type=None):
    if instance is None:
        return self
    try:
        rel_obj = getattr(instance, self.cache_name)
    except AttributeError:
        val = self.field.get_local_related_value(instance)
        if None in val:
            rel_obj = None
        else:
            params = dict(
                (rh_field.attname, getattr(instance, lh_field.attname))
                for lh_field, rh_field in self.field.related_fields)
            qs = self.get_queryset(instance=instance)
            extra_filter = self.field.get_extra_descriptor_filter(instance)
            if isinstance(extra_filter, dict):
                params.update(extra_filter)
                qs = qs.filter(**params)
            else:
                qs = qs.filter(extra_filter, **params)
            # Assuming the database enforces foreign keys, this won't fail.
            rel_obj = qs.get()
            if not self.field.rel.multiple:
                setattr(rel_obj, self.field.related.get_cache_name(), instance)
        setattr(instance, self.cache_name, rel_obj)
    if rel_obj is None and not self.field.null:
        raise self.RelatedObjectDoesNotExist(
            "%s has no %s." % (self.field.model.__name__, self.field.name)
        )
    else:
        return rel_obj

O problema é que este código:

    try:
        val = getattr(obj, attr_name)
    except related.ReverseSingleRelatedObjectDescriptor.RelatedObjectDoesNotExist:
        val = None  # Does not catch the thrown exception
    except Exception as foo:
        print type(foo)  # Catches here, not above

não vai pegar essa exceção

>>>print type(foo)
<class 'django.db.models.fields.related.RelatedObjectDoesNotExist'>
>>>isinstance(foo, related.FieldDoesNotExist)
False

e

except related.RelatedObjectDoesNotExist:

Gera um AttributeError: 'module' object has no attribute 'RelatedObjectDoesNotExist'

>>>isinstance(foo, related.ReverseSingleRelatedObjectDescriptor.RelatedObjectDoesNotExist)
Traceback (most recent call last):
  File "<string>", line 1, in <fragment>
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

e provavelmente é por isso.

boatcoder
fonte
Como você importou related?
John Zwinck
4
usar AttributeError, em vez derelated.ReverseSingleRelatedObjectDescriptor.RelatedObjectDoesNotExist
Catherine
Sim @JohnZwinck eu importei relacionados.
boatcoder

Respostas:

302

Se o seu modelo relacionado for chamado Foo, você poderá:

except Foo.DoesNotExist:

Django é incrível quando não é aterrorizante. RelatedObjectDoesNotExisté uma propriedade que retorna um tipo que é descoberto dinamicamente no tempo de execução. Esse tipo usa self.field.rel.to.DoesNotExistcomo uma classe base. De acordo com a documentação do Django:

ObjectDoesNotExist e DoesNotExist

exceção DoesNotExist

O DoesNotExist excepção é aumentada quando um objecto não é encontrada para os parâmetros indicados de uma consulta. O Django fornece uma exceção DoesNotExist como um atributo de cada classe de modelo para identificar a classe de objeto que não pôde ser encontrada e permitir que você capture uma determinada classe de modelo com try/except .

Essa é a mágica que faz isso acontecer. Depois que o modelo foi construído, self.field.rel.to.DoesNotExista exceção não existe para esse modelo.

tdelaney
fonte
7
Porque o bug estava no DjangoRestFramework, e o modelo é um pouco difícil de encontrar nesse momento. Eu decidi pegar ObjectDoesNotExist.
boatcoder
3
Você também pode usar o AttributeError, que em determinadas circunstâncias pode ser uma opção melhor (esse erro quase sempre ocorre quando você está acessando o "atributo" de um registro, portanto, dessa forma, você não precisa acompanhar se esse atributo corresponde a um atributo gravar ou não. #
3155 Jordan Reiter
1
Sim, tudo bem. Mas por que não pode simplesmente retornar Nenhum? Especialmente com campos um a um. Ou existe uma boa razão?
Neil
61

Se você não deseja importar a classe de modelo relacionada, pode:

except MyModel.related_field.RelatedObjectDoesNotExist:

ou

except my_model_instance._meta.model.related_field.RelatedObjectDoesNotExist:

onde related_fieldé o nome do campo.

Fush
fonte
7
isso é realmente bastante útil caso você precise evitar importações circulares. Obrigado
Giovanni Di Milia
40

Para capturar essa exceção em geral, você pode fazer

from django.core.exceptions import ObjectDoesNotExist

try:
    # Your code here
except ObjectDoesNotExist:
    # Handle exception
Zags
fonte
2
Descobri que isso não detectou o erro conforme o esperado. O que <Model>.DoesNotExistaconteceu
Eric Blum
1
@EricBlum <Modelo> .DoesNotExist é um descendente de ObjectDoesNotExist, portanto isso não deve acontecer. Você pode descobrir por que isso está acontecendo ou fornecer mais detalhes sobre seu código?
Zags
10

A RelatedObjectDoesNotExistexceção é criada dinamicamente no tempo de execução. Aqui está o trecho de código relevante para os descritores ForwardManyToOneDescriptore ReverseOneToOneDescriptor:

@cached_property
def RelatedObjectDoesNotExist(self):
    # The exception can't be created at initialization time since the
    # related model might not be resolved yet; `self.field.model` might
    # still be a string model reference.
    return type(
        'RelatedObjectDoesNotExist',
        (self.field.remote_field.model.DoesNotExist, AttributeError),
        {}
    )

Portanto, a exceção herda de <model name>.DoesNotExiste AttributeError. De fato, o MRO completo para esse tipo de exceção é:

[<class 'django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist'>, 
<class '<model module path>.DoesNotExist'>,
<class 'django.core.exceptions.ObjectDoesNotExist'>,
<class 'AttributeError'>,
<class 'Exception'>,
<class 'BaseException'>,
<class 'object'>]

O takeaway básica é que você pode pegar <model name>.DoesNotExist, ObjectDoesNotExist(importação de django.core.exceptions) ou AttributeError, o que faz mais sentido em seu contexto.

CS
fonte
2

A resposta de tdelaney é ótima para caminhos de código regulares, mas se você precisar saber como capturar essa exceção nos testes:

from django.core.exceptions import ObjectDoesNotExist

...

    def testCompanyRequired(self):
        with self.assertRaises(ObjectDoesNotExist):
            employee = Employee.objects.create()
LisaD
fonte
2

Um pouco tarde, mas útil para os outros.

2 maneiras de lidar com isso.

1º:

Quando precisamos pegar uma exceção

>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
>>>     p2.restaurant
>>> except ObjectDoesNotExist:
>>>     print("There is no restaurant here.")
There is no restaurant here.

2º: quando não quiser lidar com exceção

>>> hasattr(p2, 'restaurant')
False
Muhammad Faizan Fareed
fonte