mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-04-17 19:51:11 +00:00
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:
commit
31eab7666a
8 changed files with 51 additions and 158 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -21,5 +21,4 @@ Indices and tables
|
|||
configuration
|
||||
advanced_usage
|
||||
caching
|
||||
changelog
|
||||
upgrading
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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__']
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue