""" An easy-to-use Django forms integration of the reCaptcha service. v1.0.1 To use, simply base your form off the ``RecaptchaForm`` class. This class adds a new argument that must be provided to the form, ``remote_ip``. Two settings which must be set in your project's ``settings`` module are ``RECAPTCHA_PUBLIC_KEY`` and ``RECAPTCHA_SECRET_KEY``, the public and private keys for your domain, respectively. Following is an example of creating a basic comment form class and then using an instance of the form in a view:: from django import forms from mysite.utils import recaptcha class CommentForm(recaptcha.RecaptchaForm): name = forms.CharField() comment = forms.CharField(widget=Textarea()) captcha = recaptcha.RecaptchaField() def comment(request): comment_form = CommentForm(remote_ip=request.META['REMOTE_ADDR']) ... If you need to use a different base form (such as ``ModelForm``), use multiple inheritance like so:: class MyModelForm(BaseRecaptchaForm, ModelForm): ... """ import httplib from django import forms from django.conf import settings from django.utils.safestring import mark_safe from django.utils.http import urlencode OPTIONS_SCRIPT_HTML = u''' ''' RECAPTCHA_HTML = u'''%(options)s ''' class RecaptchaWidget(forms.Widget): def __init__(self, theme=None, tabindex=None, public_key=None): ''' From http://recaptcha.net/apidocs/captcha/client.html#look-n-feel:: theme: 'red' | 'white' | 'blackglass' | 'clean' Defines which theme to use for reCAPTCHA. tabindex: any integer Sets a tabindex for the reCAPTCHA text box. If other elements in the form use a tabindex, this should be set so that navigation is easier for the user. The optional ``public_key`` argument can be used to override the default use of the project-wide ``RECAPTCHA_PUBLIC_KEY`` setting. ''' options = {} if theme: options['theme'] = theme if tabindex: options['tabindex'] = tabindex self.options = options self.public_key = public_key or settings.RECAPTCHA_PUBLIC_KEY super(RecaptchaWidget, self).__init__() def render(self, name, value, attrs=None): args = dict(public_key=self.public_key, options='') if self.options: args['options'] = OPTIONS_SCRIPT_HTML % self.options return mark_safe(RECAPTCHA_HTML % args) def value_from_datadict(self, data, files, name): challenge = data.get('recaptcha_challenge_field') response = data.get('recaptcha_response_field') return (challenge, response) def id_for_label(self, id_): return None class RecaptchaField(forms.Field): widget = RecaptchaWidget default_error_messages = { 'required': u'Please enter the CAPTCHA solution.', 'invalid': u'An incorrect CAPTCHA solution was entered.', 'no-remote-ip': u'CAPTCHA failed due to no visible IP address.', 'challenge-error': u'An error occurred with the CAPTCHA service - try ' 'refreshing.', 'unknown-error': u'The CAPTCHA service returned the following error: ' '%(code)s.', } def __init__(self, private_key=None, *args, **kwargs): """ The optional ``private_key`` argument can be used to override the default use of the project-wide ``RECAPTCHA_SECRET_KEY`` setting. """ self.remote_ip = None self.private_key = private_key or settings.RECAPTCHA_SECRET_KEY super(RecaptchaField, self).__init__(*args, **kwargs) def clean(self, value): if not self.remote_ip: raise forms.ValidationError(self.error_messages['no-remote-ip']) value = super(RecaptchaField, self).clean(value) challenge, response = value if not challenge: raise forms.ValidationError(self.error_messages['challenge-error']) if not response: raise forms.ValidationError(self.error_messages['required']) try: value = validate_recaptcha(self.remote_ip, challenge, response, self.private_key) except RecaptchaError, e: if e.code == 'incorrect-captcha-sol': raise forms.ValidationError(self.error_messages['invalid']) raise forms.ValidationError(self.error_messages['unknown-error'] % {'code': e.code}) return value class BaseRecaptchaForm(forms.BaseForm): def __init__(self, remote_ip, *args, **kwargs): super(BaseRecaptchaForm, self).__init__(*args, **kwargs) for field in self.fields.values(): if isinstance(field, RecaptchaField): field.remote_ip = remote_ip class RecaptchaForm(BaseRecaptchaForm, forms.Form): pass class RecaptchaError(Exception): def __init__(self, code): self.code = code def __str__(self): return self.code def validate_recaptcha(remote_ip, challenge, response, private_key): assert challenge, 'No challenge was provided for reCaptcha validation' # Request validation from recaptcha.net params = dict(privatekey=private_key, remoteip=remote_ip, challenge=challenge, response=response) params = urlencode(params) headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} conn = httplib.HTTPConnection("api-verify.recaptcha.net") conn.request("POST", "/verify", params, headers) response = conn.getresponse() if response.status == 200: data = response.read() else: data = '' conn.close() # Validate based on response data result = data.startswith('true') if not result: bits = data.split('\n', 2) error_code = '' if len(bits) > 1: error_code = bits[1] raise RecaptchaError(error_code) # Return dictionary return result