diff --git a/src/imagekit/models.py b/src/imagekit/models.py index 48cb929..0350d9a 100644 --- a/src/imagekit/models.py +++ b/src/imagekit/models.py @@ -16,7 +16,7 @@ class IKModelBase(ModelBase): if not parents: return - user_opts = getattr(cls, 'IK', None) + user_opts = getattr(cls, 'IKConfig', None) opts = Options(user_opts) try: @@ -24,11 +24,13 @@ class IKModelBase(ModelBase): 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 \ - isinstance(spec, type) and issubclass(spec, specs.ImageSpec)]: + for spec in [spec for spec in module.__dict__.values() \ + if isinstance(spec, type) \ + and issubclass(spec, specs.ImageSpec) \ + and spec != specs.ImageSpec]: setattr(cls, spec.name(), specs.Descriptor(spec)) opts.specs.append(spec) - + setattr(cls, '_ik', opts) @@ -41,12 +43,10 @@ class IKModel(models.Model): """ __metaclass__ = IKModelBase - image = models.ImageField(_('image'), upload_to='photos') - class Meta: abstract = True - class IK: + class IKConfig: pass def admin_thumbnail_view(self): @@ -59,21 +59,25 @@ class IKModel(models.Model): (self.get_absolute_url(), prop.url) else: return u'' % \ - (self.image.url, prop.url) + (self.ik_image_field.url, prop.url) admin_thumbnail_view.short_description = _('Thumbnail') admin_thumbnail_view.allow_tags = True + @property + def ik_image_field(self): + return getattr(self, self._ik.image_field) + @property def cache_dir(self): """ Returns the path to the image cache directory """ - return os.path.join(os.path.dirname(self.image.path), - self._ik.cache_dir_name) + return os.path.join(os.path.dirname(self.ik_image_field.path), + self._ik.cache_dir) @property def cache_url(self): """ Returns a url pointing to the image cache directory """ - return '/'.join([os.path.dirname(self.image.url), - self._ik.cache_dir_name]) + return '/'.join([os.path.dirname(self.ik_image_field.url), + self._ik.cache_dir]) def _cleanup_cache_dirs(self): try: @@ -94,12 +98,12 @@ class IKModel(models.Model): 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() + self._clear_cache() super(IKModel, self).delete() diff --git a/src/imagekit/options.py b/src/imagekit/options.py index ebde0f8..508c0aa 100644 --- a/src/imagekit/options.py +++ b/src/imagekit/options.py @@ -4,19 +4,14 @@ class Options(object): """ Class handling per-model imagekit options """ - # Images will be resized to fit if they are larger than max_image_size + image_field = 'image' max_image_size = None - # Media subdirectories - image_dir_name = 'images' - cache_dir_name = 'cache' - # If given the image view count will be saved as this field name + cache_dir = 'ik_cache' save_count_as = None - # String pattern used to generate cache file names 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(): - setattr(self, key, value) \ No newline at end of file + setattr(self, key, value) + self.specs = [] \ No newline at end of file diff --git a/src/imagekit/processors.py b/src/imagekit/processors.py index 13a37be..8176b61 100644 --- a/src/imagekit/processors.py +++ b/src/imagekit/processors.py @@ -42,11 +42,11 @@ class Resize(ImageProcessor): 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: + if not cls.width is None and not cls.height is None: ratio = min(float(cls.width)/cur_width, float(cls.height)/cur_height) else: - if cls.width == 0: + if cls.width is None: ratio = float(cls.height)/cur_height else: ratio = float(cls.width)/cur_width diff --git a/src/imagekit/specs.py b/src/imagekit/specs.py index 112eed0..8e8c997 100644 --- a/src/imagekit/specs.py +++ b/src/imagekit/specs.py @@ -52,16 +52,20 @@ class Accessor(object): def create(self): 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) + self._img = self.spec.process(Image.open(self._obj.ik_image_field.path), save_as=self.path) def delete(self): if self.exists: os.remove(self.path) self._img = None + @property + def exists(self): + return os.path.isfile(self.path) + @property def name(self): - filename, ext = os.path.splitext(os.path.basename(self._obj.image.path)) + filename, ext = os.path.splitext(os.path.basename(self._obj.ik_image_field.path)) return self._obj._ik.cache_filename_format % \ {'filename': filename, 'specname': self.spec.name(), @@ -75,17 +79,13 @@ class Accessor(object): def url(self): if not self.exists: self.create() - if self.spec.increment_count(): + if self.spec.increment_count: fieldname = self._obj._ik.save_count_as if fieldname is not None: current_count = getattr(self._obj, fieldname) setattr(self._obj, fieldname, current_count + 1) return '/'.join([self._obj.cache_url, self.name]) - @property - def exists(self): - return os.path.isfile(self.path) - @property def image(self): if self._img is None: @@ -113,9 +113,6 @@ class Accessor(object): class Descriptor(object): def __init__(self, spec): self._spec = spec - self._prop = None def __get__(self, obj, type=None): - if self._prop is None: - self._prop = Accessor(obj, self._spec) - return self._prop + return Accessor(obj, self._spec) diff --git a/src/imagekit/tests.py b/src/imagekit/tests.py index a0801f9..94987a1 100644 --- a/src/imagekit/tests.py +++ b/src/imagekit/tests.py @@ -1,5 +1,5 @@ import os -import StringIO +import tempfile import unittest from django.conf import settings from django.core.files.base import ContentFile @@ -8,34 +8,71 @@ from django.test import TestCase from models import IKModel from specs import ImageSpec +from imagekit import processors from imagekit import Image + +class ResizeToWidth(processors.Resize): + width = 100 + +class ResizeToHeigh(processors.Resize): + height = 100 + +class ResizeToFit(processors.Resize): + width = 100 + height = 100 + +class ResizeCrop(ResizeToFit): + crop = True + +class TestResizeToWidth(ImageSpec): + access_as = 'to_width' + processors = [ResizeToWidth] + + IMG_PATH = os.path.join(os.path.dirname(__file__), 'test.jpg') + class TestPhoto(IKModel): """ Minimal ImageModel class for testing """ - name = models.CharField(max_length=30) + image = models.ImageField(upload_to='images') + + class IKConfig: + config_module = 'imagekit.tests' + - -class PLTest(TestCase): +class IKTest(TestCase): """ Base TestCase class """ def setUp(self): - Image.new('RGB', (100, 100)).save(IMG_PATH, 'JPEG') - self.p = TestPhoto(name='landscape') - self.p.image.save(os.path.basename(IMG_PATH), - ContentFile(open(IMG_PATH, 'rb').read())) + # create a test image using tempfile and PIL + self.tmp = tempfile.TemporaryFile() + Image.new('RGB', (800, 600)).save(self.tmp, 'JPEG') + self.tmp.seek(0) + self.p = TestPhoto() + self.p.image.save(os.path.basename('test.jpg'), + ContentFile(self.tmp.read())) self.p.save() + # destroy temp file + self.tmp.close() + + def test_config(self): + self.assertEqual(self.p._ik.specs, [TestResizeToWidth]) def test_setup(self): - self.assert_(self.p.image is not None) - self.assertEqual(self.p.image.width, 100 ) + self.assertEqual(self.p.image.width, 800) + self.assertEqual(self.p.image.height, 600) - def test_accessor(self): - self.assertEqual(self.p.admin_thumbnail.width, 100) + def test_to_width(self): + self.assertEqual(self.p.to_width.width, 100) + self.assertEqual(self.p.to_width.height, 75) + def test_url(self): + url = "%s/%s" % (self.p.cache_url, 'test_to_width.jpg') + self.assertEqual(self.p.to_width.url, url) + def tearDown(self): - os.remove(IMG_PATH) + # make sure image file is deleted path = self.p.image.path self.p.delete() self.failIf(os.path.isfile(path))