Sunday, November 25, 2007

ReCAPTCHA and Django FreeComments

My little family blog has been discovered by spammers. They have started leaving comments on my Django powered blog. Not very often, but enough to be annoying. I heard about reCAPTCHA and thought it was a fabulous idea, so I decided that was how I wanted to up the ante for the spammers on my blog. Integrating reCAPTCHA with the Django freecomments wasn’t as easy as I thought it would be though, so I want to write down how I made it work in case it will help somebody else down the road.

First of all, I did my due diligence and googled for help. I didn’t find much, but I found an email, a snippet, and a blog that got me going in the right direction. While I’m citing references here, I also made use of the comments page on the Django wiki, and the request and response documentation too. The reCAPTCHA api docs were vital as well.

OK, after doing my reading googling I got to work. I registered for a set of reCAPTCHA keys, downloaded the recaptcha-client python library. I did the easy install thing to get it, but after doing just that my scripts still couldn’t import the module. I ended up just putting captcha.py in my Django project directory.

I use the freecomments in exactly the same way that the Django blog does (they supplied all the code for me, it was the easiest thing to do), so I’ll just talk in terms of that (I’m not ready to share my family blog with the world just yet). The Django blog has a comment form at the end of each entry (scroll all the way down to see it). Typing your comment here and clicking the Preview Comment button takes you to a preview page (go ahead and try it, you won’t be posting a comment at this point). It’s on the preview page where you find the final submit button. This is where I decided the captcha would go. Spambots can preview comments freely, but to submit, the captcha test has to be passed. I kind of wanted to require the captcha for even previewing, but that was going to be too difficult, and probably too annoying for legitimate commentators.

The way to make this work was to wrap the post_free_comment view with a view of my own in order to evaluate the captcha before allowing a comment to be posted. Here’s the whole thing:

def postfree_wrapper( request, extra_context = {} ):
    if not request.POST:
        raise Http404, _("Only POSTs are allowed")

    extra_context["recaptcha_error"] = False
    if 'preview' in request.POST:
        # no captcha test needed, but supply the captcha html
        extra_context["recaptcha_html"] = captcha.displayhtml(
            settings.RECAPTCHA_PUB_KEY )
        return post_free_comment(request, extra_context)

    if 'post' in request.POST:
        # test captcha before submitting comment

        try:
            recaptcha_challenge_field, recaptcha_response_field = \
                request.POST['recaptcha_challenge_field'], \
                request.POST['recaptcha_response_field']
        except keyError:
            raise Http404, _("No recaptcha fields submitted")
        check_captcha = captcha.submit(recaptcha_challenge_field,
                                       recaptcha_response_field,
                                       settings.RECAPTCHA_PRIV_KEY, 
                                       request.META['REMOTE_ADDR'])

        if check_captcha.is_valid is False:
            extra_context["recaptcha_error"] = True
            #extra_context["recaptcha_error_code"] = check_captcha.error_code
            extra_context["recaptcha_html"] = captcha.displayhtml(
                settings.RECAPTCHA_PUB_KEY, False, check_captcha.error_code )

            # modify the POST object so that it has a 'preview' key in
            # it.  This will cause the post_free_comment method to
            # *not* save the comment, instead it will redisplay the
            # comment preview.
            mutable_post = request.POST.copy()
            mutable_post['preview'] = True
            request.POST = mutable_post

        return post_free_comment(request, extra_context)

    raise Http404, _("No preview or post from comment form")

I figured out how to do most of this from looking at the actual post_free_comment view. To briefly explain, the view is used whether the comment is being previewed or posted. If it’s a preview, I generate the captcha html using captcha.py and add it to the response context, and forward everything on to the real post_free_comment view. It does all the real work, and my template will be able to display the captcha html. If it’s a post, I check the captcha using captcha.py. Then, if the captcha was valid I just forward everything on to the real post_free_comment view and it handles validation and posting of the comment. If the captcha was not valid, I add a recaptcha_error to the context for my template, generate new captcha html, and then do a little trick to get the real post_free_comment view to treat the request as a preview instead of a post. That was the real trick to all of this that I feel clever about.

To get this view called I had to modify my urls.py and add this ahead of the comments urls include:

 ( r'^comments/postfree/', 'postfree_wrapper' ),

Next I needed to modify my free_preview.html template to display the captcha and some helpful text above it. To do that, I only had to add this to my template:

{% if recaptcha_error %}
    <p>Oops!  Those must have been too hard to read.  Please try again.</p>
{% else %}
    <p>If your comment looks good, simply type the words in the box to
          prove you aren't an evil robot, and then click Post.</p>
{% endif %}
{{ recaptcha_html }}

I put this right above the “Post public comment” button of the form. You can see an entire free_preview.html template on the wiki (scroll down a ways). Mine looks an awful lot like that one, with only the above addition.

Lastly, add your reCAPTCHA keys to your settings.py, like so:

RECAPTCHA_PUB_KEY = ''thisisabiglongpublickeythatyougetfromrecapthca
RECAPTCHA_PRIV_KEY = 'andthisisabiglongprivatekeythatyoualsogetfromrecaptcha'

And that's it. I hope that helps some other poor soul who wants to add reCAPTCHA to their Django powered blog, but who doesn't have the spare time laying on a couch with their leg elevated after surgery to figure it all out like I did.

Friday, November 23, 2007

Running gOS under QEMU on Ubuntu

I'm stuck on the couch with my knee elevated after having had surgery on Tuesday. I tore my meniscus playing Ultimate (a non-contact sport if ever there was one. I think I've just decided that cleats are the root of all evil). So, being stuck on the couch, I've been reading a lot and playing with my computer, of course.

I downloaded the CD image for gOS a couple weeks ago, and I finally decided to give it a try. I couldn't just burn it to a CD and boot it up though. Where's the fun in that? I decided to try it under kvm on my Ubuntu Gutsy Gibbon box instead. Turns out, that doesn't work. It just crashes with the cryptic message, "exception 6 (0)" and some kind of register dump.

Since you have to install qemu in order to run kvm, I tried running it under qemu next. It started to boot, but then got to a point where it just dropped me to a busybox shell with a prompt like this:

(initramfs) _

Turns out, after some googling, that this is a known issue, and this workaround worked for me:

In the boot menu
 1. Push F6 ( Other Options)
 2. remove: "quiet splash --"
 3. add: "break=top"
 4. Push Enter

Later when you see "/bin/sh: can't access tty; job control turned off"

type the following:
 modprobe ide-generic
 exit

...Start from CD image continues :-)

It then took a while (this is with the kqemu kernel module installed), but once it was all booted up it ran pretty darn well. I was impressed with the smooth little toolbar at the bottom of the screen with icons for all the google applications. Very nice idea. I'm impressed with qemu too. Once I followed the instructions I linked to, it was easy to set up and runs things pretty quickly. It was a fun little adventure while icing my knee on the couch.

Getting Desktop Effects Back for Gutsy

When I upgraded my Ubuntu machines from Feisty Fawn to Gutsy Gibbon the cool beryl/compiz desktop effects stopped being so cool. I have learned how to fix it, however, from a few awesome blog entries. Here's how I did it.

First, remove any oddball compiz/berylemerald repositories from your sources.list. Everything you need is in the standard Ubuntu Gutsy Gibbon repositories.

Next, remove any compiz/beryl packages that you previously had installed:


sudo apt-get remove compiz* && sudo apt-get autoremove
sudo apt-get remove beryl* emerald* && sudo apt-get autoremove

Now, install the needful compiz packages:


sudo aptitude install compiz compizconfig-settings-manager

Now you can turn it on by running System->Preferences->Appearance and the clicking over to the Visual Effects tab. Choose Custom and then click on Preferences to bring up the settings manager. Then you can follow the How to set up Compiz Fusion instructions to get wobbly windows, desktop cube, and all the other good stuff set up.

Saturday, November 3, 2007

Gutsy and 82801H Audio Controller

As I mentioned before, sound stopped working when I upgraded from Feisty Fawn to Gutsy Gibbon. I have the Intel 82801H (ICH8 Family) HD Audio Controller. I tried to get it working a few different ways, all following suggestions from various Ubuntu forum and launchpad websites (yes, this seems to be a well known bug), but what finally worked was "Method B" found on this page. Phew! To celebrate I watched some videos on YouTube. They are much better when you can hear 'em.

Thursday, November 1, 2007

Pretty Emacs for Gutsy

Pretty fonts for emacs on Gutsy Gibbon was easy to fix (unlike the sound problem I'm having). Just follow the instructions for pretty emacs here.