I've made some more changes/fixes to this, in case anyone's interested. Here's what's changed from the original:
- updated for compatibility w/ django-1.1 - using sha1 instead of md5 to store captcha image (theoretically more secure) - added expire_time config variable - added imagetype variable (defaults to jpg) - fixed bug where .svn dirs (or any other files in the fonts dir) messed up the font loading And here's the patch: --- cut here --- diff -x .svn -Nur captcha/__init__.py captcha.new/__init__.py --- captcha/__init__.py 2007-06-14 12:53:55.000000000 -0500 +++ captcha.new/__init__.py 2009-03-16 10:15:43.000000000 -0500 @@ -6,18 +6,21 @@ """ from os import listdir, sep, access, mkdir, W_OK, R_OK, remove, chmod, path, rmdir, stat +from glob import glob from urllib import basejoin -import md5 +import sha from random import choice, randrange from sys import modules import tempfile import datetime from PIL import Image, ImageColor, ImageFont, ImageDraw -from django.newforms import * -from django.newforms.fields import CharField +from django.forms import Widget, Field, ValidationError +from django.forms.fields import CharField from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django.utils.translation import gettext from django.utils.datastructures import MultiValueDict +from django.utils.safestring import mark_safe def clean_old_entries(captchas_dir, max_age=1200): """maintainance function for deleting all expired captchas @@ -40,7 +43,7 @@ pass def mycrypt(value, salt): - return md5.new(value.lower() + salt + settings.SECRET_KEY).hexdigest() + return sha.new(value.lower() + salt + settings.SECRET_KEY).hexdigest() class CaptchaWidget(Widget): """generate a captcha image and display the image along with @@ -53,6 +56,7 @@ 'upload_url': None, # default: None (uses MEDIA_URL/ captchas) 'captchaconf_dir': None, # default: None (uses the directory of the captcha module) 'auto_cleanup': True, # default: True (delete all captchas older than 20 minutes) + 'expire_time' : 1200, # default: 1200 (time in seconds after which old captchas are deleted) 'minmaxvpos': (8, 15), # default: (8, 15) (vertical position of characters) 'minmaxrotations': (-30,31), # default: (-30,31) (rotate characters) 'minmaxheight': (30,45), # default: (30,45) (font size) @@ -63,6 +67,7 @@ 'imagesize': (200,60), # default: (200,60) 'iterations': 1, # default 1 (change to a high value (200 is a good choice) # for trying out new settings) + 'imagetype' : 'jpg', } # change colors to tuples if possible try: @@ -83,17 +88,21 @@ if not self.csettings['captchaconf_dir']: myfile = modules[self.__module__].__file__ self.csettings['captchaconf_dir'] = myfile[:myfile.rfind (sep)] + super(CaptchaWidget, self).__init__(*args, **kwargs) def render(self, name, value, attrs=None): img = Image.new('RGB',self.csettings['imagesize'], self.csettings['bgcolor']) for dummy in range(self.csettings['iterations']): img = self.generate_image(img) - return u'''<input type="hidden" name="%(name)s" value="captcha.%(hiddentext)s" -/><img src="%(imageurl)s" alt="" /><br -/><input type="text" name="%(name)s" id="id_%(name)s" />''' % {'name':name, - 'hiddentext': self.hiddentext, - 'imageurl': self.imageurl } + html = u'''<input type="hidden" name="%(name)s" value="captcha.%(hiddentext)s"/> + <img src="%(imageurl)s" alt="" class="captcha" /> + <input type="text" name="%(name)s" id="id_%(name) s" class="text" /> + ''' % {'name':name, + 'hiddentext': self.hiddentext, + 'imageurl': self.imageurl } + + return mark_safe(html) def generate_image(self, bgimage): """ create a image file. @@ -102,13 +111,13 @@ HASH is the hashed solution. """ if self.csettings['auto_cleanup']: - clean_old_entries(self.csettings['captchas_dir']) + clean_old_entries(self.csettings['captchas_dir'], self.csettings['expire_time']) cs = self.csettings imagesize = cs['imagesize'] posnew = 7 fontdir = path.join(cs['captchaconf_dir'], 'fonts') - fontnames = [path.join(fontdir, x) for x in listdir (fontdir) ] + fontnames = [path.join(fontdir, x) for x in glob('%s/*.ttf' % fontdir) ] # generate characters @@ -161,17 +170,21 @@ self.imageurl = '%s/%s/%s' % (cs['upload_url'], self.hiddentext, plainfilename) imagepath = path.join(dirpath, plainfilename) - try: - bgimage.convert('P', palette=Image.ADAPTIVE, colors=4).save(imagepath + '.gif') - ext = '.gif' - except: - bgimage.save(imagepath + '.jpg') - ext = '.jpg' - self.imageurl += ext - chmod(imagepath + ext, 0644) + + imagetype = cs.get('imagetype') + + if imagetype == 'gif': + bgimage.convert('P', palette=Image.ADAPTIVE, colors=4).save(imagepath + imagetype) + elif imagetype in ('jpg', 'png'): + bgimage.save('%s.%s' % (imagepath, imagetype)) + else: + raise ImproperlyConfigured('Invalid captcha image imagetype: %s' % imagetype) + + self.imageurl = '%s.%s' % (self.imageurl, imagetype) + chmod('%s.%s' % (imagepath, imagetype), 0644) return bgimage - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): if isinstance(data, MultiValueDict): return data.getlist(name) return data.get(name, None) @@ -200,6 +213,7 @@ settingcaptchas_dir = None self.captchas_dir = options.get('captchas_dir') or settingcaptchas_dir or \ path.join(settings.MEDIA_ROOT, 'captchas') + super(CaptchaField, self).__init__(required=required, widget=CaptchaWidget(options=options), label=label, help_text=help_text, *args, **kwargs --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django users" group. To post to this group, send email to django-users@googlegroups.com To unsubscribe from this group, send email to django-users+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/django-users?hl=en -~----------~----~----~----~------~----~------~--~---