Math Captcha Field and Widget

  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
from binascii import hexlify, unhexlify
from random import randint, choice

from django.conf import settings
from django.core.exceptions import ValidationError
from django import forms
from django.forms.fields import MultiValueField, IntegerField, CharField
from django.template.defaultfilters import mark_safe
from django.utils.hashcompat import sha_constructor


class MathCaptchaWidget(forms.MultiWidget):
    def __init__(self, attrs=None):
        self.attrs = attrs or {}
        widgets = (
            forms.TextInput(attrs={'size' : '5'}), #this is the answer input field
            forms.HiddenInput() #this is the hashed answer field to compare to
        )
        super(MathCaptchaWidget, self).__init__(widgets, attrs)

    def decompress(self, value):
        if value:
            """
            Split the initial value set by the field that implements
            this field and return the double. These values get bound
            to the fields.
            """
            question, hashed_answer = value.split('|')
            return [question, hashed_answer]
        return [None, None]

    def format_output(self, rendered_widgets):
        return u'%s%s' % (rendered_widgets[0], rendered_widgets[1])


class MathCaptchaField(MultiValueField):
    widget = MathCaptchaWidget

    def __init__(self, start_int=0, end_int=10, *args, **kwargs):
        #set up error messages
        errors = self.default_error_messages.copy()
        if 'error_messages' in kwargs:
            errors.update(kwargs['error_messages'])
        localize = kwargs.get('localize', False)

        #set integers for question
        x = randint(start_int, end_int)
        y = randint(start_int, end_int)

        #avoid negatives
        if y > x:
            x, y = y, x

        #set up question
        operator = choice('+,-,*'.split(','))
        question = '%i %s %i' % (x, operator, y)

        #make multiplication operator more human-readable
        operator_for_label = '×' if operator == '*' else operator

        #set label for field
        kwargs['label'] = mark_safe('What is %i %s %i' % (x, operator_for_label, y))

        #hash answer and set initial value of form
        hashed_answer = sha_constructor(settings.SECRET_KEY + \
            question).hexdigest() + hexlify(question)
        kwargs['initial'] = '%s|%s' % ('', hashed_answer)

        #set fields
        fields = (
            IntegerField(min_value=0, localize=localize),
            CharField(max_length=255)
        )
        super(MathCaptchaField, self).__init__(fields, *args, **kwargs)

    def compress(self, data_list):
        """Compress takes the place of clean with MultiValueFields"""
        if data_list:
            answer = data_list[0]
            #unhash and eval question. Compare to answer.
            unhashed_answer = eval(unhexlify(data_list[1][40:]))
            if answer != unhashed_answer:
                raise ValidationError(u'Please check your math and try again.')
            return answer
        return None


###
Example usage:

from django import forms
from math_captcha_field import MathCaptchaField

class ContactForm(forms.Form):
    name = forms.CharField(max_length=75)
    #...
    captcha = MathCaptchaField(required=True)

`Optionally, MathCaptchaField has parameters for
the starting and ending integers for the range of
random numbers to select from. Defaults are:
start_int=10, end_int=10.`

More like this

  1. math filter by itchyfingrs 3 years ago
  2. ReCaptcha for django forms (improved and with remoteip) by pinkeen 3 years, 4 months ago
  3. Custom Widget Types for HTML5 Form Fields by leveillej 3 years, 11 months ago
  4. Autocomplete TextInput Widget w/ Static Data (jQuery UI) by JoeLinux 1 year, 1 month ago
  5. Captcha without Freetype or the Python Imaging Library (PIL) by gregb 4 years, 10 months ago

Comments

(Forgotten your password?)