Archive for the ‘captcha’ tag
Subclassing Django’s CommentForm
Django comes with batteries included and a very powerful and useful battery is the django.contrib.comments framework. However, to make it in time for the long awaited 1.0 release the comments framework was released without customization hooks. See #8630 for more details.
The long story in a short sentence is that you cannot subclass or customize CommentForm in todays version without getting your hands dirty.
I recently enabled Captcha verification to comments on this site to prevent spammers from filling my database with non-public comments. Tor Brede Vekterli has written about how to do this by using signals, but for technical reasons I’d rather not go down that road. So I landed on the next possible strategy, namely subclassing CommentForm.
My subclass is a standard Django form:
class CaptchaCommentForm(CommentForm):
captcha = forms.CharField(max_length=20, label='Enter this word')
def clean_captcha(self):
# verify self.cleaned_data['captcha'] here, details omitted,
# raise forms.ValidationError if verification fails
Since I wanted to control the way this field is displayed by adding an image I also edited my form template. In my comments/form.html template I added a check to include the captcha image:
{% for field in form %}
{% ifequal field.name "captcha" %}
do whatever it takes to display the custom field.
{% else %}
display regular comment form field
{% endifequal %}
{% endfor %}
As I mentioned above the comments framework is not really designed to be extended or customized in this way. To make the framework use my CaptchaCommentForm instead of its own CommentForm I resorted to monkeypatching!
The framework uses a single function, comments.get_form, whenever it needs a fresh CommentForm instance. What I wanted is for that function to return my CaptchaCommentForm instead. Here is what I did to my urls.py:
from django.contrib import comments
from forms import CaptchaCommentForm
def override_get_form():
return CaptchaCommentForm()
comments.get_form = override_get_form
Thanks to the dynamic features of Python we can simply replace the original function with our own that returns the correct instance. Not pretty, but it works.