Merge branch 'refactor_generation' into develop

This commit is contained in:
Matthew Tretter 2012-02-12 17:27:07 -05:00
commit 88cc692568
2 changed files with 136 additions and 117 deletions

100
imagekit/generators.py Normal file
View file

@ -0,0 +1,100 @@
import os
from StringIO import StringIO
from django.core.files.base import ContentFile
from .processors import ProcessorPipeline, AutoConvert
from .utils import img_to_fobj, open_image, \
format_to_extension, extension_to_format, UnknownFormatError, \
UnknownExtensionError
class SpecFileGenerator(object):
def __init__(self, processors=None, format=None, options={},
autoconvert=True, storage=None):
self.processors = processors
self.format = format
self.options = options
self.autoconvert = autoconvert
self.storage = storage
def process_content(self, content, filename=None, source_file=None):
img = open_image(content)
original_format = img.format
# Run the processors
processors = self.processors
if callable(processors):
processors = processors(source_file)
img = ProcessorPipeline(processors or []).process(img)
options = dict(self.options or {})
# Determine the format.
format = self.format
if not format:
# Try to guess the format from the extension.
extension = os.path.splitext(filename)[1].lower()
if extension:
try:
format = extension_to_format(extension)
except UnknownExtensionError:
pass
format = format or img.format or original_format or 'JPEG'
# Run the AutoConvert processor
if self.autoconvert:
autoconvert_processor = AutoConvert(format)
img = autoconvert_processor.process(img)
options = dict(autoconvert_processor.save_kwargs.items() + \
options.items())
imgfile = img_to_fobj(img, format, **options)
content = ContentFile(imgfile.read())
return img, content
def suggest_extension(self, name):
original_extension = os.path.splitext(name)[1]
try:
suggested_extension = format_to_extension(self.format)
except UnknownFormatError:
extension = original_extension
else:
if suggested_extension.lower() == original_extension.lower():
extension = original_extension
else:
try:
original_format = extension_to_format(original_extension)
except UnknownExtensionError:
extension = suggested_extension
else:
# If the formats match, give precedence to the original extension.
if self.format.lower() == original_format.lower():
extension = original_extension
else:
extension = suggested_extension
return extension
def generate_file(self, filename, source_file, save=True):
"""
Generates a new image file by processing the source file and returns
the content of the result, ready for saving.
"""
if source_file: # TODO: Should we error here or something if the source_file doesn't exist?
# Process the original image file.
try:
fp = source_file.storage.open(source_file.name)
except IOError:
return
fp.seek(0)
fp = StringIO(fp.read())
img, content = self.process_content(fp, filename, source_file)
if save:
storage = self.storage or source_file.storage
storage.save(filename, content)
return content

View file

@ -1,34 +1,13 @@
import os
import datetime
from StringIO import StringIO
from django.core.files.base import ContentFile
from django.db import models
from django.db.models.fields.files import ImageFieldFile
from django.db.models.signals import post_init, post_save, post_delete
from django.utils.encoding import force_unicode, smart_str
from imagekit.utils import img_to_fobj, open_image, \
format_to_extension, extension_to_format, UnknownFormatError, \
UnknownExtensionError
from imagekit.processors import ProcessorPipeline, AutoConvert
from ..imagecache import get_default_image_cache_backend
class _ImageSpecFieldMixin(object):
def __init__(self, processors=None, format=None, options={},
autoconvert=True):
self.processors = processors
self.format = format
self.options = options
self.autoconvert = autoconvert
def process(self, image, file, instance):
processors = self.processors
if callable(processors):
processors = processors(instance=instance, file=file)
processors = ProcessorPipeline(processors or [])
return processors.process(image.copy())
from ..generators import SpecFileGenerator
class BoundImageKitMeta(object):
@ -54,14 +33,12 @@ class ImageKitMeta(object):
return ik
class ImageSpecField(_ImageSpecFieldMixin):
class ImageSpecField(object):
"""
The heart and soul of the ImageKit library, ImageSpecField allows you to add
variants of uploaded images to your models.
"""
_upload_to_attr = 'cache_to'
def __init__(self, processors=None, format=None, options={},
image_field=None, pre_cache=None, storage=None, cache_to=None,
autoconvert=True, image_cache_backend=None):
@ -106,10 +83,16 @@ class ImageSpecField(_ImageSpecFieldMixin):
raise Exception('The pre_cache argument has been removed in favor'
' of cache state backends.')
_ImageSpecFieldMixin.__init__(self, processors, format=format,
options=options, autoconvert=autoconvert)
# The generator 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.image_field = image_field
self.pre_cache = pre_cache
self.storage = storage
self.cache_to = cache_to
self.image_cache_backend = image_cache_backend or \
@ -170,62 +153,7 @@ class ImageSpecField(_ImageSpecFieldMixin):
ImageSpecField._update_source_hashes(instance)
def _get_suggested_extension(name, format):
original_extension = os.path.splitext(name)[1]
try:
suggested_extension = format_to_extension(format)
except UnknownFormatError:
extension = original_extension
else:
if suggested_extension.lower() == original_extension.lower():
extension = original_extension
else:
try:
original_format = extension_to_format(original_extension)
except UnknownExtensionError:
extension = suggested_extension
else:
# If the formats match, give precedence to the original extension.
if format.lower() == original_format.lower():
extension = original_extension
else:
extension = suggested_extension
return extension
class _ImageSpecFieldFileMixin(object):
def _process_content(self, filename, content):
img = open_image(content)
original_format = img.format
img = self.field.process(img, self, self.instance)
options = dict(self.field.options or {})
# Determine the format.
format = self.field.format
if not format:
if callable(getattr(self.field, self.field._upload_to_attr)):
# The extension is explicit, so assume they want the matching format.
extension = os.path.splitext(filename)[1].lower()
# Try to guess the format from the extension.
try:
format = extension_to_format(extension)
except UnknownExtensionError:
pass
format = format or img.format or original_format or 'JPEG'
# Run the AutoConvert processor
if getattr(self.field, 'autoconvert', True):
autoconvert_processor = AutoConvert(format)
img = autoconvert_processor.process(img)
options = dict(autoconvert_processor.save_kwargs.items() + \
options.items())
imgfile = img_to_fobj(img, format, **options)
content = ContentFile(imgfile.read())
return img, content
class ImageSpecFieldFile(_ImageSpecFieldFileMixin, ImageFieldFile):
class ImageSpecFieldFile(ImageFieldFile):
def __init__(self, instance, field, attname):
ImageFieldFile.__init__(self, instance, field, None)
self.attname = attname
@ -278,22 +206,8 @@ class ImageSpecFieldFile(_ImageSpecFieldFileMixin, ImageFieldFile):
the content of the result, ready for saving.
"""
source_file = self.source_file
if source_file: # TODO: Should we error here or something if the source_file doesn't exist?
# Process the original image file.
try:
fp = source_file.storage.open(source_file.name)
except IOError:
return
fp.seek(0)
fp = StringIO(fp.read())
img, content = self._process_content(self.name, fp)
if save:
self.storage.save(self.name, content)
return content
return self.field.generator.generate_file(self.name, self.source_file,
save)
@property
def url(self):
@ -331,10 +245,6 @@ class ImageSpecFieldFile(_ImageSpecFieldFileMixin, ImageFieldFile):
if save:
self.instance.save()
@property
def _suggested_extension(self):
return _get_suggested_extension(self.source_file.name, self.field.format)
def _default_cache_to(self, instance, path, specname, extension):
"""
Determines the filename to use for the transformed image. Can be
@ -362,13 +272,21 @@ class ImageSpecFieldFile(_ImageSpecFieldFileMixin, ImageFieldFile):
cache_to = self.field.cache_to or self._default_cache_to
if not cache_to:
raise Exception('No cache_to or default_cache_to value specified')
raise Exception('No cache_to or default_cache_to value'
' specified')
if callable(cache_to):
new_filename = force_unicode(datetime.datetime.now().strftime( \
smart_str(cache_to(self.instance, self.source_file.name,
self.attname, self._suggested_extension))))
suggested_extension = \
self.field.generator.suggest_extension(
self.source_file.name)
new_filename = force_unicode(
datetime.datetime.now().strftime(
smart_str(cache_to(self.instance,
self.source_file.name, self.attname,
suggested_extension))))
else:
dir_name = os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(cache_to))))
dir_name = os.path.normpath(
force_unicode(datetime.datetime.now().strftime(
smart_str(cache_to))))
filename = os.path.normpath(os.path.basename(filename))
new_filename = os.path.join(dir_name, filename)
@ -417,14 +335,15 @@ def _post_delete_handler(sender, instance=None, **kwargs):
spec_file.delete(save=False)
class ProcessedImageFieldFile(ImageFieldFile, _ImageSpecFieldFileMixin):
class ProcessedImageFieldFile(ImageFieldFile):
def save(self, name, content, save=True):
new_filename = self.field.generate_filename(self.instance, name)
img, content = self._process_content(new_filename, content)
img, content = self.field.generator.process_content(content,
new_filename, self)
return super(ProcessedImageFieldFile, self).save(name, content, save)
class ProcessedImageField(models.ImageField, _ImageSpecFieldMixin):
class ProcessedImageField(models.ImageField):
"""
ProcessedImageField is an ImageField that runs processors on the uploaded
image *before* saving it to storage. This is in contrast to specs, which
@ -432,7 +351,6 @@ class ProcessedImageField(models.ImageField, _ImageSpecFieldMixin):
within a reasonable size.
"""
_upload_to_attr = 'upload_to'
attr_class = ProcessedImageFieldFile
def __init__(self, processors=None, format=None, options={},
@ -449,15 +367,16 @@ class ProcessedImageField(models.ImageField, _ImageSpecFieldMixin):
raise Exception('The "quality" keyword argument has been'
""" deprecated. Use `options={'quality': %s}` instead.""" \
% kwargs['quality'])
_ImageSpecFieldMixin.__init__(self, processors, format=format,
options=options, autoconvert=autoconvert)
models.ImageField.__init__(self, verbose_name, name, width_field,
height_field, **kwargs)
self.generator = SpecFileGenerator(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)))
filename = os.path.normpath(self.storage.get_valid_name(
os.path.basename(filename)))
name, ext = os.path.splitext(filename)
ext = _get_suggested_extension(filename, self.format)
ext = self.generator.suggest_extension(filename)
return '%s%s' % (name, ext)