2013-01-29 06:40:00 +00:00
|
|
|
"""
|
|
|
|
|
Source groups are the means by which image spec sources are identified. They
|
|
|
|
|
have two responsibilities:
|
|
|
|
|
|
2013-05-25 04:50:59 +00:00
|
|
|
1. To dispatch ``source_saved`` signals. (These will be relayed to the
|
|
|
|
|
corresponding specs' cache file strategies.)
|
2013-01-29 06:40:00 +00:00
|
|
|
2. To provide the source files that they represent, via a generator method named
|
2013-01-31 08:51:29 +00:00
|
|
|
``files()``. (This is used by the generateimages management command for
|
2013-01-29 06:40:00 +00:00
|
|
|
"pre-caching" image files.)
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2013-05-25 04:50:59 +00:00
|
|
|
from django.db.models.signals import post_init, post_save
|
2012-10-10 04:18:54 +00:00
|
|
|
from django.utils.functional import wraps
|
2013-02-10 20:39:33 +00:00
|
|
|
import inspect
|
2013-02-08 23:15:00 +00:00
|
|
|
from ..cachefiles import LazyImageCacheFile
|
2013-05-25 04:50:59 +00:00
|
|
|
from ..signals import source_saved
|
2013-02-09 05:55:49 +00:00
|
|
|
from ..utils import get_nonabstract_descendants
|
2012-10-10 04:18:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def ik_model_receiver(fn):
|
|
|
|
|
"""
|
|
|
|
|
A method decorator that filters out signals coming from models that don't
|
2013-01-29 06:40:00 +00:00
|
|
|
have fields that function as ImageFieldSourceGroup sources.
|
2012-10-10 04:18:54 +00:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
@wraps(fn)
|
|
|
|
|
def receiver(self, sender, **kwargs):
|
2013-02-10 20:39:33 +00:00
|
|
|
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
|
2012-10-10 04:18:54 +00:00
|
|
|
return receiver
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ModelSignalRouter(object):
|
2012-10-13 03:12:05 +00:00
|
|
|
"""
|
2013-01-29 06:40:00 +00:00
|
|
|
Normally, ``ImageFieldSourceGroup`` would be directly responsible for
|
|
|
|
|
watching for changes on the model field it represents. However, Django does
|
|
|
|
|
not dispatch events for abstract base classes. Therefore, we must listen for
|
|
|
|
|
the signals on all models and filter out those that aren't represented by
|
|
|
|
|
``ImageFieldSourceGroup``s. This class encapsulates that functionality.
|
|
|
|
|
|
|
|
|
|
Related:
|
2013-06-03 19:50:20 +00:00
|
|
|
https://github.com/matthewwithanm/django-imagekit/issues/126
|
2013-01-29 06:40:00 +00:00
|
|
|
https://code.djangoproject.com/ticket/9318
|
2012-10-13 03:12:05 +00:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2012-10-10 04:18:54 +00:00
|
|
|
def __init__(self):
|
2012-12-12 03:33:33 +00:00
|
|
|
self._source_groups = []
|
2012-10-10 04:18:54 +00:00
|
|
|
uid = 'ik_spec_field_receivers'
|
|
|
|
|
post_init.connect(self.post_init_receiver, dispatch_uid=uid)
|
|
|
|
|
post_save.connect(self.post_save_receiver, dispatch_uid=uid)
|
|
|
|
|
|
2012-12-12 03:33:33 +00:00
|
|
|
def add(self, source_group):
|
|
|
|
|
self._source_groups.append(source_group)
|
2012-10-10 04:18:54 +00:00
|
|
|
|
|
|
|
|
def init_instance(self, instance):
|
|
|
|
|
instance._ik = getattr(instance, '_ik', {})
|
|
|
|
|
|
|
|
|
|
def update_source_hashes(self, 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).
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
self.init_instance(instance)
|
2014-09-27 02:21:24 +00:00
|
|
|
instance._ik['source_hashes'] = dict(
|
|
|
|
|
(attname, hash(getattr(instance, attname)))
|
|
|
|
|
for attname in self.get_source_fields(instance))
|
2012-10-10 04:18:54 +00:00
|
|
|
return instance._ik['source_hashes']
|
|
|
|
|
|
2014-09-27 02:21:24 +00:00
|
|
|
def get_source_fields(self, instance):
|
2012-10-10 04:18:54 +00:00
|
|
|
"""
|
2014-09-27 02:21:24 +00:00
|
|
|
Returns a list of the source fields for the given instance.
|
2012-10-10 04:18:54 +00:00
|
|
|
|
|
|
|
|
"""
|
2014-09-27 02:21:24 +00:00
|
|
|
return set(src.image_field
|
|
|
|
|
for src in self._source_groups
|
|
|
|
|
if isinstance(instance, src.model_class))
|
2012-10-10 04:18:54 +00:00
|
|
|
|
|
|
|
|
@ik_model_receiver
|
2013-01-29 06:40:00 +00:00
|
|
|
def post_save_receiver(self, sender, instance=None, created=False, raw=False, **kwargs):
|
2012-10-10 04:18:54 +00:00
|
|
|
if not raw:
|
|
|
|
|
self.init_instance(instance)
|
|
|
|
|
old_hashes = instance._ik.get('source_hashes', {}).copy()
|
|
|
|
|
new_hashes = self.update_source_hashes(instance)
|
2014-09-27 02:21:24 +00:00
|
|
|
for attname in self.get_source_fields(instance):
|
|
|
|
|
file = getattr(instance, attname)
|
|
|
|
|
if file and old_hashes.get(attname) != new_hashes[attname]:
|
2013-05-25 03:21:30 +00:00
|
|
|
self.dispatch_signal(source_saved, file, sender, instance,
|
2013-01-29 06:40:00 +00:00
|
|
|
attname)
|
2012-10-10 04:18:54 +00:00
|
|
|
|
|
|
|
|
@ik_model_receiver
|
2012-10-13 02:36:13 +00:00
|
|
|
def post_init_receiver(self, sender, instance=None, **kwargs):
|
2014-09-27 02:21:24 +00:00
|
|
|
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())
|
2012-10-10 04:18:54 +00:00
|
|
|
|
2012-10-14 19:52:08 +00:00
|
|
|
def dispatch_signal(self, signal, file, model_class, instance, attname):
|
2012-10-10 04:18:54 +00:00
|
|
|
"""
|
2012-12-12 03:33:33 +00:00
|
|
|
Dispatch the signal for each of the matching source groups. Note that
|
|
|
|
|
more than one source can have the same model and image_field; it's
|
|
|
|
|
important that we dispatch the signal for each.
|
2012-10-10 04:18:54 +00:00
|
|
|
|
|
|
|
|
"""
|
2012-12-12 03:33:33 +00:00
|
|
|
for source_group in self._source_groups:
|
2013-02-10 20:39:33 +00:00
|
|
|
if issubclass(model_class, source_group.model_class) and source_group.image_field == attname:
|
2013-01-29 06:40:00 +00:00
|
|
|
signal.send(sender=source_group, source=file)
|
2012-10-10 04:18:54 +00:00
|
|
|
|
|
|
|
|
|
2012-12-01 20:51:28 +00:00
|
|
|
class ImageFieldSourceGroup(object):
|
2013-01-29 06:40:00 +00:00
|
|
|
"""
|
|
|
|
|
A source group that repesents a particular field across all instances of a
|
2013-02-10 21:01:50 +00:00
|
|
|
model and its subclasses.
|
2012-10-10 04:18:54 +00:00
|
|
|
|
2013-01-29 06:40:00 +00:00
|
|
|
"""
|
|
|
|
|
def __init__(self, model_class, image_field):
|
2012-10-10 04:18:54 +00:00
|
|
|
self.model_class = model_class
|
|
|
|
|
self.image_field = image_field
|
|
|
|
|
signal_router.add(self)
|
|
|
|
|
|
2013-01-29 06:40:00 +00:00
|
|
|
def files(self):
|
|
|
|
|
"""
|
|
|
|
|
A generator that returns the source files that this source group
|
|
|
|
|
represents; in this case, a particular field of every instance of a
|
2013-02-10 21:01:50 +00:00
|
|
|
particular model and its subclasses.
|
2013-01-29 06:40:00 +00:00
|
|
|
|
|
|
|
|
"""
|
2013-02-09 05:55:49 +00:00
|
|
|
for model in get_nonabstract_descendants(self.model_class):
|
|
|
|
|
for instance in model.objects.all().iterator():
|
|
|
|
|
yield getattr(instance, self.image_field)
|
2013-01-29 06:40:00 +00:00
|
|
|
|
|
|
|
|
|
2013-01-31 08:51:29 +00:00
|
|
|
class SourceGroupFilesGenerator(object):
|
2013-01-29 06:40:00 +00:00
|
|
|
"""
|
2013-02-05 00:39:25 +00:00
|
|
|
A Python generator that yields cache file objects for source groups.
|
2013-01-29 06:40:00 +00:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, source_group, generator_id):
|
|
|
|
|
self.source_group = source_group
|
|
|
|
|
self.generator_id = generator_id
|
|
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
|
return (isinstance(other, self.__class__)
|
|
|
|
|
and self.__dict__ == other.__dict__)
|
|
|
|
|
|
|
|
|
|
def __ne__(self, other):
|
|
|
|
|
return not self.__eq__(other)
|
|
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
|
return hash((self.source_group, self.generator_id))
|
|
|
|
|
|
|
|
|
|
def __call__(self):
|
|
|
|
|
for source_file in self.source_group.files():
|
2013-02-08 23:15:00 +00:00
|
|
|
yield LazyImageCacheFile(self.generator_id,
|
2013-01-29 06:40:00 +00:00
|
|
|
source=source_file)
|
|
|
|
|
|
2012-10-10 04:18:54 +00:00
|
|
|
|
|
|
|
|
signal_router = ModelSignalRouter()
|