logo sudovi.com
- X /home/ryan/reCaptcha & Django -- Jan 27, 2009 4:00pm

Using reCaptcha with Django Comments framework

I've been using solely Django for a few months now and am in the process of creating a site that will allow comments. (Not really a blog, but that's not important). One thing that I think is impressive with Django is the number of mini-apps that you can import from django.contrib. For the site that I'm working on, I thought I'd give django.contrib.comments (the django comments system) a shot.

I really like what they've done and they've made it easy to add comments to just about anything. One thing however that nagged me from the beginning was their method of "spam protection." Their idea of spam protection was to simply include a "honeypot" field that a spammer would fill out along with all the other fields. Upon submission, if there is any data in that field, the submission would fail. That's all well an good, but say you want to up the ante just a bit and add some form of captcha? Since we're all lazy programmers, why not implement reCaptcha (recaptcha.net)?

Here's where things get sticky, or at least they did for me. There's an abundance of search results for django and recaptcha but each of those solutions just felt a bit clumsy. I tried several methods suggested, (e.g. subclass CommentForm, ajaxverify...) but nothing quite gave me the solution I was looking for. So, after wasting too long, I came up with my own way. But before we get to that, allow me to mention my issues with some of my other attempts.

Subclassing was my first attempt and I thought it was all going well, until I began to realize that if I wanted to use the view that was already written (and not write my own post_comment view) that I was in trouble. Any amount of toying with/removing the honeypot field was obviously resulting in failure. At a few different points, I broke the comment system completely when trying to load my subclassed form. (I was also hoping to leverage form.as_p() to render the from rather than loop over it. Even so, wanting to add captcha and remove the honeypot was still causing me to write my own view, first to validate the captcha and then to submit the form. While my end solution still required me to write my own view, in the end, it was dead simple.

Ajaxverify (from recaptcha) -- apparently you can load the recaptcha via ajax, which I've done, but verifying is not supported AND you will get access denied errors for cross domain javascript. *DOH*

Finally, enter jQuery -- really just so I could use the ajax stuff --, some light javascripting, and urllib2. What I ended up with was a javascript that loaded the reCaptcha widget and a form button that would submit the form if the captcha passed by using the onclick handler.

In the end, my javascript submitted an ajax request (using jQuery) to my verify_recptcha view that rendered a template with simply "true" or "false" that was handed back to my javascript. The callback function then inspected that text and either called form.submit() or prompted the user to re-enter the captcha. (Instead of getting rid of the honeypot field, I just hid it and its lable using CSS and jquery).

I think my solution took less lines of code than most other suggestions I've seen and now that I look back at it, seems the most straight-forward and simple. I'm sure I could refactor a number of things and leverage jQuery even more (since some of my javascript was "from scratch" but I'm still getting comfortable and familiar with all that jQuery has to offer me.

Without further ado -- code (forgive the lack of indentation):

The View:

import urllib2, urllib
...
def verify_recaptcha(request, chal, resp):
remoteip = request.META['REMOTE_ADDR']
url = 'http://api-verify.recaptcha.net/verify'
values = {'privatekey': 'my_private_key_here',
'remoteip': remoteip,
'challenge': chal,
'response': resp}
data = urllib.urlencode(values)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
messagelist = response.read().split(' )
') message = messagelist[0]
return render_to_response('blog/captcharesult.html', {'response': message},
context_instance=RequestContext(request))



The scripting part:

//hide django's spam field label
$(document).ready(function() {
labelelement = $('#id_honeypot').parent().hide();
});

function showCaptcha() {
Recaptcha.create("my_public_key",
"recaptcha_div", {
theme: "red",
});
}

function verifycaptcha() {
challenge = Recaptcha.get_challenge()
res = Recaptcha.get_response()
if (!res) {
res = 'FAIL';
}
res = res.replace(" ", "%20");
ajaxreq = $.post('/comments/verify/' + challenge + '/' + res + '/',
function(data) {
process(data);
});
}
function process(data) {
if(data == "true") {
document.getElementById('commentform').submit();
} else {
alert('Captcha not correct, please try again');
}
}

Then I simply created an input of type button with an onclick that calls the verifycaptcha function and if/when successful, submits the form. I loaded the captcha via a call to the showCaptcha() function down in the area of the form that I wanted it to appear (not shown in included code) but could have just as easily let jQuery load it on document.ready().

The part that took me the longest was getting the value that I received from recaptcha to match what I expected. It would show that "data" came back true, but when I would do data == "true" it would evaluate to false. I later found out that it was because data contained the " " from when I called split(' ') in my code. As you can see, this version of the code doesn't fix that and I will do so shortly but I wanted to get this out there in case anyone else was having similar headaches.

Hope it helps someone! If so, lemme know!


~
:wq!


- X /home/ComeChoma -- 2009-02-09 22:58:20
Nice template. Where can i download it?
~
:wq!


- X /home/Jason Broyles -- 2009-02-11 19:51:36
Thanks for the post. Awesome looking site btw.
~
:wq!


- X /home/Ryan -- 2009-02-11 20:30:25
@Jason -- Thanks...not everyone I show it to gets it...and one guy questioned my use of the "Gnu" in the background in relation with Ubuntu...oh well, can't please 'em all.
~
:wq!


- X /home/Mark -- 2009-02-13 03:20:34
Where's the captcha...leaving a comment but don't see it??
~
:wq!


- X /home/Ryan -- 2009-02-13 10:31:01
@Mark -- I don't have it implemented on this site yet. This site is written in PHP/Code Igniter. I hope to eventually convert it either to Django or Wordpress. This post is regarding a different application I hope to launch soon. That being said, this site sorely needs captcha as I get hit with comment spam all the time!
~
:wq!


- X /home/nataly -- 2009-06-22 15:12:22
T0KEAE ghUnxCczpf72ndOqi20g
~
:wq!


- X /home/margaret -- 2009-06-22 16:27:16
SnGS7P ega7Kl0dnDduqp6s2bnp1o
~
:wq!


- X /home/LeraJenkins -- 2009-06-22 16:55:56

More precisely does not happen
~
:wq!


- X /home/PeterMontee -- 2009-07-02 21:07:27

The authoritative answer
~
:wq!


- X /home/teen and incest -- 2009-07-03 06:22:48
well.. it's like I thought!
~
:wq!


- X Add Comments
All Fields Req'd (I won't display your email)
Do not use "http" or "href" in your comment.
You will be blocked.

Bold: [b]your bold text[/b]
Italics: [i]your italic text[/i]
Underline: [u]your underlined text[/u]
Link: [mylink]http://wherever[/mylink]

Copyright 2008 -- all rights reserved