Patching, more patching. SO MUCH PATCHING
Friday, 21 October 2011 15:00![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Another code post, but don't worry, I have things to talk about for my regular update too.
I've been trying to set up a captcha field - specifically Google's ReCaptcha - on a not-as-old-as-you'd-think Pylons project which uses ToscaWidgets for rendering forms. I'd link you to Pylon's page as well but it merged with Repoze and apparently maintenance is hard. I'm pretty frowny about this but that's for another day.
On top of that, the 0.8 release of tw.recaptcha, which is supposed to make it easy to add recaptcha into ToscaWidget forms, didn't work, due to two things:
* You have to give it the remote IP in the Validator's constructor, while the Validator needs to be created before you know that.
* Formencode (apparently) validates every form twice for some reason, and of course this doesn't work on things like captcha where the correct answer changes per request.
Anyway, someone kindly left a comment on the wiki page for tw.recaptcha and although Google managed to mangle it by forcing WikiMarkup on people, I managed to reconstruct it into something that works. Below is a diff that can be applied to the 0.8 release in Pypi and makes it approximately 20% cooler.
To me it looks a bit like this introduces a memory leak since we're having to store every previous attempted captcha, but since this particular project gets restarted fairly often to perform log rotation, I'm not going to worry about that at the moment.
For completeness, the form and validator back on my side of things looks like this:
I've been trying to set up a captcha field - specifically Google's ReCaptcha - on a not-as-old-as-you'd-think Pylons project which uses ToscaWidgets for rendering forms. I'd link you to Pylon's page as well but it merged with Repoze and apparently maintenance is hard. I'm pretty frowny about this but that's for another day.
On top of that, the 0.8 release of tw.recaptcha, which is supposed to make it easy to add recaptcha into ToscaWidget forms, didn't work, due to two things:
* You have to give it the remote IP in the Validator's constructor, while the Validator needs to be created before you know that.
* Formencode (apparently) validates every form twice for some reason, and of course this doesn't work on things like captcha where the correct answer changes per request.
Anyway, someone kindly left a comment on the wiki page for tw.recaptcha and although Google managed to mangle it by forcing WikiMarkup on people, I managed to reconstruct it into something that works. Below is a diff that can be applied to the 0.8 release in Pypi and makes it approximately 20% cooler.
--- ./tw.recaptcha-0.8/tw/recaptcha/validator.py 2008-06-01 12:07:51.000000000 +0100 +++ ./tw-recaptcha-f846368854fe/tw/recaptcha/validator.py 2011-10-21 14:51:51.000000000 +0100 @@ -5,6 +5,8 @@ import gettext _ = gettext.gettext class ReCaptchaValidator(FancyValidator): """ @see formencode.validators.FieldsMatch @@ -28,6 +30,7 @@ self.remote_ip = remote_ip self.field_names = ['recaptcha_challenge_field', 'recaptcha_response_field'] + self.submitted_pairs = {} def validate_partial(self, field_dict, state): for name in self.field_names: @@ -38,13 +41,21 @@ def validate_python(self, field_dict, state): challenge = field_dict['recaptcha_challenge_field'] response = field_dict['recaptcha_response_field'] + if self.submitted_pairs.has_key(challenge): + #log.debug("This pair has already been submitted by this class!!!! CAN'T DO THAT!!!") + #log.debug(self.submitted_pairs[challenge]) + return self.submitted_pairs[challenge][1] if response == '' or challenge == '': error = Invalid(self.message('missing', state), field_dict, state) error.error_dict = {'recaptcha_response_field':'Missing value'} raise error + if callable(self.remote_ip): + remote_ip = self.remote_ip() + else: + remote_ip = self.remote_ip params = urllib.urlencode({ 'privatekey': self.private_key, - 'remoteip' : self.remote_ip, + 'remoteip' : remote_ip, 'challenge': challenge, 'response' : response, }) @@ -61,7 +72,10 @@ httpresp.close(); return_code = return_values[0] if not return_code == "true": + self.submitted_pairs[challenge] = (response, False) error = Invalid(self.message('incorrect', state), field_dict, state) error.error_dict = {'recaptcha_response_field':self.message('incorrect', state)} raise error + self.submitted_pairs[challenge] = (response, True) return True
To me it looks a bit like this introduces a memory leak since we're having to store every previous attempted captcha, but since this particular project gets restarted fairly often to perform log rotation, I'm not going to worry about that at the moment.
For completeness, the form and validator back on my side of things looks like this:
class NewUserSchema(formencode.Schema): name = formencode.validators.UnicodeString(min=5, max=64) # ... [don't add 'recaptcha_response_field' here specifically] chained_validators = [tw.recaptcha.validator.ReCaptchaValidator(config['recaptcha.private.key'], request.environ.get("X_FORWARDED_FOR", request.environ["REMOTE_ADDR"]))] filter_extra_fields = False allow_extra_fields = True filter_extra_fields = False apply_form = twf.TableForm('apply_form', action='apply', validator=NewUserSchema, children=[ twf.TextField('name', label_text="Your name", validator=twf.validators.NotEmpty), # ... tw.recaptcha.widgets.ReCaptchaWidget('recaptcha_response_field', public_key=config['recaptcha.public.key']), ], submit_text='Register')