Creating a custom Http403 exception in Django

Django comes packed with lots of wonderful shortcuts for rendering the correct response to a user. Shortcuts like get_object_or_404 which display 404 pages use the simple Http404 exception that comes with the http library. An example view:

from django.shortcuts import get_object_or_404

def my_view(request):
    my_object = get_object_or_404(MyModel, pk=1)

In the background Django raises the Http404 exception which throws a message letting you know there wasn’t a matching query or slug. You may also raise the Http404 exception ala carte in your view like this:

from django.http import Http404

def my_view(request):
    raise Http404

But what about the times when you need to let a user know that they do not have permissions to be there? For example, they may try to export data that doesn’t belong to them and we want to return a nice page with a message. In the background the http request should return a status 403 while the front end returns the message. 

We could technically return the HttpResponseForbidden object in the view but that would require passing a template parameter and context to get the nice custom look we want. We can automate this. Instead of returning HttpResponseForbidden let’s create our own Http403 exception similar to the Http404 exception that automatically uses a 403.html template. 

First, you want to create a middleware class that will handle the Http403 exception like so:

from django.template import RequestContext, loader
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseForbidden  

class Http403(Exception):
    pass  

def render_to_403(*args, **kwargs):
    """
        Returns a HttpResponseForbidden whose content is filled with the result of calling
        django.template.loader.render_to_string() with the passed arguments.
    """
    if not isinstance(args,list):
        args = []
        args.append('403.html')              

    httpresponse_kwargs = {'mimetype': kwargs.pop('mimetype', None)}
    response = HttpResponseForbidden(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs)              

    return response  

class Http403Middleware(object):
    def process_exception(self,request,exception):
        if isinstance(exception,Http403):
            if settings.DEBUG:
                raise PermissionDenied
            return render_to_403(context_instance=RequestContext(request))

The middleware class Http403Middleware catches the Http403 exception in views and raises PermissionDenied (for debug=True) or returns render_to_403 (for debug=False). The middleware class must be placed in your Django settings under MIDDLEWARE_CLASSES.

Let’s assume that the code above is placed in an application called “base” and the file it resides in is called middleware.py. Then MIDDLEWARE_CLASSES would look like:

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',

    # The HTTP 403 exception
    'base.middleware.Http403Middleware',
)

Now you can create the 403.html template and place it in your templates directory provided in TEMPLATE_DIRS. That could look something like:

<!DOCTYPE HTML>
<html lang="en-US">
<head>
        <meta charset="UTF-8">
        <title>The force is not with you today.</title>
</head>
<body>
        <h1>The force is not with you today</h1>
    <p>You cannot jedi mind trick your way into viewing this page, try again later.</p>
</body>
</html>

The last step is to raise the Http403 exception in your views when someone is denied access. Http403 exists in middleware.py according to the examples above but to better suite Django standards lets place it in a file called http.py. Here is an example of the view:

from django.shortcuts import render_to_response
from base.http import Http403

def index(request):
    if not request.user.has_perm('app_label.permission'):
        raise Http403
    return render_to_response('app/view.html',{})

Another note: the render_to_403 function should be put in a file called shortcuts.py to stick to Django standards but you can put it where you want.

You can pretty much do this with any response code but 404 and 403 are the most common ones that require a front facing message. Happy Coding!

Comments

  1. humitos says:

    Cool! Thanks for sharing this with us.

    I’m using this code in my Facebook’s application :)

  2. ebaum says:

    Thanks for this.

    Minor note: you need a couple additional imports to get the middleware code to actually work. The following works for me.

    from django.template import RequestContext, loader
    from django.conf import settings
    from django.core.exceptions import PermissionDenied
    from django.http import HttpResponseForbidden

    Cheers.

  3. cocobuster says:

    Usefull thanks

  4. Siva says:

    If I don’t have permission in django admin, it will throw plain “Permission Denied” page. Can I use the above solution for customizing that also?

Speak Your Mind

*


*

Fork me on GitHub