Login

Simple, stand-alone reCaptcha form module

Author:
SmileyChris
Posted:
July 20, 2009
Language:
Python
Version:
1.0
Score:
1 (after 1 ratings)

An easy-to-use Django forms integration of the reCaptcha service.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""
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'''<script type="text/javascript">
   var RecaptchaOptions = %r;
</script>
'''
RECAPTCHA_HTML = u'''%(options)s<script type="text/javascript" src="http://api.recaptcha.net/challenge?k=%(public_key)s"></script>
<noscript>
   <iframe src="http://api.recaptcha.net/noscript?k=%(public_key)s"
       height="300" width="500" frameborder="0"></iframe><br />
   <textarea name="recaptcha_challenge_field" rows="3" cols="40">
   </textarea>
   <input type="hidden" name="recaptcha_response_field" value="manual_challenge" />
</noscript>'''


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

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

Please login first before commenting.