DEV Community

Li
Li

Posted on

About DoesNotExist Exception with Django ForeignKey

For a model defined in Django,consider, a foreign key field pointed to User:

class Article(Model):
    title = CharField()
    content = TextField()
    user = ForeignKey(to=get_user_model())
Enter fullscreen mode Exit fullscreen mode

If article is an instance of Article, article.user would get a User instance(base on get_user_model()) based on the foreign key defined.

If you happen to a project where the data consistency is not well maintained, e.g., the admin had deleted several old users despite the data dependency. When calling article.user, you may get a DoesNotExist error, mostly look like

DoesNotExist: User matching query does not exist. 
Enter fullscreen mode Exit fullscreen mode

From the source of Django, I found a comment in db/models/fields/related_descriptors.py:

# Assuming the database enforces foreign keys, this won't fail.
Enter fullscreen mode Exit fullscreen mode

This means when dealing with the consistency of ForeignKey, Django tends to simply raise an error and leave it to the database admin. But in some cases, if we can resolve article.user as a None object, it would be easier for the code to handle, or more compatible with other serializers/validators.

I monkey-patched the module with the following code:

from django.db.models.fields.related_descriptors import ForwardManyToOneDescriptor

def get_object(self, instance):
    qs = self.get_queryset(instance=instance)
    # Assuming the database enforces foreign keys, this won't fail.
    return qs.filter(self.field.get_reverse_related_filter(instance)).first()

ForwardManyToOneDescriptor.get_object = get_object
Enter fullscreen mode Exit fullscreen mode

The code replaces query_set.get with query_set.first, no exceptions raised but return a None when object does not exist. By the way, when if the uniqueness of the foreign key object can be assured, e.g. the foreign key is the primary key in the table related, query_set.first is faster than query_set.get, since it stops scanning the table on the first row matched.

Top comments (0)