Adding Captcha To Django's Built-in Comments

I recently had a good friend of mine ask for some help in adding a captcha field to his comments form. I gave him some pointers, but before he could put them into action he had to leave for a Thanksgiving roadtrip home. I didn't give much mind to the idea of putting captchas on my own site since it's not all that popular amongst spammers yet. When I woke up this morning, however, I found myself with a few spare minutes to see if my pointers were correct.

Some of the ideas I shared with my friend turned out to not work very well. As I tinkered about trying to get things to work on my own site, I think I came up with a relatively efficient way of doing things.

Installing The Captcha

The captcha field I use is quite simple and effective. I originally got it from http://django.agami.at/media/captcha/, but the project seems to be unmaintained now. Along the road to Django 1.0, some changes were made to the way form fields work, and there is a minor change required in the base code for this captcha field if you want it to work. Alternatively, you can use a copy of the field that I'm currently using.

All you need to do is extract the captcha directory somewhere on your PYTHONPATH. The author recommends putting it in django.contrib, but I usually just place it straight on the PYTHONPATH so all I need to do is from captcha import CaptchaField instead of from django.contrib.captcha import CaptchaField. Minor details...

Adding The Captcha

The first thing you'll want to do after installing the captcha field is add the field itself to your comments form. Instead of subclassing the built-in django.contrib.comments.forms.CommentForm form, I simply decorated the constructor of the form as such:

1
2
3
4
5
6
7
8
9
from django.contrib.comments.forms import CommentForm
from captcha import CaptchaField

def add_captcha(func):
    def wrapped(self, *args, **kwargs):
        func(self, *args, **kwargs)
        self.fields['security_code'] = CaptchaField()
    return wrapped
CommentForm.__init__ = add_captcha(CommentForm.__init__)

This adds a field called security_code to the CommentForm, and it works the same way as if you had done something like this:

1
2
3
4
5
6
7
from django import forms
from captcha import CaptchaField

class MyCommentForm(forms.Form):
    name = forms.CharField()
    ...
    security_code = CaptchaField()

You can put the decorating snippet from above anywhere you'd like so long as the module you put it in is loaded at some point in your project. I usually put this sort of magic in my main urls.py file so it's harder to forget about when I debug things.

Fixing the Form

The first problem with this little trick seems to be that the CaptchaField is rendered as unsafe HTML in the default form.html template in the built-in comments application. That just means that, instead of seeing the captcha, you will see the HTML necessary to render the CaptchaField directly on the page, like this:

<input type="hidden" name="security_code" value="captcha.caZ1SqQ" />
<img src="/static/captchas/caZ1SqQ/0656f09d3974850397dd4c4974f23a35.gif"
 alt="" /><br /><input type="text" name="security_code"
 id="id_security_code" />

To fix that, you can apply the safe filter to the field and make the template look something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{% load comments i18n %}
<form action="{% comment_form_target %}" method="post">
<table>
{% for field in form %}
    {% if field.is_hidden %}
    {{ field }}
    {% else %}
    <tr{% ifequal field.name "honeypot" %} style="display:none;"{% endifequal %}>
        <th>{{ field.label_tag }}:</th>
        <td {% if field.errors %} class="error"{% endif %}>
            {{ field|safe }} &nbsp;
            {% if field.errors %}{{ field.errors|join:"; " }}{% endif %}
        </td>
    </tr>
    {% endif %}
{% endfor %}
</table>
<p class="submit">
    <input type="submit" name="post" class="submit-post"
     value="{% trans "Post" %}" />
    <input type="submit" name="preview" class="submit-preview"
     value="{% trans "Preview" %}" />
</p>
</form>

Notice the {{ field|safe }} in there. Also note that I prefer the table layout for the comment form over the default mode. If you change your form template as I have done, you should put the updated copy in your own project's template directory. It belongs in templates/comments/form.html, assuming that your templates directory is called templates. You'll probably also want to check out the preview.html template for the django.contrib.comments application. I changed mine to look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{% extends "comments/base.html" %}
{% load i18n %}

{% block title %}{% trans "Preview your comment" %}{% endblock %}

{% block content %}
    {% load comments %}
    {% if form.errors %}
    <h1>{% blocktrans count form.errors|length as counter %}Please
     correct the error below{% plural %}Please correct the errors below
     {% endblocktrans %}</h1>
    {% else %}
    <h1>{% trans "Preview your comment" %}</h1>
        <blockquote>{{ comment|linebreaks }}</blockquote>
        <p>
        {% trans "and" %} <input type="submit" name="submit"
         class="submit-post" value="{% trans "Post your comment" %}"
         id="submit" /> {% trans "or make changes" %}:
        </p>
    {% endif %}

    {% include 'comments/form.html' %}
{% endblock %}

See how I just use the include tag to pull in the comments/form.html template I mentioned above? Saves a lot of typing and potential for problems... If you update the preview.html template, you should save your copy in templates/comments/preview.html, assuming your templates directory is called templates.

Testing It Out

At this point, you should be able to try out your newly installed captcha-fied comments. If it doesn't work, please comment on this article and perhaps we can figure out the problem!

Meta

Published: Nov. 30, 2008

Author: Josh VanderLinden

Comments: 9

Word Count: 1,028

Next: New Comment Notification by E-mail

Previous: Project Release: django-watermark 0.1.0-pre1

Bookmark and Share

Filed Under

Django, How To, Idiocy, Internet, Open Source, Programming, and Python

Article Links

  1. Captcha for Django Version 1.1
  2. use a copy

Comments

Gravatar for None
omtv
Quite useful scripts!
Thanks for sharing.
30 Nov. 2008 at 8:48 p.m.
Gravatar for codekoala
wheaties
No problem! I'm glad you find them useful.
30 Nov. 2008 at 8:56 p.m.
Gravatar for None
Bart
A good addition to this would be to see: http://www.b-list.org/weblog/2008/nov...

It would allow you to use your Captcha field, but only display it when the user is not logged in for example. Of course, for your blog it does not apply but I think it's definitely an article everyone using django should read.
1 Dec. 2008 at 7:26 a.m.
Gravatar for codekoala
wheaties
Bart: Thanks for the link! That article surely would come in handy for people who allow users to log into their site. Heck, I might implement it just so I don't need to bother with the captcha too!!
1 Dec. 2008 at 7:37 a.m.
Gravatar for None
jtsnake
Total awesomeness!
1 Dec. 2008 at 10:39 a.m.
Gravatar for None
zerzer
zerkjzlejr
26 Jan. 2009 at 8:22 p.m.
Gravatar for None
ozeroff
Very useful files search engine. http://myrapida.com is a search engine designed to search files in various file sharing and uploading sites.
18 March 2009 at 8:59 a.m.
Gravatar for None
ZK@Web Marketing Blog
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)?
1 July 2009 at 1:29 a.m.
Gravatar for codekoala
Josh VanderLinden
@ZK, I dunno about you, but I spent a little bit of time a couple years ago trying to get reCaptcha to latch onto the standard comments form in Django. It proved to not be as easy as I had anticipated. The captcha solution I used in this article (which was on my site until just 2 weeks ago) was _very_ easy to get setup. And it worked very well.

I removed it because I got complaints from several regular visitors about having to fill it out all the time.
1 July 2009 at 10:08 a.m.

Post a Comment

:  
:  
:  
:  
:  

About Me

My name is Josh VanderLinden. I'm a developer, and I have a passion for technology. I dream in code.

I use Linux, Kate, Java, Python, Django, databases, Photoshop, and Firefox.

About This Site

CodeKoala.com was developed using Django and Python, and it's bursting at the seams with love.

You're one of 13 active visitors. One is viewing this very page.

Recently Finished
Books I'm Reading

None at this time.

I have read approximately 7,874 pages since September 1, 2008.

Books I Plan To Read