tumblelog detail

26.07.2009
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:

  • Download django-registration
  • Add registration to the INSTALLED_APPS setting of your Django project
  • Create a custom app registration_app with urls.py and forms.py inside
  • Add this line to <your-project>.urls.py

    (r'^accounts/', include('registration_app.urls')),

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!

35 comments

30.07.2009
Diogo Baeder

Great snippet... although it didn't work for me, because of some bug in reCaptcha that I can't find... when the page loads the remote reCaptcha script, it wipes out the entire page and makes the browser never end loading something it should be loading... well, if I found out what it is, I can post the solution here, alright? But thanks nevertheless!


30.07.2009
Diogo Baeder

Never mind, Marco, the problem was that I was using it and a script was trying to transfer the contents to another DOM Element, and it was breaking because of the document.write calls... well, I'll try to solve it, tell me if you want the solution later.


30.07.2009
Marco Fucci

Yeah, let me know what your problem was, I'm curious!


11.08.2009
Felipe 'chronos' Prenholato

Hello Marco, great article, I'm right that is very very useful for me very soon :). Thx for share, and, one tip... you need to urgently improve font readbility of your template, is hard to read because both code and text fonts have colors to much close from background :).

see ya


11.08.2009
Marco Fucci

Hi Felipe, thanks for your comment, I'll change the font soon, you know, sometimes it depends on monitor, calibration and stuff like that.
I'm glad that you like my snippet.


13.08.2009
Pablo Ilardi

Hi, very nice snipped, certainly I'll give it a try soon. Thanks for sharing it!

Complimenti e saluti,
Pablo


08.09.2009
Ricky

Worked like a charm on one of my sites. Thanks so much for this!


15.11.2009
EdJ

Thanks. Perfect!! I appreciate the post


20.11.2009
Steve

Very nice work. Thanks for sharing this.


21.11.2009
Scott Nixon

Thanks so much for this! It was very easy to integrate!


12.12.2009
Dave

Very cool. Thanks. Only thing I did different was "easy_install recaptcha_client" and then "from recaptcha.client import captcha" to use it.


16.12.2009
uarpalex

Big Thanks, be happy!


18.01.2010
markh

very neat, thanks!


11.02.2010
Ian

This is an amazing snippet. Well done!

My one suggestion is this modification, which makes registration_app a 100% drop-in replacement for django-registration. This is useful if you are reversing the registration.views.register view in your templates:

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


11.02.2010
marco.fucci

@Ian I've updated the section. Thanks for the suggestion!


11.02.2010
jul

Nice, thank you. Would be even better with some dynamic lang setting.


15.03.2010
enniom

Thank you very much for this snippets! It's wonderful!


25.03.2010
Jim Garrison

I find it interesting that in your call to captcha.submit(), you pass the empty dictionary instead of the user's remote IP address, but the documentation at http://recaptcha.net/apidocs/captcha/ says the "remoteip" is required. Any thoughts on why it still seems to work? Is there any possibility for something to go wrong?


25.03.2010
Marco Fucci

Hi Jim,
the remoteip param isn't required at the moment even though the documentation says something different, it just helps to identify potential spammers.
You can change the snippet by including an initial param with the remote ip set if you want but I haven't had problems with it.
If you need it, I think I can add an optional remoteip param in the next few weeks.

Let me know.


26.03.2010
Jim Garrison

Hi Macro,

Thanks for the quick reply.

Out of curiosity I started a thread on the recaptcha list at <http://groups.google.com/group/recaptcha/browse_thread/thread/43cdcb24acf86e20>. It seems that although they do not currently enforce the remoteip requirement, they may do so in the future.

The problem with adding support in the above example is that you have no direct access to Django's request object in RecaptchaField.clean() above. I've seen hacky attempts to get that information there, but nothing that seems very clean...

Regards,
Jim


28.03.2010
Marco Fucci

Hi Jim,
you're right, it isn't that easy to get that value from the request in the RecaptchaField.clean method and after googling a bit I haven't found a good alternative to my code.
Maybe this page: http://tinyurl.com/ydwwosp might be a good choice but I still don't like the improper use of FileField and initial param.
I'll put a warning so that users will be aware of this problem.

Cheers,
Marco

P.S. If you want to use the new version of django-registration with that code, just create a custom Backend, override get_form_class and use functools.partial (python 2.5+) to wrap the init method of your custom RegistrationForm.


26.04.2010
sp

Hi Marco,

I made a custom backend and overrode the get_form_class. I'm quite new to python though and don't understand what you mean about wrapping the init method. I'd really appreciate it if you could elaborate a bit. Thanks for the tutorial.


27.04.2010
Marco Fucci

Hi sp,
I don't have enough time to write a post but I thought you needed this info so I wrote down something to work on.

Follow the instructions that you can find at http://tinyurl.com/ydwwosp.
After that you need to create a custom Form and a custom Backend, you can find an example here: http://pastebin.com/q2wPCVi2

Please note that I haven't tested it at all (get it just like something to work on).
Let me know how it goes, I'll try to update the post as soon as I have time.

Good Luck,
Marco


17.05.2010
Ihor Kaharlichenko

Hello, Marco.

Thanks for the classes you provide. I intend to use this code in my projects.

Thus I have a couple of questions:

1. What is the license for this code?
2. Don't you mind if I create a repository on github or bitbucket, put your code there and create packages for Debian and Ubuntu so that other people could install them using their package managers?

I wouldn't claim this code is mine, all the credentials will be forwarded to you.


18.05.2010
Marco Fucci

Hi Ihor,
thanks for your feedback.
No worries about the license, you can do whatever you want. I'd be glad if you could include my name but it isn't a must.

Please note that the code isn't perfect as you can read from the previous comments. It's easy to fix it but I haven't had time to work on it.
If you create a github repo I'll fork your project and improve it.

Let me know how it goes.
Cheers,
Marco


27.05.2010
Shaun Sephton

Thanks Marco, I've gone ahead and created a github repository with your code here: http://github.com/praekelt/django-recaptcha


05.11.2010
Eduard

Hi Marco I'm having the following problem when I run it displays the message: "register () takes at least 2 non-keyword arguments (1 given) '. I forgot to do a step?


08.12.2010
Scoox

Unfortunate... I get the Error
TypeError at /signup/

object.__new__(ellipsis) is not safe, use ellipsis.__new__()

Request Method: POST
Request URL: http://127.0.0.1:8000/signup/
Django Version: 1.2.3
Exception Type: TypeError
Exception Value:

object.__new__(ellipsis) is not safe, use ellipsis.__new__()

Exception Location: C:\Python25\lib\copy_reg.py in __newobj__, line 92
Python Executable: C:\Python25\python.exe
Python Version: 2.5.2
Python Path: ['C:\\Users\\Anwender\\workspace\\abenteuer\\src\\abenteuer', 'C:\\Python25\\lib\\site-packages\\pymysql-0.2-py2.5.egg', 'C:\\Users\\Anwender\\workspace\\abenteuer\\src', 'C:\\Python25', 'C:\\Python25\\DLLs', 'C:\\Python25\\lib', 'C:\\Python25\\lib\\lib-tk', 'C:\\Python25\\lib\\plat-win', 'C:\\Python25\\lib\\site-packages', 'C:\\Windows\\system32\\python25.zip']
Server time: Mi, 8 Dez 2010 17:12:16 +0100

with Django 1.2


09.12.2010
Stephan Muller

For the current version of django_registration, use the following line in urls.py from admin_app instead of the original:

{ 'backend': 'registration.backends.default.DefaultBackend' ,'form_class': RecaptchaRegistrationForm},


27.01.2011
michele

grazie mate that was quite useful! btw love your website design!


10.02.2011
marc

brilliant post Marco!


11.02.2011
programy

Thanks a lot for this snippet!


15.02.2011
Drew

In addition to Stephen Muller's update above, I'd like to add that I had to use:
from recaptcha.client import captcha

instead of:
from recaptcha import captcha

at the top of forms.py and fields.py

Thanks!
Drew


25.03.2011
Michael

Great solution!

I had the same issue as Drew, temporary solution below since it differed on our platforms:

try:
from recaptcha import captcha
except ImportError:
try:
from recaptcha.client import captcha
except ImportError:
raise ImportError


15.03.2012
Sean Esopenko

This has been very helpful. I had to do some digging to figure out how to get django-registration working but the captcha integration you detailed worked exactly as you said it would.