reCAPTCHA Field for Django Forms
August 27, 2008
Integrating ReCAPTCHA seamlessly into Django's form library is a bit tricky but it is possible. There are many other solutions available on the web for using reCAPTCHA with Django but they all involve a lot of manual labor inside your views. This approach will allow you to just add a captcha field to your existing forms. (With one small exception) reCAPTCHA is an excellent choice for preventing spam. Although other python libraries exist for generating CAPTCHAs, some, such as the gimpy-style CAPTCHAs are not nearly as reCAPTCHA. In addition to having a secure CAPTCHA, you'll also be helping to digitize books; it's a win-win scenario! Furthermore, it is easy to run into other security pitfalls when generating your own CAPTCHAs. For instance, many websites will store the answer to a CAPTCHA in a session on the server side. Once the form has been submitted, the web application will forget to clear this value. A spammer can then re-use this session multiple times with the same CAPTCHA answer, because the application will not change the answer on the server side until the CAPTCHA image is re-downloaded. Before we get started, please verify that you are using Django 1.0+ (or the subversion tree as of August 2008.) There are significant differences between the forms library in Django 1.0 and 0.96 and this code will not work on older releases. You'll also need the python ReCAPTCHA library: sudo easy_install recaptcha-client
Now that your system is setup, sign up for a ReCAPTCHA account and generate a public and private key for you domain. Put this in your settings.py file: Excerpt: settings.py RECAPTCHA_PUBLIC = 'blahblah'
RECAPTCHA_PRIVATE = 'blargh'
Now add the following code to your applications: File: forms.py from recaptcha.client import captcha
from django import forms
from django.conf import settings
from django.utils.safestring import mark_safe
class ReCaptcha(forms.Widget):
input_type = None # Subclasses must define this.
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
html = u"<script>var RecaptchaOptions = {theme : '%s'};</script>" % (
final_attrs.get('theme', 'white'))
html += captcha.displayhtml(settings.RECAPTCHA_PUBLIC)
return mark_safe(html)
def value_from_datadict(self, data, files, name):
return {
'recaptcha_challenge_field': data.get('recaptcha_challenge_field', None),
'recaptcha_response_field': data.get('recaptcha_response_field', None),
}
# hack: Inherit from FileField so a hack in Django passes us the
# initial value for our field, which should be set to the IP
class ReCaptchaField(forms.FileField):
widget = ReCaptcha
default_error_messages = {
'invalid-site-public-key': u"Invalid public key",
'invalid-site-private-key': u"Invalid private key",
'invalid-request-cookie': u"Invalid cookie",
'incorrect-captcha-sol': u"Invalid entry, please try again.",
'verify-params-incorrect': u"The parameters to verify were incorrect, make sure you are passing all the required parameters.",
'invalid-referrer': u"Invalid referrer domain",
'recaptcha-not-reachable': u"Could not contact reCAPTCHA server",
}
def clean(self, data, initial):
if initial is None or initial == '':
raise Exception("ReCaptchaField requires the client's IP be set to the initial value")
ip = initial
resp = captcha.submit(data.get("recaptcha_challenge_field", None),
data.get("recaptcha_response_field", None),
settings.RECAPTCHA_PRIVATE, ip)
if not resp.is_valid:
raise forms.ValidationError(self.default_error_messages.get(
resp.error_code, "Unknown error: %s" % (resp.error_code)))
class CaptchaForm(forms.Form):
captcha = ReCaptchaField()
File: views.py from django.http import HttpResponse
from django.shortcuts import render_to_response
from project.app.forms import CaptchaForm
def captcha_form(request):
if request.method == "POST":
# slight hack, we need to give recaptcha the client's IP address
form = CaptchaForm(request.POST, initial={'captcha': request.META['REMOTE_ADDR']})
if form.is_valid():
return HttpResponse('SUCCESS')
else:
form = CaptchaForm()
return render_to_response(request, "captchaform.html", {'form': form})
File: captchaform.html <form method="post" action="/captchaform/">
{{ form.as_p }}
<p><input type="submit"></p>
</form>
Now if all has gone according to plan you should have a CAPTCHA form similar to the one at the bottom of the page. Django will automatically handle all the dirty work when you call form.is_valid(). Developing this solution was especially difficult for two reasons. Firstly, the reCAPTCHA widget requires two form fields. This is possible to do with the pre-1.0 release of Django by overriding value_from_datadict() in your Widget. Be very scared though, because this feature is undocumented. Secondly, reCAPTCHA needs to use the client's IP address to verify the request. The forms library does not let us anywhere near the request object so we're passing the IP from our view through the initial dictionary. Ready for the scary part? Normally django doesn't give us access to the initial value for a field when cleaning. The only exception is a hack that passes initial to FileFields. So naturally the solution was to falsely masquerade our Field as a FileField. Hey, at least there's no harm done. Enjoy your new, easy to use CAPTCHA. |
Tag Cloudaccounting assembly asterisk c django erlang games hacking i18n latin1 linux mysql networking python qos speaking tc travel tutorial unicode utf8 web Archive
July 2009 Popular Content
Asterisk Voice Changer
(6924 Views) Recent Comments
Asterisk Voice Changer
on Feb 17 by Yesh |

Comments
vikash
on December 2, 2008
[Permalink]
HI
JeffreyATW
on April 12, 2009
[Permalink]
You've got an error in views.py: you should check if request.method == "POST". You have it as '!='.
artur gajowy
on July 4, 2009
[Permalink]
you know it's tempting to test it here? ;)
Mike
on January 26, 2010
[Permalink]
helpful to me today! Thanks!
Post Comment