Home > Programming & scripting, Python > Simple middleware for database driven IP ban using django.

Simple middleware for database driven IP ban using django.

August 21st, 2007

The internet is stupid, and your users will misbehave. Therefore every homepage should have some way of banning users from posting content, or perhaps in some cases from viewing your page altogether.
I wanted a simple way to ban users from accessing one of my django made homepages, and then having them redirected to a page with an explanation why. The IP’s and explanations should be entered through the django admin interface. For this django middleware is a decent choice.

First of all, lets create a Ban model in your models file. Something like this should do (you might want timestamps and other stuff as well, I don’t really need it).

class Ban(models.Model):
    ip = models.IPAddressField()
    reason = models.TextField()

    def __str__(self):
        return self.ip

    class Admin: pass

Do a python manage.py syncdb to insert the model into your database.

Now it’s time to create the middleware. The middleware can be placed anywhere reachable from your python path.
I named my middleware file ipban.py and placed in in a subfolder called “middleware”. Also create an empty __init__.py file in this directory if you do so as well to indicate that it holds python files. The content of my ipban.py is

from django import http
from pici.picipage.models import Ban

class IPBanMiddleware(object):
  """
  Simple middleware for taking care of bans from specific IP's
  Redirects the banned user to a ban-page with an explanation
  """
  def process_request(self, request):
    ip = request.META['REMOTE_ADDR'] # user's IP
    path = request.path # requested path

    # see if user is banned
    try:
      # if this doesnt throw an exception, user is banned
      Ban.objects.get(ip=ip) 

      # only redirect when not already at the ban page
      if not path.startswith("/banned/"):
        return http.HttpResponseRedirect("/banned/%s/" % ip)
    except Ban.DoesNotExist: # not banned! goodie
      pass

pici.picipage.models should of course be changed to point to your own models module containing the Ban model.
This middleware checks if the users IP is in the list of banned users at every request. If the user is banned, then he is redirected to the ban-page “/banned/ipgoeshere/”.

Now open your settings.py file and add this class to your MIDDLEWARE_CLASSES.
In my case I added

'pici.middleware.ipban.IPBanMiddleware',

Add a urls.py entry with something like this (I’m somewhat lazy at regular expressions). Obviously replace miscviews.banned with whatever you are going to call your view-handling method and it’s module.

(r"^banned/([^/]+)/$", miscviews.banned),

Add the handler for the banning in your view. Mine looks like this. Replace “subpages/banned/banned.html” with the path to your template.

def banned(request, banip):
  try:
    ban = Ban.objects.get(ip=banip)
    reason = ban.reason
  except Ban.DoesNotExist:
    reason = "None given"
  return render_to_response("subpages/banned/banned.html",
    {"reason": reason}, context_instance=RequestContext(request))

Finally add the template and you are done!
Adding the template should be straight forward if you know anything about django, but it can look something like this

{% extends "index.html" %}

{% block main_content %}
  <div class="normal_content_centered">
    <h1>BANNED!</h1>
    <p>The reason for your banning is:</p>
    <p><b>{{ reason }}</b></p>
    <img src="url_to_crying_baby_goes_here.jpg" />
  </div>
{% endblock %}

The image of a crying baby is essential.

This can of course quite easily be optimized by using sessions to avoid having to do the lookup for each request, however a very simple database hit per request is not a big deal in my case so I’ll save that for later. Premature optimization is evil and all that.

Have fun banning users from your homepage :)

edit: Improved version up

buffi Programming & scripting, Python

  1. August 21st, 2007 at 22:32 | #1

    Nice post and nice idea. I do have a couple of suggestions though. I would likely do the check for the request page before checking if the IP is banned. Because if the user is at the banned page there is no need to hit the database one more time. You do the check in your code, but it’s after the database call.

    Secondly, I’m not sure why you didn’t just use a generic view (object_detail) for handling the banned page. That would have been a bit cleaner IMO.

    Again, great writeup.

  2. August 22nd, 2007 at 00:23 | #2

    Glad you liked it!

    The order of the database hit or the page check doesn’t really matter. In 99.9…% of the cases, the user visiting the page will not be banned and not be visiting the /banned/ page. The extra hits can of course be avoided, but they really aren’t even worth optimizing for.

    If the code should be optimized, it should probably instead use a solution with sessions (or perhaps some cache solution?) or something similar as I mentioned, to avoid doing the check all of the time.

    I honestly mostly use the object_detail generic view when presenting data from a set that the user can view multiple objects of (in combination with object_list). It is probably true that the code could have been a bit clearer if it had been used though, but I usually prefer to write my own views for small stuff like this, to be able to handle them the way I want, and leave the generic solutions for listings of objects and so on :)

  3. August 22nd, 2007 at 02:55 | #3

    I’d just return the rendered “banned” page in the middleware. Then you don’t need to worry about the hassle of modifying urls and making a view.

  4. August 22nd, 2007 at 12:15 | #4

    That however is a very valid point :)
    Just rendering the template from the middleware is a lot nicer indeed. Thanks!

  5. August 23rd, 2007 at 19:03 | #5

    There we go… added a new blog post with the rendering in the middleware. Thanks

  6. February 8th, 2008 at 13:41 | #6

    I have been visiting this site a lot lately, so i thought it is a good idea to show my appreciation with a comment.

    Thanks,
    Jim Mirkalami

    PS: I am a single dad! ;)

  1. No trackbacks yet.