From af6ebcb469bf564be50a3f134eaa16e7be3e84b7 Mon Sep 17 00:00:00 2001 From: Sean Bell Date: Sat, 9 Feb 2013 00:55:49 -0500 Subject: [PATCH 1/6] Fixing iteration over objects for abstract models --- imagekit/specs/sourcegroups.py | 6 ++++-- imagekit/utils.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/imagekit/specs/sourcegroups.py b/imagekit/specs/sourcegroups.py index 1693470..b782595 100644 --- a/imagekit/specs/sourcegroups.py +++ b/imagekit/specs/sourcegroups.py @@ -15,6 +15,7 @@ from django.db.models.signals import post_init, post_save, post_delete from django.utils.functional import wraps from ..cachefiles import LazyImageCacheFile from ..signals import source_created, source_changed, source_deleted +from ..utils import get_nonabstract_descendants def ik_model_receiver(fn): @@ -131,8 +132,9 @@ class ImageFieldSourceGroup(object): particular model. """ - for instance in self.model_class.objects.all(): - yield getattr(instance, self.image_field) + for model in get_nonabstract_descendants(self.model_class): + for instance in model.objects.all().iterator(): + yield getattr(instance, self.image_field) class SourceGroupFilesGenerator(object): diff --git a/imagekit/utils.py b/imagekit/utils.py index 6f4fd52..d749ac6 100644 --- a/imagekit/utils.py +++ b/imagekit/utils.py @@ -23,6 +23,17 @@ def _get_models(apps): return models +def get_nonabstract_descendants(model): + """ Returns all non-abstract descendants of the model. """ + if model._meta.abstract: + descendants = [] + for m in model.__subclasses__(): + descendants += get_nonabstract_descendants(m) + return descendants + else: + return [model] + + def get_by_qname(path, desc): try: dot = path.rindex('.') From df41459e65d088210a038f5eccb130b625614bc3 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sun, 10 Feb 2013 15:39:33 -0500 Subject: [PATCH 2/6] Fix signals for abstract models Includes a fix for undispatched signals, as well as signals being handled twice. A regression of #126 Related: #185 --- imagekit/specs/sourcegroups.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/imagekit/specs/sourcegroups.py b/imagekit/specs/sourcegroups.py index b782595..59810bd 100644 --- a/imagekit/specs/sourcegroups.py +++ b/imagekit/specs/sourcegroups.py @@ -13,6 +13,7 @@ have two responsibilities: from django.db.models.signals import post_init, post_save, post_delete from django.utils.functional import wraps +import inspect from ..cachefiles import LazyImageCacheFile from ..signals import source_created, source_changed, source_deleted from ..utils import get_nonabstract_descendants @@ -26,8 +27,15 @@ def ik_model_receiver(fn): """ @wraps(fn) def receiver(self, sender, **kwargs): - if sender in (src.model_class for src in self._source_groups): - fn(self, sender=sender, **kwargs) + if not inspect.isclass(sender): + return + for src in self._source_groups: + if issubclass(sender, src.model_class): + fn(self, sender=sender, **kwargs) + + # If we find a match, return. We don't want to handle the signal + # more than once. + return return receiver @@ -77,7 +85,7 @@ class ModelSignalRouter(object): """ return dict((src.image_field, getattr(instance, src.image_field)) for - src in self._source_groups if src.model_class is instance.__class__) + 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): @@ -110,7 +118,7 @@ class ModelSignalRouter(object): """ for source_group in self._source_groups: - if source_group.model_class is model_class and source_group.image_field == attname: + if issubclass(model_class, source_group.model_class) and source_group.image_field == attname: signal.send(sender=source_group, source=file) From de550b71df79ff6883157dfd24e0b640dbd03507 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sun, 10 Feb 2013 15:41:24 -0500 Subject: [PATCH 3/6] Add test for abstract model signals --- tests/models.py | 28 +++++++++++++++++++++------- tests/test_abstract_models.py | 22 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 tests/test_abstract_models.py diff --git a/tests/models.py b/tests/models.py index 17e887b..61265f0 100644 --- a/tests/models.py +++ b/tests/models.py @@ -26,17 +26,31 @@ class ProcessedImageFieldModel(models.Model): options={'quality': 90}, upload_to='p') +class CountingCacheFileStrategy(object): + def __init__(self): + self.before_access_count = 0 + self.on_source_changed_count = 0 + self.on_source_created_count = 0 + + def before_access(self, file): + self.before_access_count += 1 + + def on_source_changed(self, file): + self.on_source_changed_count += 1 + + def on_source_created(self, file): + self.on_source_created_count += 1 + + class AbstractImageModel(models.Model): original_image = models.ImageField(upload_to='photos') - abstract_class_spec = ImageSpecField() + abstract_class_spec = ImageSpecField(source='original_image', + format='JPEG', + cachefile_strategy=CountingCacheFileStrategy()) class Meta: abstract = True -class ConcreteImageModel1(AbstractImageModel): - first_spec = ImageSpecField() - - -class ConcreteImageModel2(AbstractImageModel): - second_spec = ImageSpecField() +class ConcreteImageModel(AbstractImageModel): + pass diff --git a/tests/test_abstract_models.py b/tests/test_abstract_models.py new file mode 100644 index 0000000..c144136 --- /dev/null +++ b/tests/test_abstract_models.py @@ -0,0 +1,22 @@ +from django.core.files import File +from imagekit.signals import source_created +from imagekit.specs.sourcegroups import ImageFieldSourceGroup +from nose.tools import eq_ +from . models import AbstractImageModel, ConcreteImageModel +from .utils import get_image_file + + +def test_source_created_signal(): + source_group = ImageFieldSourceGroup(AbstractImageModel, 'original_image') + count = [0] + + def receiver(sender, *args, **kwargs): + if sender is source_group: + count[0] += 1 + + source_created.connect(receiver, dispatch_uid='test_source_created') + instance = ConcreteImageModel() + img = File(get_image_file()) + instance.original_image.save('test_source_created_signal.jpg', img) + + eq_(count[0], 1) From 7bf3e4e7a3f99b405c4f653b8a16ff0b18ebcb01 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sun, 10 Feb 2013 15:54:17 -0500 Subject: [PATCH 4/6] Add test for @seanbell's utility Currently failing --- tests/models.py | 4 ++++ tests/test_abstract_models.py | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/models.py b/tests/models.py index 61265f0..7520a5c 100644 --- a/tests/models.py +++ b/tests/models.py @@ -54,3 +54,7 @@ class AbstractImageModel(models.Model): class ConcreteImageModel(AbstractImageModel): pass + + +class ConcreteImageModelSubclass(ConcreteImageModel): + pass diff --git a/tests/test_abstract_models.py b/tests/test_abstract_models.py index c144136..a3ec774 100644 --- a/tests/test_abstract_models.py +++ b/tests/test_abstract_models.py @@ -1,8 +1,10 @@ from django.core.files import File from imagekit.signals import source_created from imagekit.specs.sourcegroups import ImageFieldSourceGroup +from imagekit.utils import get_nonabstract_descendants from nose.tools import eq_ -from . models import AbstractImageModel, ConcreteImageModel +from . models import (AbstractImageModel, ConcreteImageModel, + ConcreteImageModelSubclass) from .utils import get_image_file @@ -20,3 +22,8 @@ def test_source_created_signal(): instance.original_image.save('test_source_created_signal.jpg', img) eq_(count[0], 1) + + +def test_nonabstract_descendants_generator(): + descendants = list(get_nonabstract_descendants(AbstractImageModel)) + eq_(descendants, [ConcreteImageModel, ConcreteImageModelSubclass]) From 90ae9e83391b2a8b3d66384ba3b376c6a1f66257 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sun, 10 Feb 2013 15:55:32 -0500 Subject: [PATCH 5/6] Fix get_nonabstract_descendants Previously, function was only returning the first non-abstract descendant. (It should also return concrete descendants of concrete descendants, which it now does.) Also, converted to generator. --- imagekit/utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/imagekit/utils.py b/imagekit/utils.py index d749ac6..91f3344 100644 --- a/imagekit/utils.py +++ b/imagekit/utils.py @@ -25,13 +25,11 @@ def _get_models(apps): def get_nonabstract_descendants(model): """ Returns all non-abstract descendants of the model. """ - if model._meta.abstract: - descendants = [] - for m in model.__subclasses__(): - descendants += get_nonabstract_descendants(m) - return descendants - else: - return [model] + if not model._meta.abstract: + yield model + for s in model.__subclasses__(): + for m in get_nonabstract_descendants(s): + yield m def get_by_qname(path, desc): From b53b7c3cae56e30f74c3cf0a67fb9dd658711d35 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sun, 10 Feb 2013 16:01:50 -0500 Subject: [PATCH 6/6] Clarify ImageFieldSourceGroup docstrings --- imagekit/specs/sourcegroups.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imagekit/specs/sourcegroups.py b/imagekit/specs/sourcegroups.py index 59810bd..69f2daf 100644 --- a/imagekit/specs/sourcegroups.py +++ b/imagekit/specs/sourcegroups.py @@ -125,7 +125,7 @@ class ModelSignalRouter(object): class ImageFieldSourceGroup(object): """ A source group that repesents a particular field across all instances of a - model. + model and its subclasses. """ def __init__(self, model_class, image_field): @@ -137,7 +137,7 @@ class ImageFieldSourceGroup(object): """ A generator that returns the source files that this source group represents; in this case, a particular field of every instance of a - particular model. + particular model and its subclasses. """ for model in get_nonabstract_descendants(self.model_class):