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/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/Apps Downloads -- 2009-11-17 10:23:09
Why doesn't baking soda freeze?
~
:wq!


- X /home/Humphrey -- 2009-11-17 18:34:58
Just wondering if this solution actually stops spam? If you are only loading and validating the captcha using js, then what happens if js is disabled? My guess is that the captcha wouldn't show, and you could submit the comment without entering a captcha.

Since a spambot would usually be an automated process, surely the spambot would just load the html, and then post data to the form. Surely, the js would never usually get run by the spambot, so the captcha wouldn't be shown, and therefore would be pointless?

To make a secure website, all form validation needs to be done serverside - which means that you have no choice but to override the comment view function. It's ok to do js validation too, but the logic needs to be repeated on the server. It's good practice to never rely on the clients browser for validation :-)

So I'm interested to hear if your method works. It might. Especially if you site is small, it might be enough to deter spammers. But I imagine that it's breakable.

What really needs to be done (and many I should do this myself) is to create a new django app, which wraps itself around django.contrib.comments but adds recaptcha support. That way, anybody would just install the app, and use it exactly the same as contrib.comments.
~
:wq!


- X /home/Ryan -- 2009-12-10 21:55:39
Humphrey -- You are correct, but its been enough of a deterrent, ala too many hoops to jump through. I like your idea of wrapping the comments framework. Very interesting indeed.
~
:wq!


- X /home/Jalppype -- 2009-12-12 08:19:42
ehh.. really like this thread..
~
:wq!


- X /home/Incest You Tube -- 2009-12-18 17:47:09
uh. strange thread )
~
:wq!


- X /home/Texas Holdem -- 2009-12-18 21:41:28
Why aren't there bullet-proof pants?
~
:wq!


- X /home/Forex -- 2009-12-19 12:16:14
How do I get into a money society like Japan created to meet all money and financial needs?
~
:wq!


- X /home/???????? ??????? ???? -- 2009-12-20 03:11:17
Can you show the numbers by borough and type of school (public/charter/non-public)? By type of cost - metro card / busing?
~
:wq!


- X /home/icq -- 2009-12-20 04:34:54
How can I make money monthly on my investments?
~
:wq!


- X /home/?????????????? ?????? -- 2009-12-20 14:09:04
Ýòî èíòåðåñíî è ïîçíàâàòåëüíî è ïîýòîìó ÿ ñ óäîâîëüñòâèåì ïîéäó äàëüøå â ðàçâèòèè ýòîé òåìû
~
:wq!


- X /home/bestkazino.ru -- 2009-12-20 15:25:36
Thank you for your mighty fine work. You are our beacon of light in this long dark tunnel of chaos.
~
:wq!


- X /home/Forex -- 2009-12-20 16:02:27
Bringing money from another country to purchase a house here?
~
:wq!


- X /home/Pharmacy -- 2009-12-23 05:19:40
Good article. Very well written
~
:wq!


- X /home/devchushka -- 2009-12-23 17:06:08
Äà óæ. Êàê ãîâîðèòñÿ â óñòîÿâøåìñÿ âûðàæåíèè:
Òîëüêî ìû çíàåì, êàê æèòü âäåñÿòåðîì â îäíîêîìíàòíîé êâàðòèðå...
~
:wq!


- X /home/how to beat roulette -- 2009-12-27 11:32:03
shut up stercon ur so random
~
:wq!


- X /home/winining roulette ebooks -- 2009-12-28 03:30:58
What happens if you get scared half to death twice?
~
:wq!


- X /home/Bert ?? -- 2009-12-28 22:03:47
Why is Charlie short for Charles if they are both the same number of letters?
~
:wq!


- X /home/cheap oem software -- 2009-12-29 01:41:52
Thank you very much for that glorious article
~
:wq!


- X /home/car-reviews.ru -- 2010-01-03 05:05:57
Its very important
~
:wq!


- X /home/Reseller Hosting -- 2010-01-08 05:38:57
Your blog keeps getting better and better! Your older articles are not as good as newer ones you have a lot more creativity and originality now keep it up!
~
:wq!


- X /home/Online Degree -- 2010-01-21 01:32:44
How much money is needed to live without doing work and earn money without effort?
~
:wq!


- X /home/ofigennoe -- 2010-02-03 10:59:07
Êàê îáû÷íî ïðîñòî ñóïåð îáü¸ìíàÿ ñòàòüÿ è êàê âñåãäà äî÷èòàë äî êîíöà
~
:wq!


- X /home/7ly.ru -- 2010-02-03 11:16:37
Why doesn't Tarzan have a beard?
~
:wq!


- X /home/Education site -- 2010-02-05 19:59:38
Best article, lots of intersting things to digest. Very informative
~
:wq!


- X /home/download cracks -- 2010-02-07 22:14:53
How much money will it cost to fix a Cigarette burn in Leather seat?
~
: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