Handling Custom Django Error Pages the Proper Way

2 min read
Handling Custom Django Error Pages the Proper Way

Django 3.0 introduced a convenient way of handling error views, namely handler400, handler403, handler404, and handler500.

As an example from Django, you can simply do this in your project's urls.py.

# urls.py
handler404 = 'mysite.views.my_custom_page_not_found_view'

handler500 = 'mysite.views.my_custom_error_view'

handler403 = 'mysite.views.my_custom_permission_denied_view'

handler400 = 'mysite.views.my_custom_bad_request_view'

However, these handlers act globally. That means if you have 5 different apps in your project, they will share the same error page. This is not a very good side effect.

We should make all apps independent from each other. So how should we really do it?

The proper way

For example we have 2 apps (or more) named blog and demo and we have error endpoints on each app called blog:error and demo:error, we can introduce a function directly inside the project urls.py.

# urls.py
def bad_request_handler(request: WSGIRequest, exception=None):
    if request.path.startswith("/blog/"):
        return redirect("blog:error", error_code=400)
    elif request.path.startswith("/demo/"):
        return redirect("demo:error", error_code=400)
    return defaults.bad_request(request, exception)  # use the default handler


handler400 = bad_request_handler

Notice that I made a redirect here to the error pages. You can also render the templates directly but I choose this way because I do not want to import the views from every app in my urls.py. This always depend on the developer's choice.

Continuing on all the remaining handlers...

# urls.py
def bad_request_handler(request: WSGIRequest, exception=None):
    if request.path.startswith("/blog/"):
        return redirect("blog:error", error_code=400)
    elif request.path.startswith("/demo/"):
        return redirect("demo:error", error_code=400)
    return defaults.bad_request(request, exception)


def permission_denied_handler(request: WSGIRequest, exception=None):
    if request.path.startswith("/blog/"):
        return redirect("blog:error", error_code=403)
    elif request.path.startswith("/demo/"):
        return redirect("demo:error", error_code=403)
    return defaults.permission_denied(request, exception)


def page_not_found_handler(request: WSGIRequest, exception=None):
    if request.path.startswith("/blog/"):
        return redirect("blog:error", error_code=404)
    elif request.path.startswith("/demo/"):
        return redirect("demo:error", error_code=404)
    return defaults.page_not_found(request, exception)


def server_error_handler(request: WSGIRequest):
    if request.path.startswith("/blog/"):
        return redirect("blog:error", error_code=500)
    elif request.path.startswith("/demo/"):
        return redirect("demo:error", error_code=500)
    return defaults.server_error(request)


handler400 = bad_request_handler
handler403 = permission_denied_handler
handler404 = page_not_found_handler
handler500 = server_error_handler

All handlers except 500 are the same. The defaults.server_error() only accept a request parameter. You can check the documentation for the error views from here.

Read more articles like this in the future by buying me a coffee!

Buy me a coffeeBuy me a coffee