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