Django and captchas
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!