Show a Custom 403 Forbidden Error Page in Django

Creating a custom 404 Page Not Found error page is so easy in Django (all you do is put your own template named “404.html” at the root of your templates directory) that I naturally assumed doing the same for a 403 Forbidden error page would be just as easy. Unfortunately it is not.

After searching around for quite a while last night, I found bits and pieces that I have modified slightly and republished below in a unambiguous step-by-step tutorial (see the “Source and Other Resources” section at the end of the post for a few of the source posts).

The method posted below leverages some custom Django middleware code. Please, if you have a better, more elegant solution I’d love to hear about it in the comments.

Create the Middleware

  1. Create a directory at the root of your project called “middleware”
  2. Add a file named “__init__.py” to this directory
  3. Create a file named “http.py” in this directory with the following contents:
  4. from django.conf import settings
    from django.http import HttpResponseForbidden
    from django.template import RequestContext,Template,loader,TemplateDoesNotExist
    from django.utils.importlib import import_module
    
    """
    # Middleware to allow the display of a 403.html template when a
    # 403 error is raised.
    """
    
    class Http403(Exception):
        pass
    
    class Http403Middleware(object):
        def process_exception(self, request, exception):
            from http import Http403
    
            if not isinstance(exception, Http403):
                # Return None so django doesn't re-raise the exception
                return None
    
            try:
                # Handle import error but allow any type error from view
                callback = getattr(import_module(settings.ROOT_URLCONF),'handler403')
                return callback(request,exception)
            except (ImportError,AttributeError):
                # Try to get a 403 template
                try:
                    # First look for a user-defined template named "403.html"
                    t = loader.get_template('403.html')
                except TemplateDoesNotExist:
                    # If a template doesn't exist in the projct, use the following hardcoded template
                    t = Template("""{% load i18n %}
                     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
                            "http://www.w3.org/TR/html4/strict.dtd">
                     <html>
                     <head>
                         <title>{% trans "403 ERROR: Access denied" %}</title>
                     </head>
                     <body>
                         <h1>{% trans "Access Denied (403)" %}</h1>
                         {% trans "We're sorry, but you are not authorized to view this page." %}
                     </body>
                     </html>""")
    
                # Now use context and render template
                c = RequestContext(request, {
                      'message': exception.message
                 })
    
                return HttpResponseForbidden(t.render(c))
    
  5. You should now have this:
    • /myproject/middleware/
      • __init__.py
      • http.py

Modify Your Project’s “settings.py”

  1. Add ” ‘myproject.middleware.http.Http403Middleware’, ” to your MIDDLEWARE_CLASSES

Create a Custom “403.html” page

  1. Put it at the root of your template directory
  2. Sample content: (note: assumes you’ve already defined a “base.html” template)
{% extends "base.html" %}
{% block title %} | Access Denied{% endblock %}

{% block content %}
<h1>Access Denied</h1>
<span>We're sorry, but you are not authorized to view this page (Error: 403)</span>
{% endblock content %}

Raise a 403 Error

  1. In the file where you want to raise the 403 add this at the top: (I used it in my project’s “view.py” file)
  2. from myproject.middleware.http import Http403
  3. Raise the 403
  4. if request.user.id != object.user.id:
        raise Http403

That’s it, you now have a Django template to handle 403 Forbidden errors. I’m sure there’s a way for your front-end, production web server to do the same, but I haven’t explored that yet.

Source and Other Resources

  1. A middleware solution is here (HT Felipe ‘chronos’ Prenholato)
  2. A middleware solution is here (HT Glen Zangirolami)
  3. A potential decorator solution is here (HT Magus)

Related posts:

  1. Specify a custom manager for the Django admin interface
  2. Getting Started with South (Django Database Migrations)
  3. Getting Started with virtualenv (Isolated Python Environments)
  4. Errata: Practical Django Projects 2nd Edition (PDF)
  5. Aptana Studio and “Undefined variable from import: DoesNotExist”