Integrating reCAPTCHA with Django

How to integrate reCAPTCHA with Django using a field and a widget.

Warning: although remoteip is used as optional parameter in this snippet, it is likely to become mandatory in the future. Please see the comment by Jim Garrison for more detail.

From reCAPTCHA:

reCAPTCHA is a free CAPTCHA service that helps to digitize books, newspapers and old time radio shows.
A CAPTCHA is a program that can tell whether its user is a human or a computer.

You'll probably see one if you want to sign up for google apps, twitter, last.fm etc.

Although you can find a good solution on djangosnippets, I'll show you how to add reCAPTCHA in your form without writing the same code again and again.

Step 1: Apply for a reCAPTCHA key

Register on reCAPTCHA to get your public/private key pair.

Step 2: Add your keys in settings.py

RECAPTCHA_PUBLIC_KEY = '<your public key>'
RECAPTCHA_PRIVATE_KEY = '<your private key>'

Step 3: Add recaptcha-client to your project

Download recaptcha-client, create a folder (recaptcha) in your project with a copy of captcha.py inside.

Step 4: Put ReCaptchaField and ReCaptcha widget somewhere

I prefer to use a generic app (marcofucci_utils) with 2 files:

fields.py

from django.conf import settings
from django import forms
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _

from marcofucci_utils.widgets import ReCaptcha
from recaptcha import captcha

class ReCaptchaField(forms.CharField):
    default_error_messages = {
        'captcha_invalid': _(u'Invalid captcha')
    }

    def __init__(self, *args, **kwargs):
        self.widget = ReCaptcha
        self.required = True
        super(ReCaptchaField, self).__init__(*args, **kwargs)

    def clean(self, values):
        super(ReCaptchaField, self).clean(values[1])
        recaptcha_challenge_value = smart_unicode(values[0])
        recaptcha_response_value = smart_unicode(values[1])
        check_captcha = captcha.submit(recaptcha_challenge_value, 
            recaptcha_response_value, settings.RECAPTCHA_PRIVATE_KEY, {})
        if not check_captcha.is_valid:
            raise forms.util.ValidationError(self.error_messages['captcha_invalid'])
        return values[0]

and widgets.py

from django import forms
from django.utils.safestring import mark_safe
from django.conf import settings
from recaptcha import captcha

class ReCaptcha(forms.widgets.Widget):
    recaptcha_challenge_name = 'recaptcha_challenge_field'
    recaptcha_response_name = 'recaptcha_response_field'

    def render(self, name, value, attrs=None):
        return mark_safe(u'%s' % captcha.displayhtml(settings.RECAPTCHA_PUBLIC_KEY))

    def value_from_datadict(self, data, files, name):
        return [data.get(self.recaptcha_challenge_name, None), 
            data.get(self.recaptcha_response_name, None)]

Step 5: Configure your app

I'll use django-registration because I'm lazy, but you can write your own form in the same way.

Follow these steps:

registration_app.forms

from django import forms

from marcofucci_utils import fields as marcofucci_fields

from registration.forms import RegistrationForm

class RecaptchaRegistrationForm(RegistrationForm):
    recaptcha = marcofucci_fields.ReCaptchaField()

registration_app.urls

from django.conf.urls.defaults import *
from registration.views import register

from registration_app.forms import RecaptchaRegistrationForm

urlpatterns = patterns('',
    url(r'^register/$', register,
        {'form_class': RecaptchaRegistrationForm},
        name='registration.views.register'),
    (r'', include('registration.urls')),
)

registration_app is now a 100% drop-in replacement for django-registration (Thanks to Ian for this).

That's it!

Note that we have used just 1 custom line of code (recaptcha = marcofucci_fields.ReCaptchaField()).

Let's take a look at http://localhost:8000/accounts/register/

registration form with reCAPTCHA

In conclusion

You can either write your custom code again and again making mistakes and wasting time or write your code once and have fun!

26 July 2009