mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-03-31 03:50:34 +00:00
Merge branch 'release/3.2.3'
* release/3.2.3: Bump the version to 3.2.3. Exclude tests from dist Revert "Remove test dir __init__.py" Only include fetched fields in initial hash of sources Add test to illustrate GH-295 Test that there isn't IO done when you get a URL Support should_verify_existence on strategies Test that Optimistic strategy doesn't cause reads Ignore my Python3 virtualenv Remove test dir __init__.py Fixed minor spelling error in README.rst
This commit is contained in:
commit
bc0b0a8a75
12 changed files with 104 additions and 13 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -10,4 +10,5 @@ dist
|
|||
/tests/media/*
|
||||
!/tests/media/lenna.png
|
||||
/venv
|
||||
/venv3
|
||||
/.env
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ include LICENSE
|
|||
include README.rst
|
||||
recursive-include docs *
|
||||
recursive-include imagekit/templates *
|
||||
prune tests
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ particularly when the processing being done depends on user input.
|
|||
format = 'JPEG'
|
||||
options = {'quality': 60}
|
||||
|
||||
It's probaby not surprising that this class is capable of processing an image
|
||||
It's probably not surprising that this class is capable of processing an image
|
||||
in the exact same way as our ImageSpecField above. However, unlike with the
|
||||
image spec model field, this class doesn't define what source the spec is acting
|
||||
on, or what should be done with the result; that's up to you:
|
||||
|
|
|
|||
|
|
@ -128,7 +128,14 @@ class ImageCacheFile(BaseIKFile, ImageFile):
|
|||
# Dispatch the existence_required signal before checking to see if the
|
||||
# file exists. This gives the strategy a chance to create the file.
|
||||
existence_required.send(sender=self, file=self)
|
||||
return self.cachefile_backend.exists(self)
|
||||
|
||||
try:
|
||||
check = self.cachefile_strategy.should_verify_existence(self)
|
||||
except AttributeError:
|
||||
# All synchronous backends should have created the file as part of
|
||||
# `existence_required` if they wanted to.
|
||||
check = getattr(self.cachefile_backend, 'is_async', False)
|
||||
return self.cachefile_backend.exists(self) if check else True
|
||||
|
||||
def __getstate__(self):
|
||||
state = copy(self.__dict__)
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ class BaseAsync(Simple):
|
|||
"""
|
||||
Base class for cache file backends that generate files asynchronously.
|
||||
"""
|
||||
is_async = True
|
||||
|
||||
def generate(self, file, force=False):
|
||||
# Schedule the file for generation, unless we know for sure we don't
|
||||
# need to. If an already-generated file sneaks through, that's okay;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ class Optimistic(object):
|
|||
def on_source_saved(self, file):
|
||||
file.generate()
|
||||
|
||||
def should_verify_existence(self, file):
|
||||
return False
|
||||
|
||||
|
||||
class DictStrategy(object):
|
||||
def __init__(self, callbacks):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
__title__ = 'django-imagekit'
|
||||
__author__ = 'Matthew Tretter, Eric Eldredge, Bryan Veloso, Greg Newman, Chris Drackett, Justin Driscoll'
|
||||
__version__ = '3.2.2'
|
||||
__version__ = '3.2.3'
|
||||
__license__ = 'BSD'
|
||||
__all__ = ['__title__', '__author__', '__version__', '__license__']
|
||||
|
|
|
|||
|
|
@ -72,18 +72,19 @@ class ModelSignalRouter(object):
|
|||
|
||||
"""
|
||||
self.init_instance(instance)
|
||||
instance._ik['source_hashes'] = dict((attname, hash(file_field))
|
||||
for attname, file_field in self.get_field_dict(instance).items())
|
||||
instance._ik['source_hashes'] = dict(
|
||||
(attname, hash(getattr(instance, attname)))
|
||||
for attname in self.get_source_fields(instance))
|
||||
return instance._ik['source_hashes']
|
||||
|
||||
def get_field_dict(self, instance):
|
||||
def get_source_fields(self, instance):
|
||||
"""
|
||||
Returns the source fields for the given instance, in a dictionary whose
|
||||
keys are the field names and values are the fields themselves.
|
||||
Returns a list of the source fields for the given instance.
|
||||
|
||||
"""
|
||||
return dict((src.image_field, getattr(instance, src.image_field)) for
|
||||
src in self._source_groups if isinstance(instance, src.model_class))
|
||||
return set(src.image_field
|
||||
for src in self._source_groups
|
||||
if isinstance(instance, src.model_class))
|
||||
|
||||
@ik_model_receiver
|
||||
def post_save_receiver(self, sender, instance=None, created=False, raw=False, **kwargs):
|
||||
|
|
@ -91,14 +92,22 @@ class ModelSignalRouter(object):
|
|||
self.init_instance(instance)
|
||||
old_hashes = instance._ik.get('source_hashes', {}).copy()
|
||||
new_hashes = self.update_source_hashes(instance)
|
||||
for attname, file in self.get_field_dict(instance).items():
|
||||
if file and old_hashes[attname] != new_hashes[attname]:
|
||||
for attname in self.get_source_fields(instance):
|
||||
file = getattr(instance, attname)
|
||||
if file and old_hashes.get(attname) != new_hashes[attname]:
|
||||
self.dispatch_signal(source_saved, file, sender, instance,
|
||||
attname)
|
||||
|
||||
@ik_model_receiver
|
||||
def post_init_receiver(self, sender, instance=None, **kwargs):
|
||||
self.update_source_hashes(instance)
|
||||
self.init_instance(instance)
|
||||
source_fields = self.get_source_fields(instance)
|
||||
local_fields = dict((field.name, field)
|
||||
for field in instance._meta.local_fields
|
||||
if field.name in source_fields)
|
||||
instance._ik['source_hashes'] = dict(
|
||||
(attname, hash(file_field))
|
||||
for attname, file_field in local_fields.items())
|
||||
|
||||
def dispatch_signal(self, signal, file, model_class, instance, attname):
|
||||
"""
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -48,6 +48,7 @@ setup(
|
|||
'nose-progressive==1.5',
|
||||
'django-nose==1.2',
|
||||
'Pillow<3.0',
|
||||
'mock==1.0.1',
|
||||
],
|
||||
test_suite='testrunner.run_tests',
|
||||
install_requires=[
|
||||
|
|
|
|||
16
tests/test_no_extra_queries.py
Normal file
16
tests/test_no_extra_queries.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from nose.tools import assert_false
|
||||
from mock import Mock, PropertyMock, patch
|
||||
from .models import Photo
|
||||
|
||||
|
||||
def test_dont_access_source():
|
||||
"""
|
||||
Touching the source may trigger an unneeded query.
|
||||
See <https://github.com/matthewwithanm/django-imagekit/issues/295>
|
||||
|
||||
"""
|
||||
pmock = PropertyMock()
|
||||
pmock.__get__ = Mock()
|
||||
with patch.object(Photo, 'original_image', pmock):
|
||||
photo = Photo() # noqa
|
||||
assert_false(pmock.__get__.called)
|
||||
49
tests/test_optimistic_strategy.py
Normal file
49
tests/test_optimistic_strategy.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
from nose.tools import assert_true, assert_false
|
||||
from imagekit.cachefiles import ImageCacheFile
|
||||
from mock import Mock
|
||||
from .utils import create_image
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from imagekit.cachefiles.backends import Simple as SimpleCFBackend
|
||||
from imagekit.cachefiles.strategies import Optimistic as OptimisticStrategy
|
||||
|
||||
|
||||
class ImageGenerator(object):
|
||||
def generate(self):
|
||||
return create_image()
|
||||
|
||||
def get_hash(self):
|
||||
return 'abc123'
|
||||
|
||||
|
||||
def get_image_cache_file():
|
||||
storage = Mock(FileSystemStorage)
|
||||
backend = SimpleCFBackend()
|
||||
strategy = OptimisticStrategy()
|
||||
generator = ImageGenerator()
|
||||
return ImageCacheFile(generator, storage=storage,
|
||||
cachefile_backend=backend,
|
||||
cachefile_strategy=strategy)
|
||||
|
||||
|
||||
def test_no_io_on_bool():
|
||||
"""
|
||||
When checking the truthiness of an ImageCacheFile, the storage shouldn't
|
||||
peform IO operations.
|
||||
|
||||
"""
|
||||
file = get_image_cache_file()
|
||||
bool(file)
|
||||
assert_false(file.storage.exists.called)
|
||||
assert_false(file.storage.open.called)
|
||||
|
||||
|
||||
def test_no_io_on_url():
|
||||
"""
|
||||
When getting the URL of an ImageCacheFile, the storage shouldn't be
|
||||
checked.
|
||||
|
||||
"""
|
||||
file = get_image_cache_file()
|
||||
file.url
|
||||
assert_false(file.storage.exists.called)
|
||||
assert_false(file.storage.open.called)
|
||||
|
|
@ -77,5 +77,7 @@ class DummyAsyncCacheFileBackend(Simple):
|
|||
A cache file backend meant to simulate async generation.
|
||||
|
||||
"""
|
||||
is_async = True
|
||||
|
||||
def generate(self, file, force=False):
|
||||
pass
|
||||
|
|
|
|||
Loading…
Reference in a new issue