Django and captchas

January 25, 2007

Today at work, I wrote a simple captcha system for the web application we are building. Danny mentioned that he would be interested in seeing how that stuff is done. So I made a quick demo app, and I’ll post the code here.

A few files are needed to make this work:

This is what the result will look like:

First, here’s my views.py file. I put everything in there, but if your application grows, you will probably want to move your manipulator code into another file (e.g.: forms.py) and maybe the captcha() function too (this one could go in utils.py). The code is commented, so I don’t think I need to go over everything again, do I?

import Image, ImageDraw, ImageFont
import sha
from random import randrange

from django.shortcuts import render_to_response
from django.conf import settings
from django import oldforms as forms
from django.core import validators

class CaptchaManipulator(forms.Manipulator):
    # We create a new form with a single
    # field to input our captcha.  This
    # field is required, and it will be
    # validated with our own custom method.
    def __init__(self):
        self.fields = (
            forms.TextField(field_name='captcha',
                            is_required=True,
                            validator_list=[self.captcha_check]),
        )

    # Check that the captcha field when hashed
    # is the same as in the hidden field.  If
    # not, raise a validation exception.
    def captcha_check(self, field, all_fields):
        image_hash = sha.new(field + settings.SECRET_KEY).hexdigest()
        if image_hash != all_fields['hash']:
            raise validators.ValidationError('Incorrect captcha!')

def captcha():
    # Pick a random number between 10,000 and 99,999
    n = str(randrange(10000, 100000))

    # Get a SHA hash of the random number we picked
    # and our Django project's secret key.  The idea
    # here is that a potential bot could have the SHA
    # hashings for all the possible numbers and could
    # use that to fool our system, so that's why we add
    # secret key.  It would also be really stupid to
    # put the captcha answer in plain text in the HTML.
    image_hash = sha.new(n + settings.SECRET_KEY).hexdigest()

    # Open our background image.  You may want to use
    # os.path.join and MEDIA_ROOT to avoid path problems.
    image = Image.open('media/bg.jpg')

    # We're going to draw on that image
    draw  = ImageDraw.Draw(image)

    # Using this truetype font.  It is recommended to use
    # a weird font that people have no problem reading, but
    # that is still irregular to fool potential bots.  The
    # second parameter is the size of the font. To avoid
    # path problems, use os.path.join and MEDIA_ROOT.
    font  = ImageFont.truetype('media/Nervous0.ttf', 52)

    # Write our number starting at point 0@10 using our
    # font.  fill is the color we want to use
    draw.text((0, 10), n, font=font, fill=(0, 0, 0))

    # Save the image in the media directory.  Using the
    # same name over and over may not work for large
    # sites.  Same advice as above, use os.path.join.
    image.save('media/captcha.jpg', 'JPEG')

    return image_hash

def index(request):
    manip = CaptchaManipulator()

    if request.method == 'POST':
        data = request.POST.copy()
        errors = manip.get_validation_errors(data)
    else:
        data = errors = {}

    form = forms.FormWrapper(manip, data, errors)

    # We pass two variables to our template: the
    # hash we calculated and the form.
    return render_to_response('index.html',
                              {'hash': captcha(),
                               'form': form})

And here’s what index.html looks like:

{% if form.captcha.errors %}
    {{ form.captcha.errors|join:" " }}
{% else %}
    You got it!
{% endif %}

<img src="/media/captcha.jpg" />

<form method="post" action=".">
  {{ form.captcha }}
  <input name="hash" type="hidden" value="{{ hash }}" />
  <input type="submit" />
</form>

This blog entry was really helpful in getting me in the right direction. In fact, if you look closely, you can see that I just removed some stuff from his code.

I hope this is helpful!


Django tip

January 18, 2007

If you want to use the django.test.client.Client class inside the interactive interpreter to test your views, you need to do the following first:

>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

If you don’t, you will be able to use Client(), but the properties context and template will always return None.


Car accident

January 18, 2007

It’s been a while since I posted, but it’s mostly because I didn’t have much that I wanted to talk about. However, this morning I had an unfortunate car accident, and I’d like to say that it sucks. Me an the other driver are fine, it was just a fender bender, thank God. I won’t give any detail just yet (haven’t heard back from my insurances), but the front of my car is pretty messed up. It seems to be mostly superficial damage, I was able to return home (20 minutes trip) without any problem.

My mechanic is going to look at it and will let me know if it’s indeed just a new bumper (and some hood “un-bumping”) that I need. In case I need to get a new car, what would you guys suggest? I don’t want a 2007 model, I can’t afford it, but some 2-4 years old that’s reliable and relatively cheap would greatly interest me. Any models come to mind?