Merge branch 'release/3.0.2'

* release/3.0.2:
  Removing the changelog. Changelogs are hard.
  Bump to 3.0.2.
  Use == for comparison
  Don't mutate __dict__
  Don't require source in __setstate__; fixes #234
  Add (failing) test for #234
  Don't include cache in serialization of backend
  Fixed name of spec id attr
This commit is contained in:
Bryan Veloso 2013-07-17 11:46:07 -07:00
commit 31eab7666a
8 changed files with 51 additions and 158 deletions

View file

@ -9,10 +9,9 @@ you. If you need to programatically generate one image from another, you need
ImageKit.
**For the complete documentation on the latest stable version of ImageKit, see**
`ImageKit on RTD`_. Our `changelog is also available`_.
`ImageKit on RTD`_.
.. _`ImageKit on RTD`: http://django-imagekit.readthedocs.org
.. _`changelog is also available`: http://django-imagekit.readthedocs.org/en/latest/changelog.html
Installation

View file

@ -47,7 +47,7 @@ for creating an ``ImageSpec``, registering it, and associating it with an
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
spec_id='myapp:profile:avatar_thumbnail')
id='myapp:profile:avatar_thumbnail')
Obviously, the shorthand version is a lot, well…shorter. So why would you ever
want to go through the trouble of using the long form? The answer is that the
@ -97,7 +97,7 @@ for getting this information.
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
spec_id='myapp:profile:avatar_thumbnail')
id='myapp:profile:avatar_thumbnail')
thumbnail_width = models.PositiveIntegerField()
thumbnail_height = models.PositiveIntegerField()

View file

@ -1,142 +0,0 @@
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
------
- Fixed a file descriptor leak in the `utils.quiet()` context manager.
v2.0.0
------
- Added the concept of image cache backends. Image cache backends assume
control of validating and invalidating the cached images from `ImageSpec` in
versions past. The default backend maintins the current behavior: invalidating
an image deletes it, while validating checks whether the file exists and
creates the file if it doesn't. One can create custom image cache backends to
control how their images are cached (e.g., Celery, etc.).
ImageKit ships with three built-in backends:
- ``imagekit.imagecache.PessimisticImageCacheBackend`` - A very safe image
cache backend. Guarantees that files will always be available, but at the
cost of hitting the storage backend.
- ``imagekit.imagecache.NonValidatingImageCacheBackend`` - A backend that is
super optimistic about the existence of spec files. It will hit your file
storage much less frequently than the pessimistic backend, but it is
technically possible for a cache file to be missing after validation.
- ``imagekit.imagecache.celery.CeleryImageCacheBackend`` - A pessimistic cache
state backend that uses celery to generate its spec images. Like
``PessimisticCacheStateBackend``, this one checks to see if the file
exists on validation, so the storage is hit fairly frequently, but an
image is guaranteed to exist. However, while validation guarantees the
existence of *an* image, it does not necessarily guarantee that you will
get the correct image, as the spec may be pending regeneration. In other
words, while there are ``generate`` tasks in the queue, it is possible to
get a stale spec image. The tradeoff is that calling ``invalidate()``
won't block to interact with file storage.
- Some of the processors have been renamed and several new ones have been added:
- ``imagekit.processors.ResizeToFill`` - (previously
``imagekit.processors.resize.Crop``) Scales the image to fill the provided
dimensions and then trims away the excess.
- ``imagekit.processors.ResizeToFit`` - (previously
``imagekit.processors.resize.Fit``) Scale to fit the provided dimensions.
- ``imagekit.processors.SmartResize`` - Like ``ResizeToFill``, but crops using
entroy (``SmartCrop``) instead of an anchor argument.
- ``imagekit.processors.BasicCrop`` - Crop using provided box.
- ``imagekit.processors.SmartCrop`` - (previously
``imagekit.processors.resize.SmartCrop``) Crop to provided size, trimming
based on entropy.
- ``imagekit.processors.TrimBorderColor`` - Trim the specified color from the
specified sides.
- ``imagekit.processors.AddBorder`` - Add a border of specific color and
thickness to an image.
- ``imagekit.processors.Resize`` - Scale to the provided dimensions (can distort).
- ``imagekit.processors.ResizeToCover`` - Scale to the smallest size that will
cover the specified dimensions. Used internally by ``Fill`` and
``SmartFill``.
- ``imagekit.processors.ResizeCanvas`` - Takes an image an resizes the canvas,
using a specific background color if the new size is larger than the current
image.
- ``mat_color`` has been added as an arguemnt to ``ResizeToFit``. If set, the
the target image size will be enforced and the specified color will be
used as background color to pad the image.
- We now use `Tox`_ to automate testing.
.. _`Tox`: http://pypi.python.org/pypi/tox
v1.1.0
------
- A ``SmartCrop`` resize processor was added. This allows an image to be
cropped based on the amount of entropy in the target image's histogram.
- The ``quality`` argument was removed in favor of an ``options`` dictionary.
This is a more general solution which grants access to PIL's format-specific
options (including "quality", "progressive", and "optimize" for JPEGs).
- The ``TrimColor`` processor was renamed to ``TrimBorderColor``.
- The private ``_Resize`` class has been removed.
v1.0.3
------
- ``ImageSpec._create()`` was renamed ``ImageSpec.generate()`` and is now
available in the public API.
- Added an ``AutoConvert`` processor to encapsulate the transparency
handling logic.
- Refactored transparency handling to be smarter, handling a lot more of
the situations in which one would convert to or from formats that support
transparency.
- Fixed PIL zeroing out files when write mode is enabled.
v1.0.2
------
- Added this changelog.
- Enhanced extension detection, format detection, and conversion between the
two. This eliminates the reliance on an image being loaded into memory
beforehand in order to detect said image's extension.
- Fixed a regression from the 0.4.x series in which ImageKit was unable to
convert a PNG file in ``P`` or "palette" mode to JPEG.
v1.0.1
------
- Minor fixes related to the rendering of ``README.rst`` as a reStructured
text file.
- Fixed the included admin template not being found when ImageKit was and
the packaging of the included admin templates.
v1.0
----
- Initial release of the *new* field-based ImageKit API.

View file

@ -21,5 +21,4 @@ Indices and tables
configuration
advanced_usage
caching
changelog
upgrading

View file

@ -1,4 +1,5 @@
from ..utils import get_singleton, sanitize_cache_key
from copy import copy
from django.core.cache import get_cache
from django.core.exceptions import ImproperlyConfigured
@ -71,19 +72,26 @@ class CachedFileBackend(object):
def set_state(self, file, state):
key = self.get_key(file)
if state is CacheFileState.DOES_NOT_EXIST:
if state == CacheFileState.DOES_NOT_EXIST:
self.cache.set(key, state, self.existence_check_timeout)
else:
self.cache.set(key, state)
def __getstate__(self):
state = copy(self.__dict__)
# Don't include the cache when pickling. It'll be reconstituted based
# on the settings.
state.pop('_cache', None)
return state
def exists(self, file):
return self.get_state(file) is CacheFileState.EXISTS
return self.get_state(file) == CacheFileState.EXISTS
def generate(self, file, force=False):
raise NotImplementedError
def generate_now(self, file, force=False):
if force or self.get_state(file) is CacheFileState.DOES_NOT_EXIST:
if force or self.get_state(file) == CacheFileState.DOES_NOT_EXIST:
file._generate()
self.set_state(file, CacheFileState.EXISTS)

View file

@ -1,5 +1,5 @@
__title__ = 'django-imagekit'
__author__ = 'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett, Matthew Tretter, Eric Eldredge'
__version__ = '3.0.1'
__version__ = '3.0.2'
__license__ = 'BSD'
__all__ = ['__title__', '__author__', '__version__', '__license__']

View file

@ -1,3 +1,4 @@
from copy import copy
from django.conf import settings
from django.db.models.fields.files import ImageFieldFile
from ..cachefiles.backends import get_default_cachefile_backend
@ -93,25 +94,41 @@ class ImageSpec(BaseImageSpec):
fn = get_by_qname(settings.IMAGEKIT_SPEC_CACHEFILE_NAMER, 'namer')
return fn(self)
@property
def source(self):
src = getattr(self, '_source', None)
if not src:
field_data = getattr(self, '_field_data', None)
if field_data:
src = self._source = getattr(field_data['instance'], field_data['attname'])
del self._field_data
return src
@source.setter
def source(self, value):
self._source = value
def __getstate__(self):
state = self.__dict__
state = copy(self.__dict__)
# Unpickled ImageFieldFiles won't work (they're missing a storage
# object). Since they're such a common use case, we special case them.
# Unfortunately, this also requires us to add the source getter to
# lazily retrieve the source on the reconstructed object; simply trying
# to look up the source in ``__setstate__`` would require us to get the
# model instance but, if ``__setstate__`` was called as part of
# deserializing that model, the model wouldn't be fully reconstructed
# yet, preventing us from accessing the source field.
# (This is issue #234.)
if isinstance(self.source, ImageFieldFile):
field = getattr(self.source, 'field')
state['_field_data'] = {
'instance': getattr(self.source, 'instance', None),
'attname': getattr(field, 'name', None),
}
state.pop('_source', None)
return state
def __setstate__(self, state):
field_data = state.pop('_field_data', None)
self.__dict__ = state
if field_data:
self.source = getattr(field_data['instance'], field_data['attname'])
def get_hash(self):
return hashers.pickle([
self.source.name,

View file

@ -11,3 +11,15 @@ def test_imagespecfield():
instance = create_photo('pickletest2.jpg')
thumbnail = pickleback(instance.thumbnail)
thumbnail.generate()
def test_circular_ref():
"""
A model instance with a spec field in its dict shouldn't raise a KeyError.
This corresponds to #234
"""
instance = create_photo('pickletest3.jpg')
instance.thumbnail # Cause thumbnail to be added to instance's __dict__
pickleback(instance)