Feb 18 2012

Tips for creating 404 ‘page not found’ and 500 ‘server error’ templates in Django, plus configuring email alerts

One of the steps that you have to take [at least] before deploying a Django project to production is to create templates for 404 (page not found) and 500 (server error) errors.   You can also setup some error and broken link reporting.  Here are a few tips:

Where the 404 and 500 templates live

The 404.html and 500.html templates live in the root of your templates directory (TEMPLATE_DIRS setting).

Creating the 404 template

The 404.html template is shown when a page is not found.  It is often a good idea to maintain the overall look and feel of the site even if a piece of content is not found.  As it is good practice to use template inheritance with Django’s templating system, create a 404.html template that extends your base template.  Here is an example:

 HTML |  copy code |? 
01
{% extends "base.html" %}
02
{% load i18n %}
03
 
04
{% block title %}Page not found{% endblock %}
05
 
06
{% block content %}
07
<h1>Page not found</h1>
08
 
09
<p>Sorry, but the requested page could not be found.</p>
10
{% endblock %}

Testing the 404 template

It is very easy to test your 404.html template.  Simply change DEBUG to False in settings.py and try visiting a non-existent URL.  Your 404.html page not found template should be served.

Creating the 500 template

The 500.html template is shown when there is some kind of catastrophic server error.  Since you cannot be sure what the root cause of the issue is, you cannot rely upon the entire Django framework being available and should therefore keep it decoupled from everything.  A simple HTML file is a good choice.  Here is an example:

 HTML |  copy code |? 
01
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
02
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
03
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
04
 
05
<head>
06
 <title>Page unavailable</title>
07
</head>
08
 
09
<body>
10
 <h1>Page unavailable</h1>
11
 
12
 <p>Sorry, but the requested page is unavailable due to a server hiccup.</p>
13
 
14
 <p>Our engineers have been notified, so check back later.</p>
15
</body>
16
 
17
</html>

Testing the 500 template

Testing the 500.html template is also easy.  If a 404 (page not found) error occurs and Django cannot find a 404.html template, it will return a 500 error and therefore the 500.html template.  Just like testing the 404.html template, be sure that DEBUG is set to False in settings.py.  Temporarily rename your 404.html file to something like 404.html.bak.  Try visiting a non-existent URL and you should be served the 500.html server error template.

Configuring error alert emails

Django can email a list of appropriate folks whenever an unhandled exception occurs, including a trace, variables, and settings.  First, in settings.py, add the appropriate names and email addresses to ADMINS, such as

 Python |  copy code |? 
1
ADMINS = (
2
    # ('Your Name', 'your_email@example.com'),
3
    ('John Doe', 'johndoe@gmail.com'),
4
    ('Jane Doe', 'janedoe@gmail.com'),
5
)

Second, configure Django to send email.  This isn’t hard if you look at the documentation.  In a nutshell, you need to configure the settings EMAIL_HOST in settings.py and possibly EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, EMAIL_PORT, or EMAIL_USE_TLS depending upon how you are sending your emails.  I’m running hMailServer on my Windows development machine and so I only set EMAIL_HOST to localhost and did not have to configure those other options.  I’ve configured hMailServer to relay through Google’s SMTP server using my gmail credentials.  (hMailServer offers some nice abilities to monitor SMTP queues and logging to debug email issues.)  One last gotchya: you will likely have to set SERVER_EMAIL to something other than the default root@localhost, as some SMTP servers block this, which gmail does.  It took me a little while to figure out why my emails weren’t being sent, so if you don’t see them come out the other side, make sure SERVER_EMAIL is set to something like django@myhost.com.

You can easily test this to make sure the emails are being sent by following the steps to test your 500.html server error template described above.  Out the other end you should receive an email with an error TemplateDoesNotExist: 404.html.

Configuring broken link alert emails

As with errors, Django can email a list of appropriate folks whenever a 404 page not found error is raised with a non-empty referrer.  If you want email sent for these broken links you need to make sure that CommonMiddleware is installed (it is by default).  Then edit your settings.py file and add the appropriate names and emails to MANAGERS.  By default, MANAGERS it set to ADMINS, which is probably fine for most of us. You then need to set SEND_BROKEN_LINK_EMAILS to True.

 Python |  copy code |? 
1
# We want to be notified of 404s via email to the MANAGERS
2
SEND_BROKEN_LINK_EMAILS = True
3
 
4
MANAGERS = ADMINS


Feb 17 2012

A better way to set your Django template directory setting — dynamically

When using Django you must specify where the framework can find your templates by setting the TEMPLATE_DIRS setting in settings.py.  Typically I just set it to a static path, but recently came across a great tip about how to set it dynamically (thanks to The Definitive Guide to Django).

In settings.py, I would usually specify something like this:

 Python |  copy code |? 
1
TEMPLATE_DIRS = (
2
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
3
    # Always use forward slashes, even on Windows.
4
    # Don't forget to use absolute paths, not relative paths.
5
    "C:/MyDjango/myproject/mytemplates"
6
)

But there is a much better way to specific this such that it is dynamic:

 Python |  copy code |? 
1
import os.path
2
 
3
TEMPLATE_DIRS = (
4
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
5
    # Always use forward slashes, even on Windows.
6
    # Don't forget to use absolute paths, not relative paths.
7
    os.path.join(os.path.dirname(__file__), 'mytemplates').replace('\\','/'),
8
)

This solution uses os.path.dirname(__file__), which gets the name of the directory that the current file resides in using __file__, which references the current Python module which the code lives in (settings.py).  The appropriate sub-directory mytemplates is appended using os.path.join. Finally, the .replace('\\','/') replaces any pesky backslashes with the required forward slashes.

A couple notes:

  1. Be sure to include the trailing comma at the end of the line, as you are able to specify more than one template directory and Django will not be happy without the comma.
  2. You must import os.path to have access to the appropriate Python functions used to assemble the path.

The flexibility of this solution should be immediately apparent, allowing for easily moving your project around, including to production, without having to update your template directory.