From a3cade7303cc10cfdbd4c99f272cb7e0111c6ba8 Mon Sep 17 00:00:00 2001 From: marsh_ Date: Tue, 7 Jul 2015 10:58:58 +0300 Subject: [PATCH 1/3] Captcha info can be stored in SessionStore. --- captcha/backends/__init__.py | 0 captcha/backends/base.py | 26 ++++++++ captcha/backends/db.py | 38 +++++++++++ captcha/backends/session.py | 28 ++++++++ captcha/conf/settings.py | 3 +- captcha/fields.py | 22 ++++-- captcha/management/commands/captcha_clean.py | 39 ++++++----- captcha/models.py | 70 ++++++++++---------- captcha/views.py | 24 +++++-- 9 files changed, 186 insertions(+), 64 deletions(-) create mode 100644 captcha/backends/__init__.py create mode 100644 captcha/backends/base.py create mode 100644 captcha/backends/db.py create mode 100644 captcha/backends/session.py diff --git a/captcha/backends/__init__.py b/captcha/backends/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/captcha/backends/base.py b/captcha/backends/base.py new file mode 100644 index 00000000..0751a05b --- /dev/null +++ b/captcha/backends/base.py @@ -0,0 +1,26 @@ +from captcha.conf import settings as captcha_settings + +class DoesNotExist(Exception): + """ Can't find captcha in store """ + pass + +class BaseStore(object): + DoesNotExist = DoesNotExist + + def generate_key(self): + """ + Generate captcha with unique key + """ + return captcha_settings.get_challenge()() + + def remove_expired(self): + """ + Remove expired captcha records + """ + pass + + def get(self, response=None, hashkey=None, allow_expired = True): + """ + Get captcha from store, or rise exception if captcha wasn't found + """ + pass diff --git a/captcha/backends/db.py b/captcha/backends/db.py new file mode 100644 index 00000000..02590a33 --- /dev/null +++ b/captcha/backends/db.py @@ -0,0 +1,38 @@ +from captcha.conf import settings as captcha_settings +from ..models import CaptchaStore, get_safe_now +from .base import BaseStore + +class DBStore(BaseStore): + def __init__(self, key = None): + self._captcha = {} + if key: + try: + cap = CaptchaStore.objects.get(hashkey = key) + self._captcha = { + 'hashkey': cap.hashkey, + 'challenge': cap.challenge, + 'response': cap.response, + 'expiration': cap.expiration, + } + except CaptchaStore.DoesNotExist: + raise self.DoesNotExist + + def __getitem__(self, key): + return self._captcha[key] + + def remove_expired(self): + CaptchaStore.objects.filter(expiration__lte=get_safe_now()).delete() + + def generate_key(self): + return CaptchaStore.generate_key() + + def get(self, response=None, hashkey=None, allow_expired = True): + store = DBStore(hashkey) + if response and store['response'] != response: + raise self.DoesNotExist + if not allow_expired and store['expiration'] < get_safe_now(): + raise self.DoesNotExist + return store + + def delete(self): + CaptchaStore.objects.filter(hashkey = self['hashkey']).delete() diff --git a/captcha/backends/session.py b/captcha/backends/session.py new file mode 100644 index 00000000..f014fec1 --- /dev/null +++ b/captcha/backends/session.py @@ -0,0 +1,28 @@ +from .base import BaseStore +from captcha.conf import settings as captcha_settings +from django.conf import settings +from importlib import import_module +Store = import_module(settings.SESSION_ENGINE).SessionStore + +class SessionStore(BaseStore): + + def generate_key(self): + challenge, response = super(SessionStore, self).generate_key() + store = Store() + store.set_expiry(60 * int(captcha_settings.CAPTCHA_TIMEOUT)) + store['challenge']=challenge + store['response']=response + store.save() + return store.session_key + + def remove_expired(self): + Store.clear_expired() + + def get(self, response=None, hashkey=None, allow_expired = True): + s = Store(session_key=hashkey) + if response: + if s['response'] != response: + raise self.DoesNotExist + if not allow_expired and s.get_expiry_age() < 0: + raise self.DoesNotExist + return s diff --git a/captcha/conf/settings.py b/captcha/conf/settings.py index b102bcf3..22a2a088 100644 --- a/captcha/conf/settings.py +++ b/captcha/conf/settings.py @@ -1,4 +1,4 @@ -import os +import os from django.conf import settings CAPTCHA_FONT_PATH = getattr(settings, 'CAPTCHA_FONT_PATH', os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'fonts/Vera.ttf'))) @@ -18,6 +18,7 @@ CAPTCHA_DICTIONARY_MIN_LENGTH = getattr(settings, 'CAPTCHA_DICTIONARY_MIN_LENGTH', 0) CAPTCHA_DICTIONARY_MAX_LENGTH = getattr(settings, 'CAPTCHA_DICTIONARY_MAX_LENGTH', 99) CAPTCHA_IMAGE_SIZE = getattr(settings, 'CAPTCHA_IMAGE_SIZE', None) +CAPTCHA_STORE = getattr(settings, 'CAPTCHA_STORE', 'DB') if CAPTCHA_IMAGE_BEFORE_FIELD: CAPTCHA_OUTPUT_FORMAT = getattr(settings, 'CAPTCHA_OUTPUT_FORMAT', '%(image)s %(hidden_field)s %(text_field)s') diff --git a/captcha/fields.py b/captcha/fields.py index 7fcc243f..4104c471 100644 --- a/captcha/fields.py +++ b/captcha/fields.py @@ -1,5 +1,4 @@ -from captcha.conf import settings -from captcha.models import CaptchaStore, get_safe_now +from captcha.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse, NoReverseMatch from django.forms import ValidationError @@ -7,6 +6,17 @@ from django.forms.widgets import TextInput, MultiWidget, HiddenInput from django.utils.translation import ugettext, ugettext_lazy from six import u +from .backends.base import BaseStore + +CaptchaStore = None +if settings.CAPTCHA_STORE == 'SESSION': + from .backends.session import SessionStore + CaptchaStore = SessionStore() +elif settings.CAPTCHA_STORE == 'DB': + from .backends.db import DBStore + CaptchaStore = DBStore() +else: + raise ImproperlyConfigured class BaseCaptchaTextInput(MultiWidget): @@ -132,15 +142,15 @@ def clean(self, value): # automatically pass the test try: # try to delete the captcha based on its hash - CaptchaStore.objects.get(hashkey=value[0]).delete() - except CaptchaStore.DoesNotExist: + CaptchaStore.get(hashkey=value[0]).delete() + except BaseStore.DoesNotExist: # ignore errors pass elif not self.required and not response: pass else: try: - CaptchaStore.objects.get(response=response, hashkey=value[0], expiration__gt=get_safe_now()).delete() - except CaptchaStore.DoesNotExist: + CaptchaStore.get(response=response, hashkey=value[0], allow_expired = False).delete() + except BaseStore.DoesNotExist: raise ValidationError(getattr(self, 'error_messages', {}).get('invalid', ugettext_lazy('Invalid CAPTCHA'))) return value diff --git a/captcha/management/commands/captcha_clean.py b/captcha/management/commands/captcha_clean.py index 88f28e40..179b990f 100644 --- a/captcha/management/commands/captcha_clean.py +++ b/captcha/management/commands/captcha_clean.py @@ -1,5 +1,6 @@ from django.core.management.base import BaseCommand from captcha.models import get_safe_now +from captcha.conf import settings import sys @@ -7,19 +8,27 @@ class Command(BaseCommand): help = "Clean up expired captcha hashkeys." def handle(self, **options): - from captcha.models import CaptchaStore - verbose = int(options.get('verbosity')) - expired_keys = CaptchaStore.objects.filter(expiration__lte=get_safe_now()).count() - if verbose >= 1: - print("Currently %d expired hashkeys" % expired_keys) - try: - CaptchaStore.remove_expired() - except: + if settings.CAPTCHA_STORE == 'SESSION': + from ...backends.session import SessionStore + try: + SessionStore().remove_expired() + print("Expired sessions removed.") + except: + print("Unable to delete expired sessions.") + elif settings.CAPTCHA_STORE == 'DB': + from captcha.models import CaptchaStore + verbose = int(options.get('verbosity')) + expired_keys = CaptchaStore.objects.filter(expiration__lte=get_safe_now()).count() if verbose >= 1: - print("Unable to delete expired hashkeys.") - sys.exit(1) - if verbose >= 1: - if expired_keys > 0: - print("%d expired hashkeys removed." % expired_keys) - else: - print("No keys to remove.") + print("Currently %d expired hashkeys" % expired_keys) + try: + CaptchaStore.remove_expired() + except: + if verbose >= 1: + print("Unable to delete expired hashkeys.") + sys.exit(1) + if verbose >= 1: + if expired_keys > 0: + print("%d expired hashkeys removed." % expired_keys) + else: + print("No keys to remove.") diff --git a/captcha/models.py b/captcha/models.py index 32c79ab7..51903a64 100644 --- a/captcha/models.py +++ b/captcha/models.py @@ -26,38 +26,38 @@ def get_safe_now(): pass return datetime.datetime.now() - -class CaptchaStore(models.Model): - challenge = models.CharField(blank=False, max_length=32) - response = models.CharField(blank=False, max_length=32) - hashkey = models.CharField(blank=False, max_length=40, unique=True) - expiration = models.DateTimeField(blank=False) - - def save(self, *args, **kwargs): - self.response = self.response.lower() - if not self.expiration: - self.expiration = get_safe_now() + datetime.timedelta(minutes=int(captcha_settings.CAPTCHA_TIMEOUT)) - if not self.hashkey: - key_ = ( - smart_text(randrange(0, MAX_RANDOM_KEY)) + - smart_text(time.time()) + - smart_text(self.challenge, errors='ignore') + - smart_text(self.response, errors='ignore') - ).encode('utf8') - self.hashkey = hashlib.sha1(key_).hexdigest() - del(key_) - super(CaptchaStore, self).save(*args, **kwargs) - - def __unicode__(self): - return self.challenge - - def remove_expired(cls): - cls.objects.filter(expiration__lte=get_safe_now()).delete() - remove_expired = classmethod(remove_expired) - - @classmethod - def generate_key(cls): - challenge, response = captcha_settings.get_challenge()() - store = cls.objects.create(challenge=challenge, response=response) - - return store.hashkey +if captcha_settings.CAPTCHA_STORE == "DB": + class CaptchaStore(models.Model): + challenge = models.CharField(blank=False, max_length=32) + response = models.CharField(blank=False, max_length=32) + hashkey = models.CharField(blank=False, max_length=40, unique=True) + expiration = models.DateTimeField(blank=False) + + def save(self, *args, **kwargs): + self.response = self.response.lower() + if not self.expiration: + self.expiration = get_safe_now() + datetime.timedelta(minutes=int(captcha_settings.CAPTCHA_TIMEOUT)) + if not self.hashkey: + key_ = ( + smart_text(randrange(0, MAX_RANDOM_KEY)) + + smart_text(time.time()) + + smart_text(self.challenge, errors='ignore') + + smart_text(self.response, errors='ignore') + ).encode('utf8') + self.hashkey = hashlib.sha1(key_).hexdigest() + del(key_) + super(CaptchaStore, self).save(*args, **kwargs) + + def __unicode__(self): + return self.challenge + + def remove_expired(cls): + cls.objects.filter(expiration__lte=get_safe_now()).delete() + remove_expired = classmethod(remove_expired) + + @classmethod + def generate_key(cls): + challenge, response = captcha_settings.get_challenge()() + store = cls.objects.create(challenge=challenge, response=response) + + return store.hashkey diff --git a/captcha/views.py b/captcha/views.py index df1aa36f..d31c6c19 100644 --- a/captcha/views.py +++ b/captcha/views.py @@ -1,6 +1,5 @@ from captcha.conf import settings from captcha.helpers import captcha_image_url -from captcha.models import CaptchaStore from django.http import HttpResponse, Http404 import random import re @@ -29,6 +28,17 @@ # Distance of the drawn text from the top of the captcha image from_top = 4 +from .backends.base import BaseStore + +CaptchaStore = None +if settings.CAPTCHA_STORE == 'SESSION': + from .backends.session import SessionStore + CaptchaStore = SessionStore() +elif settings.CAPTCHA_STORE == 'DB': + from .backends.db import DBStore + CaptchaStore = DBStore() +else: + raise ImproperlyConfigured def getsize(font, text): if hasattr(font, 'getoffset'): @@ -47,12 +57,12 @@ def makeimg(size): def captcha_image(request, key, scale=1): try: - store = CaptchaStore.objects.get(hashkey=key) - except CaptchaStore.DoesNotExist: + store = CaptchaStore.get(hashkey=key) + except BaseStore.DoesNotExist: # HTTP 410 Gone status so that crawlers don't index these expired urls. return HttpResponse(status=410) - text = store.challenge + text = store['challenge'] if settings.CAPTCHA_FONT_PATH.lower().strip().endswith('ttf'): font = ImageFont.truetype(settings.CAPTCHA_FONT_PATH, settings.CAPTCHA_FONT_SIZE * scale) @@ -125,12 +135,12 @@ def captcha_image(request, key, scale=1): def captcha_audio(request, key): if settings.CAPTCHA_FLITE_PATH: try: - store = CaptchaStore.objects.get(hashkey=key) - except CaptchaStore.DoesNotExist: + store = CaptchaStore.get(hashkey=key) + except BaseStore.DoesNotExist: # HTTP 410 Gone status so that crawlers don't index these expired urls. return HttpResponse(status=410) - text = store.challenge + text = store['challenge'] if 'captcha.helpers.math_challenge' == settings.CAPTCHA_CHALLENGE_FUNCT: text = text.replace('*', 'times').replace('-', 'minus') else: From b69d07e05cf530bcd0a4dbbec1b2dfba5c5006e0 Mon Sep 17 00:00:00 2001 From: marsh_ Date: Tue, 7 Jul 2015 15:31:10 +0300 Subject: [PATCH 2/3] django 1.4 compatible --- captcha/backends/session.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/captcha/backends/session.py b/captcha/backends/session.py index f014fec1..bf629046 100644 --- a/captcha/backends/session.py +++ b/captcha/backends/session.py @@ -3,6 +3,7 @@ from django.conf import settings from importlib import import_module Store = import_module(settings.SESSION_ENGINE).SessionStore +import django class SessionStore(BaseStore): @@ -16,7 +17,8 @@ def generate_key(self): return store.session_key def remove_expired(self): - Store.clear_expired() + if not django.get_version() < '1.5': + Store.clear_expired() def get(self, response=None, hashkey=None, allow_expired = True): s = Store(session_key=hashkey) From 4899abadc1c34cba086d5f395cfe6ac68eb29d0c Mon Sep 17 00:00:00 2001 From: marsh_ Date: Thu, 9 Jul 2015 12:58:49 +0300 Subject: [PATCH 3/3] minor fixes and test --- captcha/backends/base.py | 6 ++- captcha/backends/db.py | 16 +++--- captcha/backends/session.py | 22 ++++++--- captcha/fields.py | 33 ++++++++----- captcha/tests/tests.py | 99 ++++++++++++++++++++++++++++++++++++- captcha/views.py | 2 + testproject/settings.py | 2 +- 7 files changed, 150 insertions(+), 30 deletions(-) diff --git a/captcha/backends/base.py b/captcha/backends/base.py index 0751a05b..99e4f092 100644 --- a/captcha/backends/base.py +++ b/captcha/backends/base.py @@ -1,9 +1,11 @@ from captcha.conf import settings as captcha_settings + class DoesNotExist(Exception): """ Can't find captcha in store """ pass + class BaseStore(object): DoesNotExist = DoesNotExist @@ -12,14 +14,14 @@ def generate_key(self): Generate captcha with unique key """ return captcha_settings.get_challenge()() - + def remove_expired(self): """ Remove expired captcha records """ pass - def get(self, response=None, hashkey=None, allow_expired = True): + def get(self, response=None, hashkey=None, allow_expired=True): """ Get captcha from store, or rise exception if captcha wasn't found """ diff --git a/captcha/backends/db.py b/captcha/backends/db.py index 02590a33..362fa5c3 100644 --- a/captcha/backends/db.py +++ b/captcha/backends/db.py @@ -1,13 +1,13 @@ -from captcha.conf import settings as captcha_settings from ..models import CaptchaStore, get_safe_now from .base import BaseStore + class DBStore(BaseStore): - def __init__(self, key = None): + def __init__(self, key=None): self._captcha = {} if key: try: - cap = CaptchaStore.objects.get(hashkey = key) + cap = CaptchaStore.objects.get(hashkey=key) self._captcha = { 'hashkey': cap.hashkey, 'challenge': cap.challenge, @@ -16,23 +16,23 @@ def __init__(self, key = None): } except CaptchaStore.DoesNotExist: raise self.DoesNotExist - + def __getitem__(self, key): return self._captcha[key] - + def remove_expired(self): CaptchaStore.objects.filter(expiration__lte=get_safe_now()).delete() def generate_key(self): return CaptchaStore.generate_key() - def get(self, response=None, hashkey=None, allow_expired = True): + def get(self, response=None, hashkey=None, allow_expired=True): store = DBStore(hashkey) if response and store['response'] != response: raise self.DoesNotExist if not allow_expired and store['expiration'] < get_safe_now(): raise self.DoesNotExist return store - + def delete(self): - CaptchaStore.objects.filter(hashkey = self['hashkey']).delete() + CaptchaStore.objects.filter(hashkey=self['hashkey']).delete() diff --git a/captcha/backends/session.py b/captcha/backends/session.py index bf629046..1a643293 100644 --- a/captcha/backends/session.py +++ b/captcha/backends/session.py @@ -1,27 +1,35 @@ from .base import BaseStore from captcha.conf import settings as captcha_settings from django.conf import settings -from importlib import import_module -Store = import_module(settings.SESSION_ENGINE).SessionStore import django - +try: + from importlib import import_module + Store = import_module(settings.SESSION_ENGINE).SessionStore +except: + # py 2.6 + backend = __import__(settings.SESSION_ENGINE, globals(), locals(), ['SessionStore'], -1) + Store = backend.SessionStore + + class SessionStore(BaseStore): def generate_key(self): challenge, response = super(SessionStore, self).generate_key() store = Store() store.set_expiry(60 * int(captcha_settings.CAPTCHA_TIMEOUT)) - store['challenge']=challenge - store['response']=response + store['challenge'] = challenge + store['response'] = response store.save() return store.session_key - + def remove_expired(self): if not django.get_version() < '1.5': Store.clear_expired() - def get(self, response=None, hashkey=None, allow_expired = True): + def get(self, response=None, hashkey=None, allow_expired=True): s = Store(session_key=hashkey) + if not s.get('response'): + raise self.DoesNotExist if response: if s['response'] != response: raise self.DoesNotExist diff --git a/captcha/fields.py b/captcha/fields.py index 4104c471..82f89314 100644 --- a/captcha/fields.py +++ b/captcha/fields.py @@ -8,16 +8,6 @@ from six import u from .backends.base import BaseStore -CaptchaStore = None -if settings.CAPTCHA_STORE == 'SESSION': - from .backends.session import SessionStore - CaptchaStore = SessionStore() -elif settings.CAPTCHA_STORE == 'DB': - from .backends.db import DBStore - CaptchaStore = DBStore() -else: - raise ImproperlyConfigured - class BaseCaptchaTextInput(MultiWidget): """ @@ -40,6 +30,17 @@ def fetch_captcha_store(self, name, value, attrs=None): Fetches a new CaptchaStore This has to be called inside render """ + + CaptchaStore = None + if settings.CAPTCHA_STORE == 'SESSION': + from .backends.session import SessionStore + CaptchaStore = SessionStore() + elif settings.CAPTCHA_STORE == 'DB': + from .backends.db import DBStore + CaptchaStore = DBStore() + else: + raise ImproperlyConfigured + try: reverse('captcha-image', args=('dummy',)) except NoReverseMatch: @@ -135,6 +136,16 @@ def compress(self, data_list): return None def clean(self, value): + CaptchaStore = None + if settings.CAPTCHA_STORE == 'SESSION': + from .backends.session import SessionStore + CaptchaStore = SessionStore() + elif settings.CAPTCHA_STORE == 'DB': + from .backends.db import DBStore + CaptchaStore = DBStore() + else: + raise ImproperlyConfigured + super(CaptchaField, self).clean(value) response, value[1] = (value[1] or '').strip().lower(), '' CaptchaStore.remove_expired() @@ -150,7 +161,7 @@ def clean(self, value): pass else: try: - CaptchaStore.get(response=response, hashkey=value[0], allow_expired = False).delete() + CaptchaStore.get(response=response, hashkey=value[0], allow_expired=False).delete() except BaseStore.DoesNotExist: raise ValidationError(getattr(self, 'error_messages', {}).get('invalid', ugettext_lazy('Invalid CAPTCHA'))) return value diff --git a/captcha/tests/tests.py b/captcha/tests/tests.py index c82f2378..c94b1faf 100644 --- a/captcha/tests/tests.py +++ b/captcha/tests/tests.py @@ -2,11 +2,14 @@ from captcha.conf import settings from captcha.fields import CaptchaField, CaptchaTextInput from captcha.models import CaptchaStore, get_safe_now +from captcha.backends.db import DBStore +from captcha.backends.session import SessionStore from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse from django.test import TestCase from django.utils.translation import ugettext_lazy +import django import datetime import json import re @@ -29,7 +32,6 @@ class CaptchaCase(TestCase): urls = 'captcha.tests.urls' def setUp(self): - self.stores = {} self.__current_settings_output_format = settings.CAPTCHA_OUTPUT_FORMAT self.__current_settings_dictionary = settings.CAPTCHA_WORDS_DICTIONARY @@ -332,5 +334,100 @@ def test_image_size(self): settings.CAPTCHA_IMAGE_SIZE = __current_test_mode_setting +class StoresCase(TestCase): + urls = 'captcha.tests.urls' + + # store tests + def test_db_store(self): + store = DBStore() + key = store.generate_key() + record = store.get(hashkey=key) + self.assertEqual(record['hashkey'], key) + self.assertNotEqual(record._captcha.get('challenge'), None) + self.assertNotEqual(record._captcha.get('response'), None) + record.delete() + try: + store.get(hashkey=key) + self.fail('Record deletion error') + except: + pass + + key = store.generate_key() + cap = CaptchaStore.objects.get(hashkey=key) + cap.expiration = get_safe_now() - datetime.timedelta(minutes=1) + cap.save() + try: + store.get(hashkey=key, allow_expired=False) + self.fail() + except: + pass + + store.remove_expired() + try: + store.get(hashkey=key) + self.fail('remove_expired failed') + except: + pass + + def test_session_store(self): + store = SessionStore() + key = store.generate_key() + record = store.get(hashkey=key) + self.assertEqual(record.session_key, key) + self.assertNotEqual(record.get('challenge'), None) + self.assertNotEqual(record.get('response'), None) + record.delete() + try: + store.get(hashkey=key) + self.fail('Record deletion error') + except: + pass + + key = store.generate_key() + cap = store.get(hashkey=key) + cap.set_expiry(-1 * 60) + cap.save() + try: + store.get(hashkey=key, allow_expired=False) + self.fail() + except: + pass + + if not django.get_version() < '1.5': + # django lower than 1.5 can't remove expired sessions + store.remove_expired() + try: + store.get(hashkey=key) + self.fail('remove_expired failed') + except: + pass + + # view tests + def testFormSubmit(self): + settings.CAPTCHA_STORE = 'SESSION' + r = self.client.get(reverse('captcha-test')) + self.assertEqual(r.status_code, 200) + + store = SessionStore() + hash_ = re.findall(r'value="([0-9a-z]+)"', str(r.content))[0] + response = store.get(hashkey=hash_)['response'] + + r = self.client.post(reverse('captcha-test'), dict(captcha_0=hash_, captcha_1=response, subject='xxx', sender='asasd@asdasd.com')) + self.assertEqual(r.status_code, 200) + self.assertTrue(str(r.content).find('Form validated') > 0) + + r = self.client.post(reverse('captcha-test'), dict(captcha_0=hash_, captcha_1=response, subject='xxx', sender='asasd@asdasd.com')) + self.assertEqual(r.status_code, 200) + self.assertFalse(str(r.content).find('Form validated') > 0) + + def testWrongSubmit(self): + settings.CAPTCHA_STORE = 'SESSION' + for urlname in ('captcha-test', 'captcha-test-model-form'): + r = self.client.get(reverse(urlname)) + self.assertEqual(r.status_code, 200) + r = self.client.post(reverse(urlname), dict(captcha_0='abc', captcha_1='wrong response', subject='xxx', sender='asasd@asdasd.com')) + self.assertFormError(r, 'form', 'captcha', ugettext_lazy('Invalid CAPTCHA')) + + def trivial_challenge(): return 'trivial', 'trivial' diff --git a/captcha/views.py b/captcha/views.py index d31c6c19..956a2404 100644 --- a/captcha/views.py +++ b/captcha/views.py @@ -1,6 +1,7 @@ from captcha.conf import settings from captcha.helpers import captcha_image_url from django.http import HttpResponse, Http404 +from django.core.exceptions import ImproperlyConfigured import random import re import tempfile @@ -40,6 +41,7 @@ else: raise ImproperlyConfigured + def getsize(font, text): if hasattr(font, 'getoffset'): return [x + y for x, y in zip(font.getsize(text), font.getoffset(text))] diff --git a/testproject/settings.py b/testproject/settings.py index 538842ce..9a0db118 100644 --- a/testproject/settings.py +++ b/testproject/settings.py @@ -33,7 +33,7 @@ 'captcha', ] -LANGUAGE_CODE = "en" +LANGUAGE_CODE = "en-us" LANGUAGES = ( ('en', 'English'),