mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-03-17 05:40:25 +00:00
Working on tests
This commit is contained in:
parent
82a6812fea
commit
33dd02d568
7 changed files with 113 additions and 124 deletions
|
|
@ -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.')
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Reference in a new issue