mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-03-16 21:30:23 +00:00
Create ImageSpecs; remove generators
This commit is contained in:
parent
675fd02afb
commit
d2087aa168
10 changed files with 127 additions and 118 deletions
|
|
@ -1,3 +1,7 @@
|
|||
from . import conf
|
||||
from .base import ImageSpec
|
||||
|
||||
|
||||
__title__ = 'django-imagekit'
|
||||
__author__ = 'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett, Matthew Tretter, Eric Eldredge'
|
||||
__version__ = (2, 0, 1, 'final', 0)
|
||||
|
|
|
|||
|
|
@ -2,20 +2,26 @@ from django.conf import settings
|
|||
from hashlib import md5
|
||||
import os
|
||||
import pickle
|
||||
from .exceptions import UnknownExtensionError
|
||||
from .imagecache.backends import get_default_image_cache_backend
|
||||
from .imagecache.strategies import StrategyWrapper
|
||||
from .lib import StringIO
|
||||
from .processors import ProcessorPipeline
|
||||
from .utils import (img_to_fobj, open_image, IKContentFile, extension_to_format,
|
||||
suggest_extension, UnknownExtensionError)
|
||||
from .utils import (open_image, extension_to_format, IKContentFile, img_to_fobj,
|
||||
suggest_extension)
|
||||
|
||||
|
||||
class SpecFileGenerator(object):
|
||||
def __init__(self, processors=None, format=None, options=None,
|
||||
autoconvert=True, storage=None):
|
||||
self.processors = processors
|
||||
self.format = format
|
||||
self.options = options or {}
|
||||
self.autoconvert = autoconvert
|
||||
self.storage = storage
|
||||
class BaseImageSpec(object):
|
||||
processors = None
|
||||
format = None
|
||||
options = None
|
||||
autoconvert = True
|
||||
|
||||
def __init__(self, processors=None, format=None, options=None, autoconvert=None):
|
||||
self.processors = processors or self.processors or []
|
||||
self.format = format or self.format
|
||||
self.options = options or self.options
|
||||
self.autoconvert = self.autoconvert if autoconvert is None else autoconvert
|
||||
|
||||
def get_processors(self, source_file):
|
||||
processors = self.processors
|
||||
|
|
@ -23,6 +29,28 @@ class SpecFileGenerator(object):
|
|||
processors = processors(source_file)
|
||||
return processors
|
||||
|
||||
def get_hash(self, source_file):
|
||||
return md5(''.join([
|
||||
source_file.name,
|
||||
pickle.dumps(self.get_processors(source_file)),
|
||||
self.format,
|
||||
pickle.dumps(self.options),
|
||||
str(self.autoconvert),
|
||||
]).encode('utf-8')).hexdigest()
|
||||
|
||||
def generate_filename(self, source_file):
|
||||
source_filename = source_file.name
|
||||
filename = None
|
||||
if source_filename:
|
||||
hash = self.get_hash(source_file)
|
||||
extension = suggest_extension(source_filename, self.format)
|
||||
filename = os.path.normpath(os.path.join(
|
||||
settings.IMAGEKIT_CACHE_DIR,
|
||||
os.path.splitext(source_filename)[0],
|
||||
'%s%s' % (hash, extension)))
|
||||
|
||||
return filename
|
||||
|
||||
def process_content(self, content, filename=None, source_file=None):
|
||||
img = open_image(content)
|
||||
original_format = img.format
|
||||
|
|
@ -49,27 +77,44 @@ class SpecFileGenerator(object):
|
|||
content = IKContentFile(filename, imgfile.read(), format=format)
|
||||
return img, content
|
||||
|
||||
def get_hash(self, source_file):
|
||||
return md5(''.join([
|
||||
source_file.name,
|
||||
pickle.dumps(self.get_processors(source_file)),
|
||||
self.format,
|
||||
pickle.dumps(self.options),
|
||||
str(self.autoconvert),
|
||||
]).encode('utf-8')).hexdigest()
|
||||
|
||||
def generate_filename(self, source_file):
|
||||
source_filename = source_file.name
|
||||
filename = None
|
||||
if source_filename:
|
||||
hash = self.get_hash(source_file)
|
||||
extension = suggest_extension(source_filename, self.format)
|
||||
filename = os.path.normpath(os.path.join(
|
||||
settings.IMAGEKIT_CACHE_DIR,
|
||||
os.path.splitext(source_filename)[0],
|
||||
'%s%s' % (hash, extension)))
|
||||
class ImageSpec(BaseImageSpec):
|
||||
storage = None
|
||||
image_cache_backend = None
|
||||
image_cache_strategy = settings.IMAGEKIT_DEFAULT_IMAGE_CACHE_STRATEGY
|
||||
|
||||
return filename
|
||||
def __init__(self, processors=None, format=None, options=None,
|
||||
storage=None, autoconvert=None, image_cache_backend=None,
|
||||
image_cache_strategy=None):
|
||||
"""
|
||||
:param processors: A list of processors to run on the original image.
|
||||
:param format: The format of the output file. If not provided,
|
||||
ImageSpecField will try to guess the appropriate format based on the
|
||||
extension of the filename and the format of the input image.
|
||||
:param options: A dictionary that will be passed to PIL's
|
||||
``Image.save()`` method as keyword arguments. Valid options vary
|
||||
between formats, but some examples include ``quality``,
|
||||
``optimize``, and ``progressive`` for JPEGs. See the PIL
|
||||
documentation for others.
|
||||
:param autoconvert: Specifies whether automatic conversion using
|
||||
``prepare_image()`` should be performed prior to saving.
|
||||
:param image_field: The name of the model property that contains the
|
||||
original image.
|
||||
:param storage: A Django storage system to use to save the generated
|
||||
image.
|
||||
:param image_cache_backend: An object responsible for managing the state
|
||||
of cached files. Defaults to an instance of
|
||||
``IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND``
|
||||
:param image_cache_strategy: A dictionary containing callbacks that
|
||||
allow you to customize how and when the image cache is validated.
|
||||
Defaults to ``IMAGEKIT_DEFAULT_SPEC_FIELD_IMAGE_CACHE_STRATEGY``
|
||||
|
||||
"""
|
||||
super(ImageSpec, self).__init__(processors=processors, format=format,
|
||||
options=options, autoconvert=autoconvert)
|
||||
self.storage = storage or self.storage
|
||||
self.image_cache_backend = image_cache_backend or self.image_cache_backend or get_default_image_cache_backend()
|
||||
self.image_cache_strategy = StrategyWrapper(image_cache_strategy or self.image_cache_strategy)
|
||||
|
||||
def generate_file(self, filename, source_file, save=True):
|
||||
"""
|
||||
|
|
@ -7,4 +7,4 @@ class ImageKitConf(AppConf):
|
|||
CACHE_BACKEND = None
|
||||
CACHE_DIR = 'CACHE/images'
|
||||
CACHE_PREFIX = 'ik-'
|
||||
DEFAULT_SPEC_FIELD_IMAGE_CACHE_STRATEGY = 'imagekit.imagecache.strategies.Pessimistic'
|
||||
DEFAULT_IMAGE_CACHE_STRATEGY = 'imagekit.imagecache.strategies.Pessimistic'
|
||||
|
|
|
|||
8
imagekit/exceptions.py
Normal file
8
imagekit/exceptions.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
|
||||
class UnknownExtensionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownFormatError(Exception):
|
||||
pass
|
||||
|
|
@ -2,7 +2,6 @@ import os
|
|||
|
||||
from django.db.models.fields.files import ImageFieldFile
|
||||
|
||||
from .generators import SpecFileGenerator
|
||||
from .utils import SpecWrapper, suggest_extension
|
||||
|
||||
|
||||
|
|
@ -11,9 +10,6 @@ class ImageSpecFile(ImageFieldFile):
|
|||
spec = SpecWrapper(spec)
|
||||
|
||||
self.storage = spec.storage or source_file.storage
|
||||
self.generator = SpecFileGenerator(processors=spec.processors,
|
||||
format=spec.format, options=spec.options,
|
||||
autoconvert=spec.autoconvert, storage=self.storage)
|
||||
|
||||
self.spec = spec
|
||||
self.source_file = source_file
|
||||
|
|
@ -44,11 +40,11 @@ class ImageSpecFile(ImageFieldFile):
|
|||
source_filename = self.source_file.name
|
||||
filepath, basename = os.path.split(source_filename)
|
||||
filename = os.path.splitext(basename)[0]
|
||||
extension = suggest_extension(source_filename, self.generator.format)
|
||||
extension = suggest_extension(source_filename, self.spec.format)
|
||||
new_name = '%s%s' % (filename, extension)
|
||||
cache_filename = ['cache', 'iktt'] + self.spec_id.split(':') + \
|
||||
[filepath, new_name]
|
||||
return os.path.join(*cache_filename)
|
||||
|
||||
def generate(self, save=True):
|
||||
return self.generator.generate_file(self.name, self.source_file, save)
|
||||
return self.spec.generate_file(self.name, self.source_file, save)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,2 @@
|
|||
from .. import conf
|
||||
from .fields import ImageSpecField, ProcessedImageField
|
||||
import warnings
|
||||
|
||||
|
||||
class ImageSpec(ImageSpecField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
warnings.warn('ImageSpec has been moved to'
|
||||
' imagekit.models.ImageSpecField. Please use that instead.',
|
||||
DeprecationWarning)
|
||||
super(ImageSpec, self).__init__(*args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_init, post_save, post_delete
|
||||
|
||||
from ...imagecache.backends import get_default_image_cache_backend
|
||||
from ...imagecache.strategies import StrategyWrapper
|
||||
from ...generators import SpecFileGenerator
|
||||
from .files import ImageSpecFieldFile, ProcessedImageFieldFile
|
||||
from .utils import ImageSpecFileDescriptor, ImageKitMeta, BoundImageKitMeta
|
||||
from .files import ProcessedImageFieldFile
|
||||
from .utils import ImageSpecFileDescriptor, ImageKitMeta
|
||||
from ...base import ImageSpec
|
||||
from ...utils import suggest_extension
|
||||
|
||||
|
||||
|
|
@ -19,53 +16,31 @@ class ImageSpecField(object):
|
|||
|
||||
"""
|
||||
def __init__(self, processors=None, format=None, options=None,
|
||||
image_field=None, pre_cache=None, storage=None, autoconvert=True,
|
||||
image_field=None, storage=None, autoconvert=True,
|
||||
image_cache_backend=None, image_cache_strategy=None):
|
||||
"""
|
||||
:param processors: A list of processors to run on the original image.
|
||||
:param format: The format of the output file. If not provided,
|
||||
ImageSpecField will try to guess the appropriate format based on the
|
||||
extension of the filename and the format of the input image.
|
||||
:param options: A dictionary that will be passed to PIL's
|
||||
``Image.save()`` method as keyword arguments. Valid options vary
|
||||
between formats, but some examples include ``quality``,
|
||||
``optimize``, and ``progressive`` for JPEGs. See the PIL
|
||||
documentation for others.
|
||||
:param image_field: The name of the model property that contains the
|
||||
original image.
|
||||
:param storage: A Django storage system to use to save the generated
|
||||
image.
|
||||
:param autoconvert: Specifies whether automatic conversion using
|
||||
``prepare_image()`` should be performed prior to saving.
|
||||
:param image_cache_backend: An object responsible for managing the state
|
||||
of cached files. Defaults to an instance of
|
||||
``IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND``
|
||||
:param image_cache_strategy: A dictionary containing callbacks that
|
||||
allow you to customize how and when the image cache is validated.
|
||||
Defaults to ``IMAGEKIT_DEFAULT_SPEC_FIELD_IMAGE_CACHE_STRATEGY``
|
||||
|
||||
"""
|
||||
|
||||
if pre_cache is not None:
|
||||
raise Exception('The pre_cache argument has been removed in favor'
|
||||
' of cache state backends.')
|
||||
|
||||
# The generator accepts a callable value for processors, but it
|
||||
# The spec accepts a callable value for processors, but it
|
||||
# takes different arguments than the callable that ImageSpecField
|
||||
# expects, so we create a partial application and pass that instead.
|
||||
# TODO: Should we change the signatures to match? Even if `instance` is not part of the signature, it's accessible through the source file object's instance property.
|
||||
p = lambda file: processors(instance=file.instance, file=file) if \
|
||||
callable(processors) else processors
|
||||
|
||||
self.generator = SpecFileGenerator(p, format=format, options=options,
|
||||
autoconvert=autoconvert, storage=storage)
|
||||
self.spec = ImageSpec(
|
||||
processors=p,
|
||||
format=format,
|
||||
options=options,
|
||||
storage=storage,
|
||||
autoconvert=autoconvert,
|
||||
image_cache_backend=image_cache_backend,
|
||||
image_cache_strategy=image_cache_strategy,
|
||||
)
|
||||
|
||||
self.image_field = image_field
|
||||
self.storage = storage
|
||||
self.image_cache_backend = image_cache_backend or \
|
||||
get_default_image_cache_backend()
|
||||
if image_cache_strategy is None:
|
||||
image_cache_strategy = settings.IMAGEKIT_DEFAULT_SPEC_FIELD_IMAGE_CACHE_STRATEGY
|
||||
self.image_cache_strategy = StrategyWrapper(image_cache_strategy)
|
||||
|
||||
@property
|
||||
def storage(self):
|
||||
return self.spec.storage
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
setattr(cls, name, ImageSpecFileDescriptor(self, name))
|
||||
|
|
@ -94,7 +69,7 @@ class ImageSpecField(object):
|
|||
|
||||
# Register the field with the image_cache_backend
|
||||
try:
|
||||
self.image_cache_backend.register_field(cls, self, name)
|
||||
self.spec.image_cache_backend.register_field(cls, self, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
|
@ -106,9 +81,9 @@ class ImageSpecField(object):
|
|||
for attname in instance._ik.spec_fields:
|
||||
file = getattr(instance, attname)
|
||||
if created:
|
||||
file.field.image_cache_strategy.invoke_callback('source_create', file)
|
||||
file.field.spec.image_cache_strategy.invoke_callback('source_create', file)
|
||||
elif old_hashes[attname] != new_hashes[attname]:
|
||||
file.field.image_cache_strategy.invoke_callback('source_change', file)
|
||||
file.field.spec.image_cache_strategy.invoke_callback('source_change', file)
|
||||
|
||||
@staticmethod
|
||||
def _update_source_hashes(instance):
|
||||
|
|
@ -125,7 +100,7 @@ class ImageSpecField(object):
|
|||
@staticmethod
|
||||
def _post_delete_receiver(sender, instance=None, **kwargs):
|
||||
for spec_file in instance._ik.spec_files:
|
||||
spec_file.field.image_cache_strategy.invoke_callback('source_delete', spec_file)
|
||||
spec_file.field.spec.image_cache_strategy.invoke_callback('source_delete', spec_file)
|
||||
|
||||
@staticmethod
|
||||
def _post_init_receiver(sender, instance, **kwargs):
|
||||
|
|
@ -152,20 +127,16 @@ class ProcessedImageField(models.ImageField):
|
|||
:class:`imagekit.models.ImageSpecField`.
|
||||
|
||||
"""
|
||||
if 'quality' in kwargs:
|
||||
raise Exception('The "quality" keyword argument has been'
|
||||
""" deprecated. Use `options={'quality': %s}` instead.""" \
|
||||
% kwargs['quality'])
|
||||
models.ImageField.__init__(self, verbose_name, name, width_field,
|
||||
height_field, **kwargs)
|
||||
self.generator = SpecFileGenerator(processors, format=format,
|
||||
options=options, autoconvert=autoconvert)
|
||||
self.spec = ImageSpec(processors, format=format, options=options,
|
||||
autoconvert=autoconvert)
|
||||
|
||||
def get_filename(self, filename):
|
||||
filename = os.path.normpath(self.storage.get_valid_name(
|
||||
os.path.basename(filename)))
|
||||
name, ext = os.path.splitext(filename)
|
||||
ext = suggest_extension(filename, self.generator.format)
|
||||
ext = suggest_extension(filename, self.spec.format)
|
||||
return u'%s%s' % (name, ext)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ class ImageSpecFieldFile(ImageFieldFile):
|
|||
self.attname = attname
|
||||
|
||||
def get_hash(self):
|
||||
return self.field.generator.get_hash(self.source_file)
|
||||
return self.field.spec.get_hash(self.source_file)
|
||||
|
||||
@property
|
||||
def source_file(self):
|
||||
|
|
@ -34,16 +34,16 @@ class ImageSpecFieldFile(ImageFieldFile):
|
|||
def _require_file(self):
|
||||
if not self.source_file:
|
||||
raise ValueError("The '%s' attribute's image_field has no file associated with it." % self.attname)
|
||||
self.field.image_cache_strategy.invoke_callback('access', self)
|
||||
self.field.spec.image_cache_strategy.invoke_callback('access', self)
|
||||
|
||||
def clear(self):
|
||||
return self.field.image_cache_backend.clear(self)
|
||||
return self.field.spec.image_cache_backend.clear(self)
|
||||
|
||||
def invalidate(self):
|
||||
return self.field.image_cache_backend.invalidate(self)
|
||||
return self.field.spec.image_cache_backend.invalidate(self)
|
||||
|
||||
def validate(self):
|
||||
return self.field.image_cache_backend.validate(self)
|
||||
return self.field.spec.image_cache_backend.validate(self)
|
||||
|
||||
def generate(self, save=True):
|
||||
"""
|
||||
|
|
@ -51,7 +51,7 @@ class ImageSpecFieldFile(ImageFieldFile):
|
|||
the content of the result, ready for saving.
|
||||
|
||||
"""
|
||||
return self.field.generator.generate_file(self.name, self.source_file,
|
||||
return self.field.spec.generate_file(self.name, self.source_file,
|
||||
save)
|
||||
|
||||
def delete(self, save=False):
|
||||
|
|
@ -91,7 +91,7 @@ class ImageSpecFieldFile(ImageFieldFile):
|
|||
Specifies the filename that the cached image will use.
|
||||
|
||||
"""
|
||||
return self.field.generator.generate_filename(self.source_file)
|
||||
return self.field.spec.generate_filename(self.source_file)
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
|
|
@ -123,7 +123,7 @@ class ImageSpecFieldFile(ImageFieldFile):
|
|||
|
||||
class ProcessedImageFieldFile(ImageFieldFile):
|
||||
def save(self, name, content, save=True):
|
||||
new_filename = self.field.generate_filename(self.instance, name)
|
||||
img, content = self.field.generator.process_content(content,
|
||||
new_filename = self.field.spec.generate_filename(self.instance, name)
|
||||
img, content = self.field.spec.process_content(content,
|
||||
new_filename, self)
|
||||
return super(ProcessedImageFieldFile, self).save(name, content, save)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from django.utils.encoding import smart_str, smart_unicode
|
|||
from django.utils.functional import wraps
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
from .exceptions import UnknownExtensionError, UnknownFormatError
|
||||
from .lib import Image, ImageFile, StringIO
|
||||
|
||||
|
||||
|
|
@ -76,14 +77,6 @@ def _wrap_copy(f):
|
|||
return copy
|
||||
|
||||
|
||||
class UnknownExtensionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownFormatError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
_pil_init = 0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import os
|
|||
from django.test import TestCase
|
||||
|
||||
from imagekit import utils
|
||||
from imagekit.exceptions import UnknownFormatError
|
||||
from .models import (Photo, AbstractImageModel, ConcreteImageModel1,
|
||||
ConcreteImageModel2)
|
||||
from .testutils import create_photo, pickleback
|
||||
|
|
@ -59,7 +60,7 @@ class IKUtilsTest(TestCase):
|
|||
self.assertEqual(utils.format_to_extension('PNG'), '.png')
|
||||
self.assertEqual(utils.format_to_extension('ICO'), '.ico')
|
||||
|
||||
with self.assertRaises(utils.UnknownFormatError):
|
||||
with self.assertRaises(UnknownFormatError):
|
||||
utils.format_to_extension('TXT')
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue