Accessible Captchas in Django
Sunday, July 6th, 2008While we were implementing the GNOME.Asia summit website (will be available on www.gnome.asia soon), we were confronted with the problem of captcha accessibility. Despite Recaptcha’s claim of being accessible by providing sound captchas, the sound quality turned out to be rather poor and I actually did not succeed in using it even after listening to it four times. Based on this solution implemented in Rails, I reimpleted a similar idea for question captchas in Django. The code is fairly simple, based on a model which contains questions and the corresponding answers, and an implementation of a Django newforms field which can be used in the form. Some example questions can be found here.
models.py
from django.db import models from django.utils.translation import ugettext_lazy as _ class AccessibleCaptcha(models.Model): question = models.CharField( max_length=255) answer = models.CharField(max_length=255) question.verbose_name = _(u'Question') answer.verbose_name = _(u'Answer') def __unicode__(self): return self.question
fields.py
from random import randint from django.newforms import * from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe from captcha.models import AccessibleCaptcha class AccessibleCaptchaWidget(Widget): def __init__(self, options={}, *args, **kwargs): super(AccessibleCaptchaWidget, self).__init__(*args, **kwargs) def render(self, name, value, attrs=None): index = randint(0, AccessibleCaptcha.objects.count()-1) accessible_captcha = AccessibleCaptcha.objects.all()[index] question = accessible_captcha.question return mark_safe(u'''<strong>%(question)s</strong> <input name="%(name)s" value="%(index)d" type="hidden" /> <input name="%(name)s" id="id_%(name)s" type="text" />''' % {'name':'captcha', 'index':index, 'question':question}) def value_from_datadict(self, data, name, prefix): return data.getlist(prefix) class AccessibleCaptchaField(Field): def __init__(self, required=True, label=None, help_text=None, options={}, *args, **kwargs): super(AccessibleCaptchaField, self).__init__(required=required, widget=AccessibleCaptchaWidget(options=options), label=label, help_text=help_text, *args, **kwargs ) def clean(self, value): errormsg = gettext(u'You did not enter the correct answer. Please try again.') if not isinstance(value, (list, tuple)): raise ValidationError(errormsg) #get the right answer from the database #value[0] contains the id of the hidden field, value[1] contains the user input index = int(value[0]) answer = AccessibleCaptcha.objects.all()[index].answer if not value[1].lower() == answer.lower(): raise ValidationError(errormsg) return True
Usage
captcha = AccessibleCaptchaField(label=_(u'Are you human?'))