diff --git a/src/imagekit/ikconfig.py b/src/imagekit/ikconfig.py index 2823704..05bf140 100644 --- a/src/imagekit/ikconfig.py +++ b/src/imagekit/ikconfig.py @@ -1,14 +1,15 @@ -from imagekit import specs +from imagekit.specs import ImageSpec +from imagekit import processors -class ResizeThumbnail(specs.Resize): +class ResizeThumbnail(processors.Resize): width = 100 height = 75 crop = True -class EnhanceSmall(specs.Adjustment): +class EnhanceSmall(processors.Adjustment): contrast = 1.2 sharpness = 1.1 -class Thumbnail(specs.Spec): +class Thumbnail(ImageSpec): processors = [ResizeThumbnail, EnhanceSmall] diff --git a/src/imagekit/models.py b/src/imagekit/models.py index 1b1dcbd..0d62bcf 100644 --- a/src/imagekit/models.py +++ b/src/imagekit/models.py @@ -4,97 +4,10 @@ from django.conf import settings from django.db import models from django.db.models.base import ModelBase from django.utils.translation import ugettext_lazy as _ -import specs +from imagekit.options import Options +from imagekit import specs -class IKProperty(object): - def __init__(self, obj, spec): - self._img = None - self._obj = obj - self.spec = spec - - def create(self): - self.spec.process(self.image, self.path) - - def delete(self): - if self.exists: - os.remove(self.path) - self._img = None - - @property - def name(self): - filename, extension = os.path.splitext(self.obj._image_basename()) - return self.spec._ik.cache_filename_format % \ - {'filename': filename, - 'sizename': self.spec.name, - 'extension': extension.lstrip('.')} - - @property - def path(self): - return os.abspath(os.path.join(self._obj._cache_dir(), self.spec.name) - - @property - def url(self): - if not self.exists: - self.create() - 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: - if not self.exists(): - self.create() - self._img = Image.open(self.path) - return self._img - - @property - def width(self): - return self.image.size[0] - - @property - def height(self): - return self.image.size[1] - - @property - def file(self): - if not self.exists: - self.create() - return open(self.path) - - -class IKOptions(object): - # Images will be resized to fit if they are larger than max_image_size - 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 - save_count_on_model_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.ikconfig' - - def __init__(self, opts): - for key, value in opts.__dict__.iteritems(): - setattr(self, key, value) - - -class IKDescriptor(object): - def __init__(self, spec): - self._spec = spec - self._prop = None - - def __get__(self, obj, type=None): - if self._prop is None: - self._prop = IKProperty(obj, self._spec) - return self._prop - - class IKModelBase(ModelBase): def __init__(cls, name, bases, attrs): @@ -103,7 +16,7 @@ class IKModelBase(ModelBase): return user_opts = getattr(cls, 'IK', None) - opts = IKOptions(user_opts) + opts = Options(user_opts) setattr(cls, '_ik', opts) @@ -113,9 +26,8 @@ class IKModelBase(ModelBase): 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)]: - if issubclass(spec, specs.Size): - setattr(cls, spec.__name__.lower(), IKDescriptor(spec)) + issubclass(spec, specs.ImageSpec)]: + setattr(cls, spec.name, specs.Descriptor(spec)) class IKModel(models.Model): @@ -162,19 +74,19 @@ class IKModel(models.Model): class IK: pass - - def _image_basename(self): - """ Returns the basename of the original image file """ - return os.path.basename(self.image.path) - - def _cache_dir(self): + + @property + def cache_dir(self): """ Returns the path to the image cache directory """ - return os.path.join(os.path.dirname(self.image.path), self.IKConfig.cache_dir) + return os.path.join(os.path.dirname(self._obj.image.path), + self._ik.cache_dir_name) - def _cache_url(self): + @property + def cache_url(self): """ Returns a url pointing to the image cache directory """ - return '/'.join([os.path.dirname(self.image.url), self.IKConfig.cache_dir]) - + return '/'.join([os.path.dirname(self._obj.image.url), + self._ik.cache_dir_name]) + def _cache_spec(self, spec): if self._file_exists(spec): return @@ -210,7 +122,7 @@ class IKModel(models.Model): if os.path.isfile(accessor.path): os.remove(accessor.path) if remove_dirs: - self._cleanup_cache_dirs + self._cleanupget_cache_dirs def _cleanup_cache_dirs(self): try: diff --git a/src/imagekit/specs.py b/src/imagekit/specs.py index 22f609d..f9df183 100644 --- a/src/imagekit/specs.py +++ b/src/imagekit/specs.py @@ -1,12 +1,114 @@ -""" Base imagekit specification classes +""" ImageKit image specifications -This module holds the base implementations and defaults for imagekit -specification classes. Users import and subclass these classes to define new -specifications. +All imagekit specifications must inherit from the ImageSpec class. Models +inheriting from IKModel will be modified with a descriptor/accessor for each +spec found. """ -class Spec(object): +class ImageSpec(object): + cache_on_save = False + output_quality = 70 increment_count = False - pre_cache = False - jpeg_quality = 70 processors = [] + + @property + @classmethod + def name(cls): + return getattr(cls, 'access_as', cls.__name__.lower()) + + @classmethod + def create(cls, image, filename): + processed_image = image.copy() + for proc in cls.processors: + processed_image = proc.process(processed_image) + try: + if image.format != 'JPEG': + try: + processed_image.save(im_filename) + return + except KeyError: + pass + processed_image.save(filename, 'JPEG', + quality=int(cls.output_quality), + optimize=True) + except IOError, e: + if os.path.isfile(filename): + os.unlink(filename) + raise e + return processed_image + + +class Accessor(object): + def __init__(self, obj, spec): + self._img = None + self._obj = obj + self.spec = spec + + def create(self): + self._img = self.spec.create(self.image, self.path) + + def delete(self): + if self.exists: + os.remove(self.path) + self._img = None + + @property + def name(self): + filename, ext = os.path.splitext(os.path.basename(self._obj.image.path)) + return self.spec._ik.cache_filename_format % \ + {'filename': filename, + 'sizename': self.spec.name, + 'extension': ext.lstrip('.')} + + @property + def path(self): + return os.abspath(os.path.join(self._obj.cache_dir, self.name) + + @property + def url(self): + if not self.exists: + self.create() + 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: + if not self.exists(): + self.create() + else: + self._img = Image.open(self.path) + return self._img + + @property + def width(self): + return self.image.size[0] + + @property + def height(self): + return self.image.size[1] + + @property + def file(self): + if not self.exists: + self.create() + return open(self.path) + + +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