Django tips: Recovering Gracefully From ORM Errors

How do you handle ORM errors in Django? Learn how to make your Django function views more robust with try/except (or with a neat shortcut).

Django: Recovering Gracefully From ORM Errors

Django views are the glue between your users and the underlying database. When a user visits an url Django can map that url with a view. And most of the times the view is also responsible for fetching some data from the database.

Django views and the ORM

Consider the following example. There is a model named Workshop which among the others has a slug field:

"""
models.py
"""

from django.db import models


class Workshop(models.Model):
    """
    ... some other field here
    """
    slug = models.SlugField(max_length=20)
    """
    ... some other field here
    """

    def __str__(self):
        return f'{self.title}'

You may want to use that slug for fetching the appropriate entity from the database when the user visits someurl/slugname/. That means you will have an url declaration like so:

"""
urls.py of your Django app
"""

from django.urls import path
from .views import workshop_detail


urlpatterns = [
    path('someurl/<slug:slug>/', workshop_detail)
]

At this point you’re ready to create a Django view for displaying the model detail. I’ll use a function view for keeping things easy. A naive implementation for the view could be the following:

"""
views.py of your Django app
"""

from django.shortcuts import render
from .models import Workshop


def workshop_detail(request, slug):
    workshop = Workshop.objects.get(slug=slug)
    context = { 'workshop': workshop }
    return render(request,
                  'yourapp/workshop_detail.html',
                  context=context)

The view should fetch a model matching the given slug. Django has a fantastic ORM and we can use the get() method for obtaining the model:

    workshop = Workshop.objects.get(slug=slug)

Next up we build a context object for holding our model and maybe some other stuff for the actual template:

    context = { 'workshop': workshop }

Finally we can render the template alongside with the context:

    return render(request,
                  'yourapp/workshop_detail.html',
                  context=context)

At this point we’re ready to smoke test our Django app in the browser. But first let’s talk a moment about get().

Recovering Gracefully From ORM Errors: objects.get()

The get() method returns the matching model as long as said model exists. If you run your Django app straight away without creating any entity inside the database, well your view will fail miserably. The same holds true for any model that does not exist yet. If the user goes to /someurl/py/ he/she is welcomed with a scary error:

Django matching query does not exist.

Model matching query does not exist. get() in fact raises a DoesNotExists exception when it can’t find any model matching the query. That’s reasonable! But from a UX perspective it is a bit rough four our users.

So what’s the solution? Luckily we can catch the exception with try/except for gracefully handling the error. First make sure to import ObjectDoesNotExist in your view:

"""
views.py of your Django app
"""

from django.shortcuts import render
from .models import Workshop
from django.core.exceptions import ObjectDoesNotExist

"""
actual view here
"""

Next up we wrap get() and render in a try/except block:

"""
views.py of your Django app
"""

from django.shortcuts import render
from .models import Workshop
from django.core.exceptions import ObjectDoesNotExist


def workshop_detail(request, slug):
    try:
        workshop = Workshop.objects.get(slug=slug)
        context = { 'workshop': workshop }
        return render(request,
                    'yourapp/workshop_detail.html',
                    context=context)
    except ObjectDoesNotExist:
        """
        do something here
        """
        pass

The above code tries to fetch an entity from the database and then renders a template to the user.

But if the query Workshop.objects.get(slug=slug) fails the error is caught by the except block.

What to do in the except is up to you. Maybe logging the error? More important though, we can continue executing the code outside try/except and show a courtesy message for our users.

For example you can import HttpResponseNotFound in your view and then render a generic error:

"""
views.py of your Django app
"""

from django.shortcuts import render
from .models import Workshop
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponseNotFound


def workshop_detail(request, slug):
    try:
        workshop = Workshop.objects.get(slug=slug)
        context = { 'workshop': workshop }
        return render(request,
                    'yourapp/workshop_detail.html',
                    context=context)
    except ObjectDoesNotExist:
        """
        do something here
        """
        pass
    return HttpResponseNotFound('Oops! Not found!')

Even better you can show a nice 404 page! But what if I tell you there is a cleaner way for dealing with this kind of situation?

Django Tips: Recovering Gracefully From ORM errors. Going fancy with get_object_or_404

You don’t want to import ObjectDoesNotExist, HttpResponseNotFound, and wrap everything in try/except all over the place. Django offers a cleaner way for dealing with errors coming from the ORM.

There is a shortcut called get_object_or_404 doing the hard work for you. get_object_or_404 does two things:

it calls Modelname.object.get() with the desired arguments

it raises an Http404 if no instance of the model is found

And this is convenient because when using HttpResponseNotFound you should also provide some HTML for the user. But if we just raise an Http404 Django catches the exception and returns a 404 page for us.

Also, when DEBUG is True you’ll see a “page not found” error. In production with DEBUG=False the user will see a generic 404 page. So back to our code, we can refactor like so:

"""
views.py of your Django app
"""

from django.shortcuts import render, get_object_or_404
from .models import Workshop


def workshop_detail(request, slug):
    workshop = get_object_or_404(Workshop, slug=slug)
    context = { 'workshop': workshop }
    return render(request,
                  'yourapp/workshop_detail.html',
                  context=context)

Look how cleaner is that! We get rid of ObjectDoesNotExist, HttpResponseNotFound, and try/except. And the result is even better!

Wrapping up

try/except is your friend whenever you want to recover from a fatal error in Python. It is also useful for dealing with Django ORM methods that can potentially raise exceptions, like objects.get().

The get() method raises when it cannot find the desired model in the database. That’s a good reason for wrapping the call within a safe block. But cluttering your code with try/except is not always the right thing to do.

Luckily Django has get_object_or_404 which wraps get() in a try/except and raises for us an Http404. Django is then able to catch the Http404 for showing a Not found page to our users.

In this post you learned how to make your Django function view more robust with try/except and get_object_or_404, the latter being more convenient and idiomatic.

Thanks for reading and stay tuned on this blog!

New to Python? Learn more about exceptions in Python exceptions: an introduction.

Need more on Django? Check out Django REST with React (and a sprinkle of testing).

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.