From 71c56c7d6affae7506b298ad7f7c3855ce0e93a9 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 20:11:56 -0500 Subject: [PATCH 01/13] Separate generator --- imagekit/models/fields.py | 204 ++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 94 deletions(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index f2f012b..673189b 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -15,13 +15,17 @@ from imagekit.processors import ProcessorPipeline, AutoConvert from ..imagecache import get_default_image_cache_backend -class _ImageSpecFieldMixin(object): +class SpecFileGenerator(object): def __init__(self, processors=None, format=None, options={}, - autoconvert=True): + autoconvert=True, cache_to=None, storage=None, + cache_state_backend=None): self.processors = processors self.format = format self.options = options self.autoconvert = autoconvert + self.cache_to = cache_to + self.storage = storage + self.cache_state_backend = cache_state_backend or get_default_cache_state_backend() def process(self, image, file, instance): processors = self.processors @@ -30,6 +34,91 @@ class _ImageSpecFieldMixin(object): processors = ProcessorPipeline(processors or []) return processors.process(image.copy()) + def generate_content(self, filename, content, model=None): + img = open_image(content) + original_format = img.format + img = self.process(img, self, model) + options = dict(self.options or {}) + + # Determine the format. + format = self.format + if not format: + if callable(self.cache_to): + # 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 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.generate_content(filename, fp, + getattr(source_file, 'instance', None)) + + if save: + storage = self.storage or source_file.storage + storage.save(filename, content) + + return content + + def invalidate(self, file): + return self.cache_state_backend.invalidate(file) + + def validate(self, file): + return self.cache_state_backend.validate(file) + + def clear(self, file): + return self.cache_state_backend.clear(file) + class BoundImageKitMeta(object): def __init__(self, instance, spec_fields): @@ -54,14 +143,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,14 +193,11 @@ 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) + self.generator = SpecFileGenerator(processors, format=format, + options=options, autoconvert=autoconvert, image_cache_backend=image_cache_backend) 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 \ - get_default_image_cache_backend() def contribute_to_class(self, cls, name): setattr(cls, name, _ImageSpecFieldDescriptor(self, name)) @@ -170,64 +254,13 @@ 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.generator = field.generator self.attname = attname self.storage = self.field.storage or self.source_file.storage @@ -264,13 +297,13 @@ class ImageSpecFieldFile(_ImageSpecFieldFileMixin, ImageFieldFile): file = property(_get_file, ImageFieldFile._set_file, ImageFieldFile._del_file) def clear(self): - return self.field.image_cache_backend.clear(self) + return self.generator.clear(self) def invalidate(self): - return self.field.image_cache_backend.invalidate(self) + return self.generator.invalidate(self) def validate(self): - return self.field.image_cache_backend.validate(self) + return self.generator.validate(self) def generate(self, save=True): """ @@ -278,22 +311,7 @@ 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.generator.generate_file(self.name, self.source_file, save) @property def url(self): @@ -331,10 +349,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 @@ -364,9 +378,11 @@ class ImageSpecFieldFile(_ImageSpecFieldFileMixin, ImageFieldFile): if not cache_to: raise Exception('No cache_to or default_cache_to value specified') if callable(cache_to): + suggested_extension = self.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, self._suggested_extension)))) + self.attname, suggested_extension)))) else: dir_name = os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(cache_to)))) filename = os.path.normpath(os.path.basename(filename)) @@ -417,14 +433,14 @@ 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.generate_content(new_filename, content) 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 +448,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 +464,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, + cache_to=kwargs.get('upload_to')) 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 = _get_suggested_extension(filename, self.format) + ext = self.generator.suggested_extension(filename, self.format) return '%s%s' % (name, ext) From 7d6036aaacec73138452fb0fe0af2df9c56aeaaf Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 20:20:11 -0500 Subject: [PATCH 02/13] Remove cache_to from generator --- imagekit/models/fields.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 673189b..547b98f 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -17,13 +17,12 @@ from ..imagecache import get_default_image_cache_backend class SpecFileGenerator(object): def __init__(self, processors=None, format=None, options={}, - autoconvert=True, cache_to=None, storage=None, + autoconvert=True, storage=None, cache_state_backend=None): self.processors = processors self.format = format self.options = options self.autoconvert = autoconvert - self.cache_to = cache_to self.storage = storage self.cache_state_backend = cache_state_backend or get_default_cache_state_backend() @@ -43,10 +42,9 @@ class SpecFileGenerator(object): # Determine the format. format = self.format if not format: - if callable(self.cache_to): - # 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 to guess the format from the extension. + extension = os.path.splitext(filename)[1].lower() + if extension: try: format = extension_to_format(extension) except UnknownExtensionError: @@ -467,8 +465,7 @@ class ProcessedImageField(models.ImageField): models.ImageField.__init__(self, verbose_name, name, width_field, height_field, **kwargs) self.generator = SpecFileGenerator(processors, format=format, - options=options, autoconvert=autoconvert, - cache_to=kwargs.get('upload_to')) + options=options, autoconvert=autoconvert) def get_filename(self, filename): filename = os.path.normpath(self.storage.get_valid_name(os.path.basename(filename))) From 146a5ee01c7e7ca8ff892953ceb7bf6fc11236f6 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 20:23:00 -0500 Subject: [PATCH 03/13] Access generator through field --- imagekit/models/fields.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 547b98f..65b013b 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -252,13 +252,9 @@ class ImageSpecField(object): ImageSpecField._update_source_hashes(instance) - - - class ImageSpecFieldFile(ImageFieldFile): def __init__(self, instance, field, attname): ImageFieldFile.__init__(self, instance, field, None) - self.generator = field.generator self.attname = attname self.storage = self.field.storage or self.source_file.storage @@ -295,13 +291,13 @@ class ImageSpecFieldFile(ImageFieldFile): file = property(_get_file, ImageFieldFile._set_file, ImageFieldFile._del_file) def clear(self): - return self.generator.clear(self) + return self.field.generator.clear(self) def invalidate(self): - return self.generator.invalidate(self) + return self.field.generator.invalidate(self) def validate(self): - return self.generator.validate(self) + return self.field.generator.validate(self) def generate(self, save=True): """ @@ -309,7 +305,7 @@ class ImageSpecFieldFile(ImageFieldFile): the content of the result, ready for saving. """ - return self.generator.generate_file(self.name, self.source_file, save) + return self.field.generator.generate_file(self.name, self.source_file, save) @property def url(self): @@ -376,7 +372,7 @@ class ImageSpecFieldFile(ImageFieldFile): if not cache_to: raise Exception('No cache_to or default_cache_to value specified') if callable(cache_to): - suggested_extension = self.generator.suggest_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, From 4ab5aadec616aa7c8054e46b7d4476f217b36956 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 20:38:17 -0500 Subject: [PATCH 04/13] Remove process method --- imagekit/models/fields.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 65b013b..e7fc4a7 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -17,8 +17,7 @@ from ..imagecache import get_default_image_cache_backend class SpecFileGenerator(object): def __init__(self, processors=None, format=None, options={}, - autoconvert=True, storage=None, - cache_state_backend=None): + autoconvert=True, storage=None, cache_state_backend=None): self.processors = processors self.format = format self.options = options @@ -26,17 +25,16 @@ class SpecFileGenerator(object): self.storage = storage self.cache_state_backend = cache_state_backend or get_default_cache_state_backend() - 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()) - def generate_content(self, filename, content, model=None): img = open_image(content) original_format = img.format - img = self.process(img, self, model) + + # Run the processors + processors = self.processors + if callable(processors): + processors = processors(instance=model, file=content) + img = ProcessorPipeline(processors or []).process(img) + options = dict(self.options or {}) # Determine the format. @@ -305,7 +303,8 @@ class ImageSpecFieldFile(ImageFieldFile): the content of the result, ready for saving. """ - return self.field.generator.generate_file(self.name, self.source_file, save) + return self.field.generator.generate_file(self.name, self.source_file, + save) @property def url(self): From a7cf79290cadab62633fe5cad66efb62e98f4cd3 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 22:51:20 -0500 Subject: [PATCH 05/13] Don't return content --- imagekit/models/fields.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index e7fc4a7..3639bd6 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -104,8 +104,6 @@ class SpecFileGenerator(object): storage = self.storage or source_file.storage storage.save(filename, content) - return content - def invalidate(self, file): return self.cache_state_backend.invalidate(file) From 1054d055fb4156707cbec8bda3460571d70069ca Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 23:00:20 -0500 Subject: [PATCH 06/13] Rename `generate_content` to `process_content` --- imagekit/models/fields.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 3639bd6..7e01254 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -25,7 +25,7 @@ class SpecFileGenerator(object): self.storage = storage self.cache_state_backend = cache_state_backend or get_default_cache_state_backend() - def generate_content(self, filename, content, model=None): + def process_content(self, filename, content, model=None): img = open_image(content) original_format = img.format @@ -97,7 +97,7 @@ class SpecFileGenerator(object): fp.seek(0) fp = StringIO(fp.read()) - img, content = self.generate_content(filename, fp, + img, content = self.process_content(filename, fp, getattr(source_file, 'instance', None)) if save: @@ -427,7 +427,7 @@ def _post_delete_handler(sender, instance=None, **kwargs): class ProcessedImageFieldFile(ImageFieldFile): def save(self, name, content, save=True): new_filename = self.field.generate_filename(self.instance, name) - img, content = self.field.generator.generate_content(new_filename, content) + img, content = self.field.generator.process_content(new_filename, content) return super(ProcessedImageFieldFile, self).save(name, content, save) From 2a422f5cb6c19ad189a3b2efc7270d9b428012b0 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 23:22:06 -0500 Subject: [PATCH 07/13] Decouple generator from model --- imagekit/models/fields.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 7e01254..517be62 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -25,14 +25,14 @@ class SpecFileGenerator(object): self.storage = storage self.cache_state_backend = cache_state_backend or get_default_cache_state_backend() - def process_content(self, filename, content, model=None): + 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(instance=model, file=content) + processors = processors(source_file) img = ProcessorPipeline(processors or []).process(img) options = dict(self.options or {}) @@ -90,6 +90,7 @@ class SpecFileGenerator(object): """ 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: @@ -97,8 +98,7 @@ class SpecFileGenerator(object): fp.seek(0) fp = StringIO(fp.read()) - img, content = self.process_content(filename, fp, - getattr(source_file, 'instance', None)) + img, content = self.process_content(fp, filename, source_file) if save: storage = self.storage or source_file.storage @@ -187,8 +187,16 @@ class ImageSpecField(object): raise Exception('The pre_cache argument has been removed in favor' ' of cache state backends.') - self.generator = SpecFileGenerator(processors, format=format, - options=options, autoconvert=autoconvert, image_cache_backend=image_cache_backend) + # 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, + cache_state_backend=cache_state_backend) self.image_field = image_field self.storage = storage self.cache_to = cache_to @@ -427,7 +435,8 @@ def _post_delete_handler(sender, instance=None, **kwargs): 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(new_filename, content) + img, content = self.field.generator.process_content(content, + new_filename, self) return super(ProcessedImageFieldFile, self).save(name, content, save) From b24fe7b8e0dda064e73b090a2265a13556a1d309 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 23:27:25 -0500 Subject: [PATCH 08/13] Cleanup --- imagekit/models/fields.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 517be62..2a6a241 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -8,11 +8,11 @@ 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, \ +from ..imagecache import get_default_image_cache_backend +from ..processors import ProcessorPipeline, AutoConvert +from ..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 SpecFileGenerator(object): @@ -375,15 +375,21 @@ class ImageSpecFieldFile(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): - suggested_extension = self.field.generator.suggest_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)))) + 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) @@ -470,7 +476,8 @@ class ProcessedImageField(models.ImageField): 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 = self.generator.suggested_extension(filename, self.format) return '%s%s' % (name, ext) From a668b28257997e0c492105f7f9aac06d67753766 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 23:32:25 -0500 Subject: [PATCH 09/13] Fix ProcessedImageField bug Was using an old method signature --- imagekit/models/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 2a6a241..34ec80c 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -479,7 +479,7 @@ class ProcessedImageField(models.ImageField): filename = os.path.normpath(self.storage.get_valid_name( os.path.basename(filename))) name, ext = os.path.splitext(filename) - ext = self.generator.suggested_extension(filename, self.format) + ext = self.generator.suggest_extension(filename) return '%s%s' % (name, ext) From ff1b76f9236244cfaa27735f13f0bd0c92596c24 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 23:39:16 -0500 Subject: [PATCH 10/13] Remove image cache backend from generator --- imagekit/models/fields.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 34ec80c..2575e9f 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -17,13 +17,12 @@ from ..utils import img_to_fobj, open_image, \ class SpecFileGenerator(object): def __init__(self, processors=None, format=None, options={}, - autoconvert=True, storage=None, cache_state_backend=None): + autoconvert=True, storage=None): self.processors = processors self.format = format self.options = options self.autoconvert = autoconvert self.storage = storage - self.cache_state_backend = cache_state_backend or get_default_cache_state_backend() def process_content(self, content, filename=None, source_file=None): img = open_image(content) @@ -104,15 +103,6 @@ class SpecFileGenerator(object): storage = self.storage or source_file.storage storage.save(filename, content) - def invalidate(self, file): - return self.cache_state_backend.invalidate(file) - - def validate(self, file): - return self.cache_state_backend.validate(file) - - def clear(self, file): - return self.cache_state_backend.clear(file) - class BoundImageKitMeta(object): def __init__(self, instance, spec_fields): @@ -195,11 +185,12 @@ class ImageSpecField(object): callable(processors) else processors self.generator = SpecFileGenerator(p, format=format, options=options, - autoconvert=autoconvert, - cache_state_backend=cache_state_backend) + autoconvert=autoconvert) self.image_field = image_field self.storage = storage self.cache_to = cache_to + self.image_cache_backend = image_cache_backend or \ + get_default_image_cache_backend() def contribute_to_class(self, cls, name): setattr(cls, name, _ImageSpecFieldDescriptor(self, name)) @@ -295,13 +286,13 @@ class ImageSpecFieldFile(ImageFieldFile): file = property(_get_file, ImageFieldFile._set_file, ImageFieldFile._del_file) def clear(self): - return self.field.generator.clear(self) + return self.field.image_cache_backend.clear(self) def invalidate(self): - return self.field.generator.invalidate(self) + return self.field.image_cache_backend.invalidate(self) def validate(self): - return self.field.generator.validate(self) + return self.field.image_cache_backend.validate(self) def generate(self, save=True): """ From d9abd75d8a3c1692a8a933ad9db15fce62c238f4 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 23:41:07 -0500 Subject: [PATCH 11/13] Pass storage to generator --- imagekit/models/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 2575e9f..47842b0 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -185,7 +185,7 @@ class ImageSpecField(object): callable(processors) else processors self.generator = SpecFileGenerator(p, format=format, options=options, - autoconvert=autoconvert) + autoconvert=autoconvert, storage=storage) self.image_field = image_field self.storage = storage self.cache_to = cache_to From e31080ff4a1eb3d587a1d026c247dd7bc6d34f99 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 11 Feb 2012 23:48:54 -0500 Subject: [PATCH 12/13] Moved SpecFileGenerator to new module --- imagekit/generators.py | 98 +++++++++++++++++++++++++++++++++++++++ imagekit/models/fields.py | 96 +------------------------------------- 2 files changed, 99 insertions(+), 95 deletions(-) create mode 100644 imagekit/generators.py diff --git a/imagekit/generators.py b/imagekit/generators.py new file mode 100644 index 0000000..2ad1a25 --- /dev/null +++ b/imagekit/generators.py @@ -0,0 +1,98 @@ +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) diff --git a/imagekit/models/fields.py b/imagekit/models/fields.py index 47842b0..b7f3862 100644 --- a/imagekit/models/fields.py +++ b/imagekit/models/fields.py @@ -1,107 +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 ..imagecache import get_default_image_cache_backend -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) +from ..generators import SpecFileGenerator class BoundImageKitMeta(object): From e71432d8edd9481aaaee69667429e0025432cb32 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sun, 12 Feb 2012 00:01:03 -0500 Subject: [PATCH 13/13] Undo "return content" removal I changed my mind. For now, at least. --- imagekit/generators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imagekit/generators.py b/imagekit/generators.py index 2ad1a25..d064d80 100644 --- a/imagekit/generators.py +++ b/imagekit/generators.py @@ -96,3 +96,5 @@ class SpecFileGenerator(object): if save: storage = self.storage or source_file.storage storage.save(filename, content) + + return content