| 1 | # -*- coding:UTF-8 -*- |
|---|
| 2 | # Copyright (c) 2007 Brandon Low |
|---|
| 3 | # Licensed under the GPL v2 |
|---|
| 4 | from django import forms |
|---|
| 5 | from django.forms.util import ValidationError #, flatatt |
|---|
| 6 | from models import Captcha |
|---|
| 7 | from settings import BASE_URL, MIN_LENGTH, MAX_LENGTH |
|---|
| 8 | from util import get_string |
|---|
| 9 | from django.utils.translation import ugettext_lazy as _ |
|---|
| 10 | |
|---|
| 11 | class CaptchaWidget(forms.TextInput): |
|---|
| 12 | |
|---|
| 13 | def __init__(self, captcha=None, *args, **kwargs): |
|---|
| 14 | self.captcha_id = captcha.id |
|---|
| 15 | super(CaptchaWidget, self).__init__(*args, **kwargs) |
|---|
| 16 | |
|---|
| 17 | def render(self, *args, **kwargs): |
|---|
| 18 | value = super(CaptchaWidget, self).render(*args, **kwargs) |
|---|
| 19 | |
|---|
| 20 | # I'd rather have this in the label, but they aren't per-instance |
|---|
| 21 | img_src = "/".join([BASE_URL, str(self.captcha_id),""]) |
|---|
| 22 | image = "<img src='%s' alt='Captcha' /> <br/>" % img_src |
|---|
| 23 | return " ".join([image, value]) |
|---|
| 24 | |
|---|
| 25 | class CaptchaField(forms.CharField): |
|---|
| 26 | |
|---|
| 27 | def __init__(self, captcha=None, *args, **kwargs): |
|---|
| 28 | self.captcha = captcha |
|---|
| 29 | self.widget = CaptchaWidget(captcha=captcha) |
|---|
| 30 | # CharField takes care of setting the max length onto the widget |
|---|
| 31 | super(CaptchaField, self).__init__(min_length=MIN_LENGTH, |
|---|
| 32 | max_length=MAX_LENGTH, label=_("Captcha"), *args, **kwargs) |
|---|
| 33 | |
|---|
| 34 | def clean(self, value): |
|---|
| 35 | value = super(CaptchaField, self).clean(value) |
|---|
| 36 | if not self.captcha.text.lower() == value.lower(): |
|---|
| 37 | raise ValidationError(u'Enter the string exactly as shown') |
|---|
| 38 | return value |
|---|
| 39 | |
|---|
| 40 | class CaptchaForm(forms.Form): |
|---|
| 41 | def __init__(self, *args, **kwargs): |
|---|
| 42 | super(CaptchaForm, self).__init__(*args, **kwargs) |
|---|
| 43 | |
|---|
| 44 | # The super.__init__ call does not actually set the data or initial |
|---|
| 45 | # onto the fields/widgets, so we just need the captcha_id field and |
|---|
| 46 | # widget to exist before self.get_captcha() is called |
|---|
| 47 | self.fields['captcha_id'] = forms.IntegerField(label=_('ZZZZZZ')) |
|---|
| 48 | self.fields['captcha_id'].widget = forms.HiddenInput() |
|---|
| 49 | |
|---|
| 50 | if not self.is_bound: |
|---|
| 51 | # New form, new captcha |
|---|
| 52 | c = Captcha(text=get_string()) |
|---|
| 53 | c.save() |
|---|
| 54 | # Ensure that the hidden field is properly populated |
|---|
| 55 | self.initial['captcha_id'] = c.id |
|---|
| 56 | else: |
|---|
| 57 | c = self.get_captcha() |
|---|
| 58 | |
|---|
| 59 | self.fields['captcha'] = CaptchaField(captcha=c) |
|---|
| 60 | |
|---|
| 61 | # Only called on a bound form |
|---|
| 62 | def get_captcha(self): |
|---|
| 63 | # Somewhat hacky, but best way I had to get the captcha id |
|---|
| 64 | f = self.fields['captcha_id'] |
|---|
| 65 | w = f.widget |
|---|
| 66 | value = w.value_from_datadict(self.data, None, self.add_prefix('captcha_id')) |
|---|
| 67 | captcha_id = f.clean(value) |
|---|
| 68 | |
|---|
| 69 | try: |
|---|
| 70 | # Always clean expired captchas before getting one for validation |
|---|
| 71 | Captcha.clean_expired() |
|---|
| 72 | return Captcha.objects.get(pk=captcha_id) |
|---|
| 73 | except Captcha.DoesNotExist: |
|---|
| 74 | # The original captcha expired, make a new one for revalidation |
|---|
| 75 | c = Captcha(id=captcha_id,text=get_string()) |
|---|
| 76 | c.save() |
|---|
| 77 | return c |
|---|
| 78 | |
|---|
| 79 | |
|---|
| 80 | def full_clean(self): |
|---|
| 81 | super(CaptchaForm, self).full_clean() |
|---|
| 82 | |
|---|
| 83 | # Once the form has validated, the captcha is 'used up' |
|---|
| 84 | if self.is_valid(): |
|---|
| 85 | self.fields['captcha'].captcha.delete() |
|---|