How to customize Python / Flask Web Application Error Pages?
Tags: python, webdev, tutorial, beginners
Published: 2020-05-16 15:15:39

Applications could fail in the production environment due to several reasons. Reasons for failure may not always be issues within the application code. Errors could also be due to external factors such as network connectivity between application and application’s database, or user aborting request in the middle of a file upload or download.

It is a common practice to show custom error pages when errors like these happen in production environments. Also, it is fairly straightforward to show custom error pages in the application. 

This tutorial walks through an example application containing custom error pages.

* Create project directory - `mkdir app`
* Navigate to the project directory and set up a virtual environment for the app. Instructions for setting up virtual environment is found <a href="https://dev.to/dev0928/what-is-a-python-virtual-environment-2hkn">here</a>
* Install Flask framework from the project directory
 ```python
(venv) $ pip install flask 
```
* Application is going to contain a landing page with two links that simulate custom error page display for the two most common errors: 404 - page not found and 500 - internal server error. Here is application's directory structure: ![Application Structure](https://dev-to-uploads.s3.amazonaws.com/i/2vpei859b6hiwgh1w39q.png)
* First, we are going to create the view portion of the project by creating necessary files in the templates folder. Create a new sub-folder called templates in the project directory. Add below HTML files to the template folder.
* **base.html:** This file contains header information and is going to be included as part of other HTML files. Please note I am using Bootstrap 4 from CDN for basic styling.  
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <title>Custom Error App</title>
    </head>
    <body>
        <div class="jumbotron">
            <h1 class="display-4">Custom Error Application</h1>
            <p class="lead">This is a simple Python / Flask web application demonstrating custom error functionality</p>
        </div>
        <div class="container-fluid">
        {% block content %}{% endblock %}
        </div>
    </body>
</html> 
```
- **index.html:** This is the landing page of the app. This page contains links for simulating errors in the app.
```html
{% extends "base.html" %}

{% block content %}
    <h3>Welcome to Home Page </h3>

    <p><a href="{{ url_for('simulate404') }}">Click link to simulate 404 error</a></p>

    <p><a href="{{ url_for('simulate500') }}">Click link to simulate 500 error</a></p>

{% endblock %} 
```
- **404.html:** This file contains custom error page 404 errors.
```html
{% extends "base.html" %}

{% block content %}
    <h3>Error Page - 404</h3>
    <div class="alert alert-danger" role="alert">The URL you are trying to access is not found. Please verify.</div>
    <p><a href="{{ url_for('index') }}">Back</a></p>
{% endblock %}
```
- **500.html:** This file contains custom error page 500 errors.
```html
{% extends "base.html" %}

{% block content %}
    <h3>Error Page - 500</h3>
    <div class="alert alert-danger" role="alert">The server encountered an internal error and was unable to complete your request. 
        Site administrators are aware of the issue. Please try back later.</div>
    <p><a href="{{ url_for('index') }}">Back</a></p>
{% endblock %}
```
- **more.html:** This is the target page for home page links if there are no errors in the visited links. 
```html
{% extends "base.html" %}

{% block content %}
    <h3>More Page</h3>
{% endblock %}
```
* Finally, create a file called app.py in the application’s root folder.
```python
from flask import Flask, render_template, request, abort
import werkzeug

app = Flask(__name__)

@app.route("/")
def index():
    return render_template("index.html")

@app.route("/simulate404")
def simulate404():
    abort(404)
    return render_template("more.html")

@app.route("/simulate500")
def simulate500():
    abort(500)
    return render_template("more.html")

@app.errorhandler(404)
def not_found_error(error):
    return render_template('404.html'), 404

@app.errorhandler(werkzeug.exceptions.HTTPException)
def internal_error(error):
    return render_template('500.html'), 500
```
<h3>How app.py is setup?</h3>
* I am using an abort command with an appropriate error code to simulate error in the application.
* `@app.errorhandler(<error code>)` decorator is used to define custom error routes.
* Error handlers could also be registered using register_error_handler method: `app.register_error_handler(<error_code>, <error_function>)`
* In error handler routes, render_template returns appropriate response code while sending the response. Normal routes don’t have a response code as it is defaulted to 200.
* Error handlers could also be registered with an exception name as shown in the internal server error handler.

<h3>Flask Error Handling Behavior</h3>
* Error handlers work hierarchically in choosing a suitable error handler route. When error happens in an application, the framework finds the most specific error handler within the application / module.
* For example, if there are custom handlers available for Exception and HTTPException and if HTTPException happens in the application, HTTPException handler would be used for handling the exception as it is more specific than Exception error handler.
* If there are no custom error handler implementations in the application, a generic error page is displayed by the framework.

Here is an illustration of Python error handler hierarchy:
![Error Handler Hierarchy](https://dev-to-uploads.s3.amazonaws.com/i/zhamsgn8h8exaqb4ffp8.png)

Below images show application's landing page along with a sample custom error page generated through the app:
![Landing Page](https://dev-to-uploads.s3.amazonaws.com/i/ntx3cvmiy47vumry7wxi.png)
![Error Output](https://dev-to-uploads.s3.amazonaws.com/i/dmqsgp9ugtd53ogp3lkd.png)

Sample application's source code be downloaded from <a href="https://github.com/dev0928/custom-error-app">here</a>