This blog was getting stormed by comment spam so i decided to use some captcha protection. I chose pycaptcha for this and had to integrate it nicely with Django. I wanted to make it an easy widget that does it’s own validation and does not require view code for validating the captcha. So here is what i came up with:
EDIT: Updated for SVN trunk version of Django ( value_from_datadict has an extra parameter)
First, the custom form field:
class CaptchaField(CharField):
widget = CaptchaWidget
def __init__(self, *args, **kwargs):
super(CaptchaField, self).__init__(*args, **kwargs)
def clean(self,values):
value,captchaid = values
#Check if the value matches that in the session
factory = PersistentFactory('/tmp/captcha_persistence')
test = factory.get(str(captchaid))
factory.storedInstances.close()
if (not test) or (not test.valid):
raise ValidationError(u"""Captcha does not exist or is
invalid. Please refresh to get new captcha""")
if not test.testSolutions([value.strip()]):
raise ValidationError(u"""You did not type the correct
captcha text""")
return ""
The Widget used by this form field:
class CaptchaWidget(Input):
input_type = "text"
def render(self,name,value,attrs=None):
#Always generate a new captcha on rendering
factory = PersistentFactory('/tmp/captcha_persistence')
test = factory.new(Tests.PseudoGimpy)
factory.storedInstances.close()
captchaid = test.id
superclass_output = super(CaptchaWidget,self).render(name,"",attrs)
output = """<img src="/captcha/%s" border="0"/><br/>
<input type="hidden" name="captchaid" value="%s" />
%s
""" %(captchaid,captchaid,superclass_output)
return output
def value_from_datadict(self,data,files,name):
return [ data[name],data['captchaid'] ]
We also need a view to deliver the image for the captcha so here it is:
def captcha_image(request,id):
factory = PersistentFactory('/tmp/captcha_persistence')
test = factory.get(str(id))
factory.storedInstances.close()
if not test:
return HttpResponseNotFound()
response = HttpResponse(mimetype='image/jpeg')
test.render().save(response,'JPEG')
return response
Dont forget to add a urls.py entry for our view. something like:
(r'^captcha/(?P<id>\w+)','blogon.blog.views.captcha_image'),
should be enough (change the view to your view. If you change the url then be sure to change it in the html generated by the widget.
.
Notice that in our code we use a file in /tmp for writing the persistence of pycaptcha. This may be a security problem depending on our environment. So be careful, or us a trusted location.
I should use a template for the HTML generated by the widget so as to make it more easily customizable but i just got bored after i got it working.