mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-05-05 20:04:42 +00:00
Merge branch 'refactor_generation' into develop
This commit is contained in:
commit
88cc692568
2 changed files with 136 additions and 117 deletions
100
imagekit/generators.py
Normal file
100
imagekit/generators.py
Normal 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
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue