From 322c96eec22c23a884b4dee392c4f26ce2ff3142 Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Sat, 28 Apr 2012 11:32:22 -0700 Subject: [PATCH 01/25] Merge branch 'release/2.0.1' into develop * release/2.0.1: Bumping the version number. Changelog update. --- docs/changelog.rst | 6 ++++++ docs/conf.py | 4 ++-- imagekit/__init__.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9257870..34d3ef7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,12 @@ Changelog ========= +v2.0.1 +------ + +- Fixed a file descriptor leak in the `utils.quiet()` context manager. + + v2.0.0 ------ diff --git a/docs/conf.py b/docs/conf.py index 1ba36ca..580401c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2011, Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & # built documents. # # The short X.Y version. -version = '2.0.0' +version = '2.0.1' # The full version, including alpha/beta/rc tags. -release = '2.0.0' +release = '2.0.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/imagekit/__init__.py b/imagekit/__init__.py index d525af2..55a5d04 100644 --- a/imagekit/__init__.py +++ b/imagekit/__init__.py @@ -1,6 +1,6 @@ __title__ = 'django-imagekit' __author__ = 'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett, Matthew Tretter, Eric Eldredge' -__version__ = (2, 0, 0, 'final', 0) +__version__ = (2, 0, 1, 'final', 0) __license__ = 'BSD' From 784afcc95d0712ff05407089e63112e22e3ad65e Mon Sep 17 00:00:00 2001 From: "German M. Bravo" Date: Fri, 4 May 2012 17:24:04 -0500 Subject: [PATCH 02/25] Simplified path join for the cache --- imagekit/models/fields/files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imagekit/models/fields/files.py b/imagekit/models/fields/files.py index 153c69f..e206377 100644 --- a/imagekit/models/fields/files.py +++ b/imagekit/models/fields/files.py @@ -100,7 +100,7 @@ class ImageSpecFieldFile(ImageFieldFile): filepath, basename = os.path.split(path) filename = os.path.splitext(basename)[0] new_name = '%s_%s%s' % (filename, specname, extension) - return os.path.join(os.path.join('cache', filepath), new_name) + return os.path.join('cache', filepath, new_name) @property def name(self): From 89f2aa7a7d8175f9b6144673d2f3a636bd9d9ad3 Mon Sep 17 00:00:00 2001 From: "German M. Bravo" Date: Fri, 4 May 2012 17:30:16 -0500 Subject: [PATCH 03/25] Reflections on images using RGBA --- imagekit/processors/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imagekit/processors/base.py b/imagekit/processors/base.py index 06239e2..8320a3d 100644 --- a/imagekit/processors/base.py +++ b/imagekit/processors/base.py @@ -77,11 +77,11 @@ class Reflection(object): # Convert bgcolor string to RGB value. background_color = ImageColor.getrgb(self.background_color) # Handle palleted images. - img = img.convert('RGB') + img = img.convert('RGBA') # Copy orignial image and flip the orientation. reflection = img.copy().transpose(Image.FLIP_TOP_BOTTOM) # Create a new image filled with the bgcolor the same size. - background = Image.new("RGB", img.size, background_color) + background = Image.new("RGBA", img.size, background_color) # Calculate our alpha mask. start = int(255 - (255 * self.opacity)) # The start of our gradient. steps = int(255 * self.size) # The number of intermedite values. @@ -101,7 +101,7 @@ class Reflection(object): reflection = reflection.crop((0, 0, img.size[0], reflection_height)) # Create new image sized to hold both the original image and # the reflection. - composite = Image.new("RGB", (img.size[0], img.size[1] + reflection_height), background_color) + composite = Image.new("RGBA", (img.size[0], img.size[1] + reflection_height), background_color) # Paste the orignal image and the reflection into the composite image. composite.paste(img, (0, 0)) composite.paste(reflection, (0, img.size[1])) From c1b4c9bf719f185c259b47216fcc98f0097c04f8 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sat, 12 May 2012 15:45:08 -0400 Subject: [PATCH 04/25] Use cStringIO if available --- imagekit/generators.py | 3 +-- imagekit/lib.py | 5 +++++ imagekit/utils.py | 3 +-- tests/core/tests.py | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/imagekit/generators.py b/imagekit/generators.py index 50d04ac..046f0c0 100644 --- a/imagekit/generators.py +++ b/imagekit/generators.py @@ -1,6 +1,5 @@ import os -from StringIO import StringIO - +from .lib import StringIO from .processors import ProcessorPipeline from .utils import (img_to_fobj, open_image, IKContentFile, extension_to_format, UnknownExtensionError) diff --git a/imagekit/lib.py b/imagekit/lib.py index efacb79..574e587 100644 --- a/imagekit/lib.py +++ b/imagekit/lib.py @@ -15,3 +15,8 @@ except ImportError: import ImageStat except ImportError: raise ImportError('ImageKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.') + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO diff --git a/imagekit/utils.py b/imagekit/utils.py index 4e680fe..4ffb556 100644 --- a/imagekit/utils.py +++ b/imagekit/utils.py @@ -1,6 +1,5 @@ import os import mimetypes -from StringIO import StringIO import sys import types @@ -9,7 +8,7 @@ from django.db.models.loading import cache from django.utils.functional import wraps from django.utils.encoding import smart_str, smart_unicode -from .lib import Image, ImageFile +from .lib import Image, ImageFile, StringIO RGBA_TRANSPARENCY_FORMATS = ['PNG'] diff --git a/tests/core/tests.py b/tests/core/tests.py index d3039e4..07b504a 100644 --- a/tests/core/tests.py +++ b/tests/core/tests.py @@ -2,11 +2,11 @@ from __future__ import with_statement import os import pickle -from StringIO import StringIO from django.test import TestCase from imagekit import utils +from imagekit.lib import StringIO from .models import (Photo, AbstractImageModel, ConcreteImageModel1, ConcreteImageModel2) from .testutils import generate_lenna, create_photo From 82c7d5e475d30b80c065c74b157766c21d9d7d51 Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Tue, 29 May 2012 12:55:51 -0700 Subject: [PATCH 05/25] Adding Django 1.4 to tox. --- tox.ini | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tox.ini b/tox.ini index 8f2eadc..b1ab30b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,9 @@ [tox] envlist = + py27-django14, py27-django13, py27-django12, + py26-django14, py26-django13, py26-django12 @@ -10,6 +12,11 @@ changedir = tests setenv = PYTHONPATH = {toxinidir}/tests commands = django-admin.py test core --settings=settings +[testenv:py27-django14] +deps = + Django>=1.4 + Pillow + [testenv:py27-django13] deps = Django>=1.3,<=1.4 @@ -20,6 +27,11 @@ deps = Django>=1.2,<=1.3 Pillow +[testenv:py26-django14] +deps = + Django>=1.4 + Pillow + [testenv:py26-django13] deps = Django>=1.3,<=1.4 From 35343eaa9d741231b7a942b24f02990c5775a9dc Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Tue, 29 May 2012 12:57:06 -0700 Subject: [PATCH 06/25] Adding basepython to the tox directives. --- tox.ini | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tox.ini b/tox.ini index b1ab30b..72bf206 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,7 @@ [tox] envlist = - py27-django14, - py27-django13, - py27-django12, - py26-django14, - py26-django13, - py26-django12 + py27-django14, py27-django13, py27-django12, + py26-django14, py26-django13, py26-django12 [testenv] changedir = tests @@ -13,31 +9,37 @@ setenv = PYTHONPATH = {toxinidir}/tests commands = django-admin.py test core --settings=settings [testenv:py27-django14] +basepython = python2.7 deps = Django>=1.4 Pillow [testenv:py27-django13] +basepython = python2.7 deps = Django>=1.3,<=1.4 Pillow [testenv:py27-django12] +basepython = python2.7 deps = Django>=1.2,<=1.3 Pillow [testenv:py26-django14] +basepython = python2.6 deps = Django>=1.4 Pillow [testenv:py26-django13] +basepython = python2.6 deps = Django>=1.3,<=1.4 Pillow [testenv:py26-django12] +basepython = python2.6 deps = Django>=1.2,<=1.3 Pillow From 6009bd418ab445e815474aeb9c772eb02c04792d Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Tue, 29 May 2012 12:58:28 -0700 Subject: [PATCH 07/25] Adding a travis-ci configuration file. --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ec17c2d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: python +python: + - 2.7 +install: pip install tox --use-mirrors +script: tox -e py26-1.2.X,py27-1.2.X,py26-1.3.X,py27-1.3.X,py26,py27,docs +notifications: + irc: "irc.freenode.org#imagekit" From f2f6766b8629a6bd6d42197b50dc712a539a93f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timothe=CC=81e=20Peignier?= Date: Tue, 29 May 2012 22:05:37 +0200 Subject: [PATCH 08/25] add irc channel to docs and README --- README.rst | 6 ++++++ docs/index.rst | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/README.rst b/README.rst index e1730b6..3bb6e43 100644 --- a/README.rst +++ b/README.rst @@ -164,6 +164,12 @@ Or in your ``settings.py`` file if you want to use it as the default:: IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND = 'path.to.MyImageCacheBackend' +Community +--------- + +The official Freenode channel for ImageKit is `#imagekit `_. +You should always find some fine people to answer your questions +about ImageKit there. Contributing ------------ diff --git a/docs/index.rst b/docs/index.rst index 5634f21..2c06385 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,6 +18,14 @@ Authors .. include:: ../AUTHORS +Community +--------- + +The official Freenode channel for ImageKit is `#imagekit `_. +You should always find some fine people to answer your questions +about ImageKit there. + + Digging Deeper -------------- From 5ae961733cd7beee5588bf5c74b6e64ed8a8fac7 Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Tue, 29 May 2012 13:14:52 -0700 Subject: [PATCH 09/25] Derp, forgot to change the tox command. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ec17c2d..5a5e2e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,6 @@ language: python python: - 2.7 install: pip install tox --use-mirrors -script: tox -e py26-1.2.X,py27-1.2.X,py26-1.3.X,py27-1.3.X,py26,py27,docs +script: tox -e py27-django13,py27-django12,py26-django13,py27-django12 notifications: irc: "irc.freenode.org#imagekit" From 5e1757c1ee3456a0911323bc0811c16a0bc64f35 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Wed, 18 Jul 2012 17:21:21 -0400 Subject: [PATCH 10/25] Remove unused stuff --- tests/core/tests.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/core/tests.py b/tests/core/tests.py index 07b504a..eb42ba8 100644 --- a/tests/core/tests.py +++ b/tests/core/tests.py @@ -9,15 +9,10 @@ from imagekit import utils from imagekit.lib import StringIO from .models import (Photo, AbstractImageModel, ConcreteImageModel1, ConcreteImageModel2) -from .testutils import generate_lenna, create_photo +from .testutils import create_photo class IKTest(TestCase): - def generate_image(self): - tmp = tempfile.TemporaryFile() - Image.new('RGB', (800, 600)).save(tmp, 'JPEG') - tmp.seek(0) - return tmp def setUp(self): self.photo = create_photo('test.jpg') @@ -27,7 +22,6 @@ class IKTest(TestCase): """ filename = self.photo.thumbnail.file.name - thumbnail_timestamp = os.path.getmtime(filename) self.photo.save() self.assertTrue(self.photo.thumbnail.storage.exists(filename)) From 118f6e42064bd328c9ebf5834ae64341878ea4ec Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Wed, 18 Jul 2012 17:21:47 -0400 Subject: [PATCH 11/25] Create failing test to illustrate #97 --- tests/core/tests.py | 20 ++++++++++---------- tests/core/testutils.py | 10 +++++++++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/core/tests.py b/tests/core/tests.py index eb42ba8..b4884c8 100644 --- a/tests/core/tests.py +++ b/tests/core/tests.py @@ -1,15 +1,13 @@ from __future__ import with_statement import os -import pickle from django.test import TestCase from imagekit import utils -from imagekit.lib import StringIO from .models import (Photo, AbstractImageModel, ConcreteImageModel1, ConcreteImageModel2) -from .testutils import create_photo +from .testutils import create_photo, pickleback class IKTest(TestCase): @@ -66,15 +64,17 @@ class IKUtilsTest(TestCase): class PickleTest(TestCase): - def test_source_file(self): - ph = create_photo('pickletest.jpg') - pickled_model = StringIO() - pickle.dump(ph, pickled_model) - pickled_model.seek(0) - unpickled_model = pickle.load(pickled_model) + def test_model(self): + ph = pickleback(create_photo('pickletest.jpg')) # This isn't supposed to error. - unpickled_model.thumbnail.source_file + ph.thumbnail.source_file + + def test_field(self): + thumbnail = pickleback(create_photo('pickletest2.jpg').thumbnail) + + # This isn't supposed to error. + thumbnail.source_file class InheritanceTest(TestCase): diff --git a/tests/core/testutils.py b/tests/core/testutils.py index 27e0f52..4acc13c 100644 --- a/tests/core/testutils.py +++ b/tests/core/testutils.py @@ -3,8 +3,9 @@ import tempfile from django.core.files.base import ContentFile -from imagekit.lib import Image +from imagekit.lib import Image, StringIO from .models import Photo +import pickle def generate_lenna(): @@ -36,3 +37,10 @@ def create_instance(model_class, image_name): def create_photo(name): return create_instance(Photo, name) + + +def pickleback(obj): + pickled = StringIO() + pickle.dump(obj, pickled) + pickled.seek(0) + return pickle.load(pickled) From becee54c03d8f3b6553103b39a100574c28c8a81 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Wed, 18 Jul 2012 22:24:39 -0400 Subject: [PATCH 12/25] Fix pickling of ImageSpecFieldFile Code now passes the test added in 118f6e4. Hopefully this will address #97. --- imagekit/models/fields/files.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/imagekit/models/fields/files.py b/imagekit/models/fields/files.py index e206377..d6f3dc2 100644 --- a/imagekit/models/fields/files.py +++ b/imagekit/models/fields/files.py @@ -11,7 +11,6 @@ class ImageSpecFieldFile(ImageFieldFile): def __init__(self, instance, field, attname): super(ImageSpecFieldFile, self).__init__(instance, field, None) self.attname = attname - self.storage = self.field.storage or self.source_file.storage @property def source_file(self): @@ -145,6 +144,25 @@ class ImageSpecFieldFile(ImageFieldFile): # it at least that one time. pass + @property + def storage(self): + return getattr(self, '_storage', None) or self.field.storage or self.source_file.storage + + @storage.setter + def storage(self, storage): + self._storage = storage + + def __getstate__(self): + return dict( + attname=self.attname, + instance=self.instance, + ) + + def __setstate__(self, state): + self.attname = state['attname'] + self.instance = state['instance'] + self.field = getattr(self.instance.__class__, self.attname) + class ProcessedImageFieldFile(ImageFieldFile): def save(self, name, content, save=True): From c24746ea1a6bae802dd9a6eb193ba5572a157e6f Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Thu, 19 Jul 2012 15:29:50 -0700 Subject: [PATCH 13/25] Code blocks. --- README.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.rst b/README.rst index 3bb6e43..65ec0d6 100644 --- a/README.rst +++ b/README.rst @@ -34,6 +34,8 @@ Adding Specs to a Model Much like ``django.db.models.ImageField``, Specs are defined as properties of a model class:: +.. code-block:: python + from django.db import models from imagekit.models import ImageSpecField @@ -46,6 +48,8 @@ Accessing the spec through a model instance will create the image and return an ImageFile-like object (just like with a normal ``django.db.models.ImageField``):: +.. code-block:: python + photo = Photo.objects.all()[0] photo.original_image.url # > '/media/photos/birthday.tiff' photo.formatted_image.url # > '/media/cache/photos/birthday_formatted_image.jpeg' @@ -55,6 +59,8 @@ Check out ``imagekit.models.ImageSpecField`` for more information. If you only want to save the processed image (without maintaining the original), you can use a ``ProcessedImageField``:: +.. code-block:: python + from django.db import models from imagekit.models.fields import ProcessedImageField @@ -71,6 +77,8 @@ The real power of ImageKit comes from processors. Processors take an image, do something to it, and return the result. By providing a list of processors to your spec, you can expose different versions of the original image:: +.. code-block:: python + from django.db import models from imagekit.models import ImageSpecField from imagekit.processors import ResizeToFill, Adjust @@ -83,6 +91,8 @@ your spec, you can expose different versions of the original image:: The ``thumbnail`` property will now return a cropped image:: +.. code-block:: python + photo = Photo.objects.all()[0] photo.thumbnail.url # > '/media/cache/photos/birthday_thumbnail.jpeg' photo.thumbnail.width # > 50 @@ -98,6 +108,8 @@ image manipulations, like resizing, rotating, and color adjustments. However, if they aren't up to the task, you can create your own. All you have to do is implement a ``process()`` method:: +.. code-block:: python + class Watermark(object): def process(self, image): # Code for adding the watermark goes here. @@ -117,6 +129,8 @@ for displaying specs (or even regular ImageFields) in the `Django admin change list`_. AdminThumbnail is used as a property on Django admin classes:: +.. code-block:: python + from django.contrib import admin from imagekit.admin import AdminThumbnail from .models import Photo @@ -156,12 +170,16 @@ could store the state (valid, invalid) of the cache in a database to avoid filesystem access. You can then specify your image cache backend on a per-field basis:: +.. code-block:: python + class Photo(models.Model): ... thumbnail = ImageSpecField(..., image_cache_backend=MyImageCacheBackend()) Or in your ``settings.py`` file if you want to use it as the default:: +.. code-block:: python + IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND = 'path.to.MyImageCacheBackend' Community From 70ab4a0cc0e52364c235aff29bdfea0dd1a17897 Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Thu, 19 Jul 2012 15:31:08 -0700 Subject: [PATCH 14/25] Whoops! Messed that up. --- README.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 65ec0d6..a584eba 100644 --- a/README.rst +++ b/README.rst @@ -32,7 +32,7 @@ Adding Specs to a Model ----------------------- Much like ``django.db.models.ImageField``, Specs are defined as properties -of a model class:: +of a model class: .. code-block:: python @@ -46,7 +46,7 @@ of a model class:: Accessing the spec through a model instance will create the image and return an ImageFile-like object (just like with a normal -``django.db.models.ImageField``):: +``django.db.models.ImageField``): .. code-block:: python @@ -57,7 +57,7 @@ an ImageFile-like object (just like with a normal Check out ``imagekit.models.ImageSpecField`` for more information. If you only want to save the processed image (without maintaining the original), -you can use a ``ProcessedImageField``:: +you can use a ``ProcessedImageField``: .. code-block:: python @@ -75,7 +75,7 @@ Processors The real power of ImageKit comes from processors. Processors take an image, do something to it, and return the result. By providing a list of processors to -your spec, you can expose different versions of the original image:: +your spec, you can expose different versions of the original image: .. code-block:: python @@ -89,7 +89,7 @@ your spec, you can expose different versions of the original image:: ResizeToFill(50, 50)], image_field='original_image', format='JPEG', options={'quality': 90}) -The ``thumbnail`` property will now return a cropped image:: +The ``thumbnail`` property will now return a cropped image: .. code-block:: python @@ -106,7 +106,7 @@ pass processors to a ``ProcessedImageField`` instead of an ``ImageSpecField``.) The ``imagekit.processors`` module contains processors for many common image manipulations, like resizing, rotating, and color adjustments. However, if they aren't up to the task, you can create your own. All you have to do is -implement a ``process()`` method:: +implement a ``process()`` method: .. code-block:: python @@ -127,7 +127,7 @@ Admin ImageKit also contains a class named ``imagekit.admin.AdminThumbnail`` for displaying specs (or even regular ImageFields) in the `Django admin change list`_. AdminThumbnail is used as a property on -Django admin classes:: +Django admin classes: .. code-block:: python @@ -168,7 +168,7 @@ no longer needed in any form, i.e. the model is deleted). Each of these methods must accept a file object, but the internals are up to you. For example, you could store the state (valid, invalid) of the cache in a database to avoid filesystem access. You can then specify your image cache backend on a per-field -basis:: +basis: .. code-block:: python @@ -176,7 +176,7 @@ basis:: ... thumbnail = ImageSpecField(..., image_cache_backend=MyImageCacheBackend()) -Or in your ``settings.py`` file if you want to use it as the default:: +Or in your ``settings.py`` file if you want to use it as the default: .. code-block:: python From 548fb65618dfce8aa43671f79231628a773a8f88 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Thu, 19 Jul 2012 20:01:40 -0400 Subject: [PATCH 15/25] Allow callables for AdminThumbnail image_field arg This allows images from related models to be displayed. Closes #138. --- imagekit/admin.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/imagekit/admin.py b/imagekit/admin.py index f764d3e..4466e6e 100644 --- a/imagekit/admin.py +++ b/imagekit/admin.py @@ -21,11 +21,14 @@ class AdminThumbnail(object): self.template = template def __call__(self, obj): - try: - thumbnail = getattr(obj, self.image_field) - except AttributeError: - raise Exception('The property %s is not defined on %s.' % \ - (self.image_field, obj.__class__.__name__)) + if callable(self.image_field): + thumbnail = self.image_field(obj) + else: + try: + thumbnail = getattr(obj, self.image_field) + except AttributeError: + raise Exception('The property %s is not defined on %s.' % \ + (self.image_field, obj.__class__.__name__)) original_image = getattr(thumbnail, 'source_file', None) or thumbnail template = self.template or 'imagekit/admin/thumbnail.html' From a196e00059cde3a306dbe13e79c2de38306e75fe Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Thu, 19 Jul 2012 21:03:15 -0400 Subject: [PATCH 16/25] Use django-appconf --- imagekit/conf.py | 5 +++++ imagekit/imagecache/__init__.py | 3 ++- imagekit/models/__init__.py | 1 + imagekit/settings.py | 5 ----- setup.py | 3 +++ 5 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 imagekit/conf.py delete mode 100644 imagekit/settings.py diff --git a/imagekit/conf.py b/imagekit/conf.py new file mode 100644 index 0000000..51ddf55 --- /dev/null +++ b/imagekit/conf.py @@ -0,0 +1,5 @@ +from appconf import AppConf + + +class ImageKitConf(AppConf): + DEFAULT_IMAGE_CACHE_BACKEND = 'imagekit.imagecache.PessimisticImageCacheBackend' diff --git a/imagekit/imagecache/__init__.py b/imagekit/imagecache/__init__.py index 3c582c8..cf98a9d 100644 --- a/imagekit/imagecache/__init__.py +++ b/imagekit/imagecache/__init__.py @@ -14,7 +14,8 @@ def get_default_image_cache_backend(): """ global _default_image_cache_backend if not _default_image_cache_backend: - from ..settings import DEFAULT_IMAGE_CACHE_BACKEND as import_path + from django.conf import settings + import_path = settings.IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND try: dot = import_path.rindex('.') except ValueError: diff --git a/imagekit/models/__init__.py b/imagekit/models/__init__.py index c5ef221..4207987 100644 --- a/imagekit/models/__init__.py +++ b/imagekit/models/__init__.py @@ -1,3 +1,4 @@ +from .. import conf from .fields import ImageSpecField, ProcessedImageField import warnings diff --git a/imagekit/settings.py b/imagekit/settings.py deleted file mode 100644 index d84030d..0000000 --- a/imagekit/settings.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.conf import settings - -DEFAULT_IMAGE_CACHE_BACKEND = getattr(settings, - 'IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND', - 'imagekit.imagecache.PessimisticImageCacheBackend') diff --git a/setup.py b/setup.py index 6fdb8fb..6f662f9 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,9 @@ setup( packages=find_packages(), zip_safe=False, include_package_data=True, + install_requires=[ + 'django-appconf>=0.5', + ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', From f3008f68b88b6543e27d968faf1a934a857ac3d4 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Mon, 23 Jul 2012 10:22:23 -0300 Subject: [PATCH 17/25] Add Google Group to README --- README.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index a584eba..d22739a 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,3 @@ - ImageKit is a Django app that helps you to add variations of uploaded images to your models. These variations are called "specs" and can include things like different sizes (e.g. thumbnails) and black and white versions. @@ -182,12 +181,15 @@ Or in your ``settings.py`` file if you want to use it as the default: IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND = 'path.to.MyImageCacheBackend' + Community --------- -The official Freenode channel for ImageKit is `#imagekit `_. -You should always find some fine people to answer your questions -about ImageKit there. +Please use `the GitHub issue tracker `_ +to report bugs with django-imagekit. `A mailing list `_ +also exists to discuss the project and ask questions, as well as the official +`#imagekit `_ channel on Freenode. + Contributing ------------ From 23c6e9a70ed8c40f6c7c02ca0e7624a735208411 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Wed, 25 Jul 2012 22:19:42 -0400 Subject: [PATCH 18/25] Add __init__ to Reflection processor; closes #141 --- imagekit/processors/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/imagekit/processors/base.py b/imagekit/processors/base.py index 8320a3d..61c0e30 100644 --- a/imagekit/processors/base.py +++ b/imagekit/processors/base.py @@ -69,9 +69,10 @@ class Reflection(object): Creates an image with a reflection. """ - background_color = '#FFFFFF' - size = 0.0 - opacity = 0.6 + def __init__(self, background_color='#FFFFFF', size=0.0, opacity=0.6): + self.background_color = background_color + self.size = size + self.opacity = opacity def process(self, img): # Convert bgcolor string to RGB value. From c8733c4707591fc0c29aef0164c5688f31f17075 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Wed, 25 Jul 2012 23:04:21 -0400 Subject: [PATCH 19/25] Change how signals are used Signals are now connected without specifying the class and non-IK models are filtered out in the receivers. This is necessary because of a bug with how Django handles abstract models. Closes #126 --- imagekit/models/fields/__init__.py | 44 +++------------------------ imagekit/models/receivers.py | 48 ++++++++++++++++++++++++++++++ imagekit/utils.py | 8 +++++ 3 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 imagekit/models/receivers.py diff --git a/imagekit/models/fields/__init__.py b/imagekit/models/fields/__init__.py index b2cceda..3e11e69 100644 --- a/imagekit/models/fields/__init__.py +++ b/imagekit/models/fields/__init__.py @@ -1,15 +1,18 @@ import os from django.db import models -from django.db.models.signals import post_init, post_save, post_delete from ...imagecache import get_default_image_cache_backend from ...generators import SpecFileGenerator from .files import ImageSpecFieldFile, ProcessedImageFieldFile +from ..receivers import configure_receivers from .utils import ImageSpecFileDescriptor, ImageKitMeta, BoundImageKitMeta from ...utils import suggest_extension +configure_receivers() + + class ImageSpecField(object): """ The heart and soul of the ImageKit library, ImageSpecField allows you to add @@ -91,51 +94,12 @@ class ImageSpecField(object): setattr(cls, '_ik', ik) ik.spec_fields.append(name) - # Connect to the signals only once for this class. - uid = '%s.%s' % (cls.__module__, cls.__name__) - post_init.connect(ImageSpecField._post_init_receiver, sender=cls, - dispatch_uid=uid) - post_save.connect(ImageSpecField._post_save_receiver, sender=cls, - dispatch_uid=uid) - post_delete.connect(ImageSpecField._post_delete_receiver, sender=cls, - dispatch_uid=uid) - # Register the field with the image_cache_backend try: self.image_cache_backend.register_field(cls, self, name) except AttributeError: pass - @staticmethod - def _post_save_receiver(sender, instance=None, created=False, raw=False, **kwargs): - if not raw: - old_hashes = instance._ik._source_hashes.copy() - new_hashes = ImageSpecField._update_source_hashes(instance) - for attname in instance._ik.spec_fields: - if old_hashes[attname] != new_hashes[attname]: - getattr(instance, attname).invalidate() - - @staticmethod - def _update_source_hashes(instance): - """ - Stores hashes of the source image files so that they can be compared - later to see whether the source image has changed (and therefore whether - the spec file needs to be regenerated). - - """ - instance._ik._source_hashes = dict((f.attname, hash(f.source_file)) \ - for f in instance._ik.spec_files) - return instance._ik._source_hashes - - @staticmethod - def _post_delete_receiver(sender, instance=None, **kwargs): - for spec_file in instance._ik.spec_files: - spec_file.clear() - - @staticmethod - def _post_init_receiver(sender, instance, **kwargs): - ImageSpecField._update_source_hashes(instance) - class ProcessedImageField(models.ImageField): """ diff --git a/imagekit/models/receivers.py b/imagekit/models/receivers.py new file mode 100644 index 0000000..da93a69 --- /dev/null +++ b/imagekit/models/receivers.py @@ -0,0 +1,48 @@ +from django.db.models.signals import post_init, post_save, post_delete +from ..utils import ik_model_receiver + + +def update_source_hashes(instance): + """ + Stores hashes of the source image files so that they can be compared + later to see whether the source image has changed (and therefore whether + the spec file needs to be regenerated). + + """ + instance._ik._source_hashes = dict((f.attname, hash(f.source_file)) \ + for f in instance._ik.spec_files) + return instance._ik._source_hashes + + +@ik_model_receiver +def post_save_receiver(sender, instance=None, created=False, raw=False, **kwargs): + if not raw: + old_hashes = instance._ik._source_hashes.copy() + new_hashes = update_source_hashes(instance) + for attname in instance._ik.spec_fields: + if old_hashes[attname] != new_hashes[attname]: + getattr(instance, attname).invalidate() + + +@ik_model_receiver +def post_delete_receiver(sender, instance=None, **kwargs): + for spec_file in instance._ik.spec_files: + spec_file.clear() + + +@ik_model_receiver +def post_init_receiver(sender, instance, **kwargs): + update_source_hashes(instance) + + +def configure_receivers(): + # Connect the signals. We have to listen to every model (not just those + # with IK fields) and filter in our receivers because of a Django issue with + # abstract base models. + # Related: + # https://github.com/jdriscoll/django-imagekit/issues/126 + # https://code.djangoproject.com/ticket/9318 + uid = 'ik_spec_field_receivers' + post_init.connect(post_init_receiver, dispatch_uid=uid) + post_save.connect(post_save_receiver, dispatch_uid=uid) + post_delete.connect(post_delete_receiver, dispatch_uid=uid) diff --git a/imagekit/utils.py b/imagekit/utils.py index 4ffb556..d1d607e 100644 --- a/imagekit/utils.py +++ b/imagekit/utils.py @@ -371,3 +371,11 @@ def prepare_image(img, format): save_kwargs['optimize'] = True return img, save_kwargs + + +def ik_model_receiver(fn): + @wraps(fn) + def receiver(sender, **kwargs): + if getattr(sender, '_ik', None): + fn(sender, **kwargs) + return receiver From 9cc86d597ea0c9f6bcd25e7a7af26a50dd31735f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timothe=CC=81e=20Peignier?= Date: Mon, 13 Aug 2012 11:47:20 +0200 Subject: [PATCH 20/25] fix API documentation --- docs/apireference.rst | 2 +- docs/conf.py | 2 +- requirements.txt | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/apireference.rst b/docs/apireference.rst index df4b5c7..d4a2ed8 100644 --- a/docs/apireference.rst +++ b/docs/apireference.rst @@ -5,7 +5,7 @@ API Reference :mod:`models` Module -------------------- -.. automodule:: imagekit.models +.. automodule:: imagekit.models.fields :members: diff --git a/docs/conf.py b/docs/conf.py index 580401c..9305a5c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('..')) sys.path.append(os.path.abspath('_themes')) os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' diff --git a/requirements.txt b/requirements.txt index 6a3296e..5161716 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ -Django >= 1.3.1 +Django>=1.3.1 +django-appconf>=0.5 +PIL>=1.1.7 From e136957fc0406496ac1fea11b539a3bd3ce10ce6 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Tue, 28 Aug 2012 10:25:12 -0400 Subject: [PATCH 21/25] Fix docs typo; closes #147 --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d22739a..041d02d 100644 --- a/README.rst +++ b/README.rst @@ -64,7 +64,7 @@ you can use a ``ProcessedImageField``: from imagekit.models.fields import ProcessedImageField class Photo(models.Model): - processed_image = ImageSpecField(format='JPEG', options={'quality': 90}) + processed_image = ProcessedImageField(format='JPEG', options={'quality': 90}) See the class documentation for details. From 7c0511bd818a0684e18b43364b34344103ea8145 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Thu, 6 Sep 2012 22:40:20 -0400 Subject: [PATCH 22/25] Test correct versions of Django It looks like our versions ranges weren't correct. --- tox.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tox.ini b/tox.ini index 72bf206..1ad1957 100644 --- a/tox.ini +++ b/tox.ini @@ -11,35 +11,35 @@ commands = django-admin.py test core --settings=settings [testenv:py27-django14] basepython = python2.7 deps = - Django>=1.4 + Django>=1.4,<1.5 Pillow [testenv:py27-django13] basepython = python2.7 deps = - Django>=1.3,<=1.4 + Django>=1.3,<1.4 Pillow [testenv:py27-django12] basepython = python2.7 deps = - Django>=1.2,<=1.3 + Django>=1.2,<1.3 Pillow [testenv:py26-django14] basepython = python2.6 deps = - Django>=1.4 + Django>=1.4,<1.5 Pillow [testenv:py26-django13] basepython = python2.6 deps = - Django>=1.3,<=1.4 + Django>=1.3,<1.4 Pillow [testenv:py26-django12] basepython = python2.6 deps = - Django>=1.2,<=1.3 + Django>=1.2,<1.3 Pillow From eb0558e144b2acaec0b9cd698b8cb1156150df0d Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Thu, 6 Sep 2012 23:08:50 -0400 Subject: [PATCH 23/25] Change assertRaises for Python 2.6 compatibility --- tests/core/tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/core/tests.py b/tests/core/tests.py index b4884c8..a73915e 100644 --- a/tests/core/tests.py +++ b/tests/core/tests.py @@ -52,15 +52,15 @@ class IKUtilsTest(TestCase): self.assertEqual(utils.extension_to_format('.jpeg'), 'JPEG') self.assertEqual(utils.extension_to_format('.rgba'), 'SGI') - with self.assertRaises(utils.UnknownExtensionError): - utils.extension_to_format('.txt') + self.assertRaises(utils.UnknownExtensionError, + lambda: utils.extension_to_format('.txt')) def test_format_to_extension_no_init(self): self.assertEqual(utils.format_to_extension('PNG'), '.png') self.assertEqual(utils.format_to_extension('ICO'), '.ico') - with self.assertRaises(utils.UnknownFormatError): - utils.format_to_extension('TXT') + self.assertRaises(utils.UnknownFormatError, + lambda: utils.format_to_extension('TXT')) class PickleTest(TestCase): From 81db33e63aa1a30c37c2ab683fd1a6f6fb76061d Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Fri, 14 Sep 2012 11:14:46 -0700 Subject: [PATCH 24/25] Bumping the version number. --- docs/conf.py | 4 ++-- imagekit/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9305a5c..e0913b9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2011, Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & # built documents. # # The short X.Y version. -version = '2.0.1' +version = '2.0.2' # The full version, including alpha/beta/rc tags. -release = '2.0.1' +release = '2.0.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/imagekit/__init__.py b/imagekit/__init__.py index 55a5d04..9dd4ffe 100644 --- a/imagekit/__init__.py +++ b/imagekit/__init__.py @@ -1,6 +1,6 @@ __title__ = 'django-imagekit' __author__ = 'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett, Matthew Tretter, Eric Eldredge' -__version__ = (2, 0, 1, 'final', 0) +__version__ = (2, 0, 2, 'final', 0) __license__ = 'BSD' From 664b7d4cf427c57b4ca1cbcb4cebbacda4fe2a04 Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Fri, 14 Sep 2012 11:22:38 -0700 Subject: [PATCH 25/25] Changelog for 2.0.2. --- docs/changelog.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 34d3ef7..072d8e1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,17 @@ Changelog ========= +v2.0.2 +------ + +- Fixed the pickling of ImageSpecFieldFile. +- Signals are now connected without specifying the class and non-IK models + are filitered out in the receivers. This is necessary beacuse of a bug + with how Django handles abstract models. +- Fixed a `ZeroDivisionError` in the Reflection processor. +- `cStringIO` is now used if it's available. +- Reflections on images now use RGBA instead of RGB. + v2.0.1 ------