Working on tests

This commit is contained in:
Justin Driscoll 2009-01-04 17:14:13 -05:00
parent 82a6812fea
commit 33dd02d568
7 changed files with 113 additions and 124 deletions

View file

@ -6,5 +6,19 @@ Author: Justin Driscoll <justin.driscoll@gmail.com>
Version: 1.0
"""
from models import IKModel
from specs import ImageSpec
# Required PIL classes may or may not be available from the root namespace
# depending on the installation method used.
try:
import Image
import ImageFile
import ImageFilter
import ImageEnhance
except ImportError:
try:
from PIL import Image
from PIL import ImageFile
from PIL import ImageFilter
from PIL import ImageEnhance
except ImportError:
raise ImportError('ImageKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')

View file

@ -5,7 +5,7 @@ from imagekit import processors
class ResizeThumbnail(processors.Resize):
width = 100
height = 75
crop = True
crop = ('center', 'center')
class EnhanceSmall(processors.Adjustment):
contrast = 1.2

View file

@ -19,16 +19,17 @@ class IKModelBase(ModelBase):
user_opts = getattr(cls, 'IK', None)
opts = Options(user_opts)
setattr(cls, '_ik', opts)
try:
module = __import__(opts.config_module, {}, {}, [''])
except ImportError:
raise ImportError('Unable to load imagekit config module: %s' % opts.config_module)
for spec in [spec for spec in module.__dict__.values() if \
issubclass(spec, specs.ImageSpec)]:
setattr(cls, spec.name, specs.Descriptor(spec))
isinstance(spec, type) and issubclass(spec, specs.ImageSpec)]:
setattr(cls, spec.name(), specs.Descriptor(spec))
opts.specs.append(spec)
setattr(cls, '_ik', opts)
class IKModel(models.Model):
@ -39,117 +40,66 @@ class IKModel(models.Model):
"""
__metaclass__ = IKModelBase
CROP_X_NONE = 0
CROP_X_LEFT = 1
CROP_X_CENTER = 2
CROP_X_RIGHT = 3
CROP_Y_NONE = 0
CROP_Y_TOP = 1
CROP_Y_CENTER = 2
CROP_Y_BOTTOM = 3
CROP_X_CHOICES = (
(CROP_X_NONE, 'None'),
(CROP_X_LEFT, 'Left'),
(CROP_X_CENTER, 'Center'),
(CROP_X_RIGHT, 'Right'),
)
CROP_Y_CHOICES = (
(CROP_Y_NONE, 'None'),
(CROP_Y_TOP, 'Top'),
(CROP_Y_CENTER, 'Center'),
(CROP_Y_BOTTOM, 'Bottom'),
)
image = models.ImageField(_('image'), upload_to='photos')
crop_x = models.PositiveSmallIntegerField(choices=CROP_X_CHOICES,
default=CROP_X_CENTER)
crop_y = models.PositiveSmallIntegerField(choices=CROP_Y_CHOICES,
default=CROP_Y_CENTER)
class Meta:
abstract = True
class IK:
pass
def admin_thumbnail_view(self):
prop = getattr(self, 'admin_thumbnail', None)
if prop is None:
return 'An "admin_thumbnail" image spec has not been defined.'
else:
if hasattr(self, 'get_absolute_url'):
return u'<a href="%s"><img src="%s"></a>' % \
(self.get_absolute_url(), prop.url)
else:
return u'<a href="%s"><img src="%s"></a>' % \
(self.image.url, prop.url)
admin_thumbnail_view.short_description = _('Thumbnail')
admin_thumbnail_view.allow_tags = True
@property
def cache_dir(self):
""" Returns the path to the image cache directory """
return os.path.join(os.path.dirname(self._obj.image.path),
return os.path.join(os.path.dirname(self.image.path),
self._ik.cache_dir_name)
@property
def cache_url(self):
""" Returns a url pointing to the image cache directory """
return '/'.join([os.path.dirname(self._obj.image.url),
return '/'.join([os.path.dirname(self.image.url),
self._ik.cache_dir_name])
def _cache_spec(self, spec):
if self._file_exists(spec):
return
# create cache directory if it does not exist
if not os.path.isdir(self._cache_path()):
os.makedirs(self._cache_path())
img = Image.open(self.image.path)
if img.size != spec.size and spec.size != (0, 0):
resized = resize_image(img, spec)
output_filename = self._spec_filename(spec)
try:
if img.format == 'JPEG':
resized.save(output_filename, img.format, quality=int(spec.quality))
else:
try:
im.save(im_filename)
except KeyError:
pass
except IOError, e:
if os.path.isfile(output_filename):
os.unlink(output_filename)
raise e
def _delete_spec(self, spec, remove_dirs=True):
if not self._file_exists(spec):
return
accessor = getattr(self, spec.name)
if os.path.isfile(accessor.path):
os.remove(accessor.path)
if remove_dirs:
self._cleanupget_cache_dirs
def _cleanup_cache_dirs(self):
try:
os.removedirs(self._cache_path())
os.removedirs(self.cache_path)
except:
pass
def _clear_cache(self):
cache = SpecCache()
for photosize in cache.sizes.values():
self._delete_spec(spec, False)
for spec in self._ik.specs:
prop = getattr(self, spec.name())
prop.delete()
self._cleanup_cache_dirs()
def _pre_cache(self):
cache = SpecCache()
for spec in cache.specs.values():
if spec.cache_on_save:
self._cache_spec(spec)
for spec in self._ik.specs:
if spec.pre_cache:
prop = getattr(self, spec.name())
prop.create()
def save(self, *args, **kwargs):
#if self._get_pk_val():
# self._clear_cache()
if self._get_pk_val():
self._clear_cache()
super(IKModel, self).save(*args, **kwargs)
#self._pre_cache()
self._pre_cache()
def delete(self):
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
self._clear_cache()
super(ImageModel, self).delete()
#self._clear_cache()
super(IKModel, self).delete()

View file

@ -15,6 +15,7 @@ class Options(object):
cache_filename_format = "%(filename)s_%(specname)s.%(extension)s"
# Configuration options coded in the models itself
config_module = 'imagekit.config'
specs = []
def __init__(self, opts):
for key, value in opts.__dict__.iteritems():

View file

@ -6,6 +6,7 @@ the class properties as settings. The process method can be overridden as well a
own effects/processes entirely.
"""
from imagekit import *
class ImageProcessor(object):
""" Base image processor class """
@ -17,9 +18,47 @@ class ImageProcessor(object):
class Resize(ImageProcessor):
width = None
height = None
crop = False
crop = False # ('top', 'left')
upscale = False
@classmethod
def process(cls, image):
cur_width, cur_height = image.size
if cls.crop:
ratio = max(float(cls.width)/cur_width, float(cls.height)/cur_height)
resize_x, resize_y = ((cur_width * ratio), (cur_height * ratio))
crop_x, crop_y = (abs(cls.width - resize_x), abs(cls.height - resize_y))
x_diff, y_diff = (int(crop_x / 2), int(crop_y / 2))
box_upper, box_lower = {
'top': (9, cls.height),
'center': (int(y_diff), int(y_diff + cls.height)),
'bottom': (int(crop_y), int(resize_y)),
}[cls.crop[0]]
box_left, box_right = {
'left': (0, cls.width),
'center': (int(x_diff), int(x_diff +cls.width)),
'right': (int(crop_x), int(resize_x)),
}[cls.crop[1]]
box = (box_left, box_upper, box_right, box_lower)
image = image.resize((int(resize_x), int(resize_y)), Image.ANTIALIAS).crop(box)
else:
if not cls.width == 0 and not cls.height == 0:
ratio = min(float(cls.width)/cur_width,
float(cls.height)/cur_height)
else:
if cls.width == 0:
ratio = float(cls.height)/cur_height
else:
ratio = float(cls.width)/cur_width
new_dimensions = (int(round(cur_width*ratio)),
int(round(cur_height*ratio)))
if new_dimensions[0] > cur_width or \
new_dimensions[1] > cur_height:
if not cls.upscale:
return image
image = image.resize(new_dimensions, Image.ANTIALIAS)
return image
class Transpose(ImageProcessor):
""" Rotates or flips the image

View file

@ -5,13 +5,15 @@ inheriting from IKModel will be modified with a descriptor/accessor for each
spec found.
"""
import os
from imagekit import Image
class ImageSpec(object):
cache_on_save = False
pre_cache = False
output_quality = 70
increment_count = False
processors = []
@property
@classmethod
def name(cls):
return getattr(cls, 'access_as', cls.__name__.lower())
@ -34,8 +36,8 @@ class ImageSpec(object):
quality=int(cls.output_quality),
optimize=True)
except IOError, e:
if os.path.isfile(filename):
os.unlink(filename)
if os.path.isfile(save_as):
os.remove(save_as)
raise e
return processed_image
@ -48,7 +50,9 @@ class Accessor(object):
self.spec = spec
def create(self):
self._img = self.spec.process(self.image, save_as=self.path)
if not os.path.isdir(self._obj.cache_dir):
os.makedirs(self._obj.cache_dir)
self._img = self.spec.process(Image.open(self._obj.image.path), save_as=self.path)
def delete(self):
if self.exists:
@ -58,14 +62,14 @@ class Accessor(object):
@property
def name(self):
filename, ext = os.path.splitext(os.path.basename(self._obj.image.path))
return self.spec._ik.cache_filename_format % \
return self._obj._ik.cache_filename_format % \
{'filename': filename,
'sizename': self.spec.name,
'specname': self.spec.name(),
'extension': ext.lstrip('.')}
@property
def path(self):
return os.abspath(os.path.join(self._obj.cache_dir, self.name)
return os.path.abspath(os.path.join(self._obj.cache_dir, self.name))
@property
def url(self):
@ -85,7 +89,7 @@ class Accessor(object):
@property
def image(self):
if self._img is None:
if not self.exists():
if not self.exists:
self.create()
else:
self._img = Image.open(self.path)

View file

@ -3,27 +3,15 @@ import StringIO
import unittest
from django.conf import settings
from django.core.files.base import ContentFile
from django.db import models
from django.test import TestCase
from models import IKModel
from specs import ImageSpec
from imagekit import Image
# Required PIL classes may or may not be available from the root namespace
# depending on the installation method used.
try:
import Image
import ImageFile
import ImageFilter
import ImageEnhance
except ImportError:
try:
from PIL import Image
from PIL import ImageFile
from PIL import ImageFilter
from PIL import ImageEnhance
except ImportError:
raise ImportError(_('Photologue was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.'))
IMG_PATH = os.path.join(os.path.dirname(__file__), 'test.jpg')
class TestPhoto(IKModel):
""" Minimal ImageModel class for testing """
@ -33,13 +21,10 @@ class TestPhoto(IKModel):
class PLTest(TestCase):
""" Base TestCase class """
def setUp(self):
imgfile = StringIO.StringIO()
Image.new('RGB', (100, 100)).save(imgfile, 'JPEG')
content_file = ContentFile(imgfile.read())
Image.new('RGB', (100, 100)).save(IMG_PATH, 'JPEG')
self.p = TestPhoto(name='landscape')
self.p.image.save('image.jpeg', content_file)
self.p.image.save(os.path.basename(IMG_PATH),
ContentFile(open(IMG_PATH, 'rb').read()))
self.p.save()
def test_setup(self):
@ -47,14 +32,10 @@ class PLTest(TestCase):
self.assertEqual(self.p.image.width, 100 )
def test_accessor(self):
pass
self.assertEqual(self.p.thumbnail.siz)
self.assertEqual(self.p.admin_thumbnail.width, 100)
def tearDown(self):
os.remove(IMG_PATH)
path = self.p.image.path
os.remove(path)
return
self.p.delete()
self.failIf(os.path.isfile(path))