mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-04-10 16:30:58 +00:00
Beginning to move functionality into "sources"
Before this is applied, we're going to have to make it so that the image cache strategies are passed the source file, not the other file.
This commit is contained in:
parent
06e5f45904
commit
fe803f8981
7 changed files with 139 additions and 98 deletions
|
|
@ -1,11 +1,12 @@
|
|||
import os
|
||||
|
||||
from django.db import models
|
||||
from .files import ProcessedImageFieldFile
|
||||
from .utils import ImageSpecFileDescriptor, ImageKitMeta
|
||||
from ..files import ProcessedImageFieldFile
|
||||
from .utils import ImageSpecFileDescriptor
|
||||
from ..receivers import configure_receivers
|
||||
from ...utils import suggest_extension
|
||||
from ...specs import SpecHost
|
||||
from ...specs import SpecHost, spec_registry
|
||||
from ...specs.sources import ImageFieldSpecSource
|
||||
|
||||
|
||||
class ImageSpecField(SpecHost):
|
||||
|
|
@ -40,19 +41,6 @@ class ImageSpecField(SpecHost):
|
|||
|
||||
def contribute_to_class(self, cls, name):
|
||||
setattr(cls, name, ImageSpecFileDescriptor(self, name))
|
||||
try:
|
||||
# Make sure we don't modify an inherited ImageKitMeta instance
|
||||
ik = cls.__dict__['ik']
|
||||
except KeyError:
|
||||
try:
|
||||
base = getattr(cls, '_ik')
|
||||
except AttributeError:
|
||||
ik = ImageKitMeta()
|
||||
else:
|
||||
# Inherit all the spec fields.
|
||||
ik = ImageKitMeta(base.spec_fields)
|
||||
setattr(cls, '_ik', ik)
|
||||
ik.spec_fields.append(name)
|
||||
|
||||
# Generate a spec_id to register the spec with. The default spec id is
|
||||
# "<app>:<model>_<field>"
|
||||
|
|
@ -64,6 +52,10 @@ class ImageSpecField(SpecHost):
|
|||
# later, from outside of the model definition.
|
||||
self.set_spec_id(self.spec_id)
|
||||
|
||||
# Register the model and field as a source for this spec id
|
||||
spec_registry.add_source(self.spec_id,
|
||||
ImageFieldSpecSource(cls, self.image_field))
|
||||
|
||||
|
||||
class ProcessedImageField(models.ImageField, SpecHost):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,29 +1,6 @@
|
|||
from .files import ImageSpecFieldFile
|
||||
|
||||
|
||||
class BoundImageKitMeta(object):
|
||||
def __init__(self, instance, spec_fields):
|
||||
self.instance = instance
|
||||
self.spec_fields = spec_fields
|
||||
|
||||
@property
|
||||
def spec_files(self):
|
||||
return [getattr(self.instance, n) for n in self.spec_fields]
|
||||
|
||||
|
||||
class ImageKitMeta(object):
|
||||
def __init__(self, spec_fields=None):
|
||||
self.spec_fields = list(spec_fields) if spec_fields else []
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
else:
|
||||
ik = BoundImageKitMeta(instance, self.spec_fields)
|
||||
setattr(instance, '_ik', ik)
|
||||
return ik
|
||||
|
||||
|
||||
class ImageSpecFileDescriptor(object):
|
||||
def __init__(self, field, attname):
|
||||
self.attname = attname
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
from django.db.models.signals import post_init, post_save, post_delete
|
||||
from ..utils import ik_model_receiver
|
||||
|
||||
|
||||
def update_source_hashes(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).
|
||||
|
||||
"""
|
||||
instance._ik._source_hashes = dict((f.attname, hash(f.source_file)) \
|
||||
for f in instance._ik.spec_files)
|
||||
return instance._ik._source_hashes
|
||||
|
||||
|
||||
@ik_model_receiver
|
||||
def post_save_receiver(sender, instance=None, created=False, raw=False, **kwargs):
|
||||
if not raw:
|
||||
old_hashes = instance._ik._source_hashes.copy()
|
||||
new_hashes = update_source_hashes(instance)
|
||||
for attname in instance._ik.spec_fields:
|
||||
file = getattr(instance, attname)
|
||||
if created:
|
||||
file.field.spec.image_cache_strategy.invoke_callback('source_create', file)
|
||||
elif old_hashes[attname] != new_hashes[attname]:
|
||||
file.field.spec.image_cache_strategy.invoke_callback('source_change', file)
|
||||
|
||||
|
||||
@ik_model_receiver
|
||||
def post_delete_receiver(sender, instance=None, **kwargs):
|
||||
for spec_file in instance._ik.spec_files:
|
||||
spec_file.field.spec.image_cache_strategy.invoke_callback('source_delete', spec_file)
|
||||
|
||||
|
||||
@ik_model_receiver
|
||||
def post_init_receiver(sender, instance, **kwargs):
|
||||
update_source_hashes(instance)
|
||||
|
||||
|
||||
def configure_receivers():
|
||||
# Connect the signals. We have to listen to every model (not just those
|
||||
# with IK fields) and filter in our receivers because of a Django issue with
|
||||
# abstract base models.
|
||||
# Related:
|
||||
# https://github.com/jdriscoll/django-imagekit/issues/126
|
||||
# https://code.djangoproject.com/ticket/9318
|
||||
uid = 'ik_spec_field_receivers'
|
||||
post_init.connect(post_init_receiver, dispatch_uid=uid)
|
||||
post_save.connect(post_save_receiver, dispatch_uid=uid)
|
||||
post_delete.connect(post_delete_receiver, dispatch_uid=uid)
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
from collections import defaultdict
|
||||
from django.conf import settings
|
||||
from hashlib import md5
|
||||
import os
|
||||
|
|
@ -7,6 +8,7 @@ from .imagecache.backends import get_default_image_cache_backend
|
|||
from .imagecache.strategies import StrategyWrapper
|
||||
from .lib import StringIO
|
||||
from .processors import ProcessorPipeline
|
||||
from .signals import source_created, source_changed, source_deleted
|
||||
from .utils import (open_image, extension_to_format, IKContentFile, img_to_fobj,
|
||||
suggest_extension)
|
||||
|
||||
|
|
@ -14,12 +16,29 @@ from .utils import (open_image, extension_to_format, IKContentFile, img_to_fobj,
|
|||
class SpecRegistry(object):
|
||||
def __init__(self):
|
||||
self._specs = {}
|
||||
self._sources = defaultdict(list)
|
||||
|
||||
def register(self, id, spec):
|
||||
if id in self._specs:
|
||||
raise AlreadyRegistered('The spec with id %s is already registered' % id)
|
||||
self._specs[id] = spec
|
||||
|
||||
def add_source(self, id, source):
|
||||
self._sources[id].append(source)
|
||||
source_created.connect(receiver, sender, weak, dispatch_uid)
|
||||
source_changed.connect(receiver, sender, weak, dispatch_uid)
|
||||
source_deleted.connect(receiver, sender, weak, dispatch_uid)
|
||||
|
||||
def source_receiver(self, source, source_file):
|
||||
# Get a list of specs that use this source.
|
||||
ids = (k for k, v in self._sources.items() if source in v)
|
||||
specs = (self.get_spec(id) for id in ids)
|
||||
for spec in specs:
|
||||
spec.image_cache_strategy.invoke_callback(..., source_file)
|
||||
|
||||
def get_sources(self, id):
|
||||
return self._sources[id]
|
||||
|
||||
def unregister(self, id, spec):
|
||||
try:
|
||||
del self._specs[id]
|
||||
5
imagekit/specs/signals.py
Normal file
5
imagekit/specs/signals.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from django.dispatch import Signal
|
||||
|
||||
source_created = Signal(providing_args=[])
|
||||
source_changed = Signal()
|
||||
source_deleted = Signal()
|
||||
107
imagekit/specs/sources.py
Normal file
107
imagekit/specs/sources.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
from django.db.models.signals import post_init, post_save, post_delete
|
||||
from django.utils.functional import wraps
|
||||
from .signals import source_created, source_changed, source_deleted
|
||||
|
||||
|
||||
def ik_model_receiver(fn):
|
||||
"""
|
||||
A method decorator that filters out signals coming from models that don't
|
||||
have fields that function as ImageFieldSpecSources
|
||||
|
||||
"""
|
||||
@wraps(fn)
|
||||
def receiver(self, sender, **kwargs):
|
||||
if sender in (src.model_class for src in self._sources):
|
||||
fn(sender, **kwargs)
|
||||
return receiver
|
||||
|
||||
|
||||
class ModelSignalRouter(object):
|
||||
def __init__(self):
|
||||
self._sources = []
|
||||
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)
|
||||
post_delete.connect(self.post_delete_receiver, dispatch_uid=uid)
|
||||
|
||||
def add(self, source):
|
||||
self._sources.append(source)
|
||||
|
||||
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)
|
||||
instance._ik['source_hashes'] = dict((k, hash(v.source_file))
|
||||
for k, v in self.get_field_dict(instance).items())
|
||||
return instance._ik['source_hashes']
|
||||
|
||||
def get_field_dict(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.
|
||||
|
||||
"""
|
||||
return dict((src.image_field, getattr(instance, src.image_field)) for
|
||||
src in self._sources if src.model_class is instance.__class__)
|
||||
|
||||
@ik_model_receiver
|
||||
def post_save_receiver(self, sender, instance=None, created=False, raw=False, **kwargs):
|
||||
if not raw:
|
||||
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 created:
|
||||
self.dispatch_signal(source_created, sender, file)
|
||||
elif old_hashes[attname] != new_hashes[attname]:
|
||||
self.dispatch_signal(source_changed, sender, file)
|
||||
|
||||
@ik_model_receiver
|
||||
def post_delete_receiver(self, sender, instance=None, **kwargs):
|
||||
for attname, file in self.get_field_dict(instance):
|
||||
self.dispatch_signal(source_deleted, sender, file)
|
||||
|
||||
@classmethod
|
||||
@ik_model_receiver
|
||||
def post_init_receiver(self, sender, instance, **kwargs):
|
||||
self.update_source_hashes(instance)
|
||||
|
||||
def dispatch_signal(self, signal, model_class, file):
|
||||
"""
|
||||
Dispatch the signal for each of the matching sources. Note that more
|
||||
than one source can have the same model and image_field; it's important
|
||||
that we dispatch the signal for each.
|
||||
|
||||
"""
|
||||
for source in self._sources:
|
||||
if source.model_class is model_class and source.image_field == file.attname:
|
||||
signal.send(sender=source, source_file=file)
|
||||
|
||||
|
||||
class ImageFieldSpecSource(object):
|
||||
def __init__(self, model_class, image_field):
|
||||
"""
|
||||
Good design would dictate that this instance would be responsible for
|
||||
watching for changes for the provided field. However, due to a bug in
|
||||
Django, we can't do that without leaving abstract base models (which
|
||||
don't trigger signals) in the lurch. So instead, we do all signal
|
||||
handling through the signal router.
|
||||
|
||||
Related:
|
||||
https://github.com/jdriscoll/django-imagekit/issues/126
|
||||
https://code.djangoproject.com/ticket/9318
|
||||
|
||||
"""
|
||||
self.model_class = model_class
|
||||
self.image_field = image_field
|
||||
signal_router.add(self)
|
||||
|
||||
|
||||
signal_router = ModelSignalRouter()
|
||||
|
|
@ -399,14 +399,6 @@ def get_singleton(class_path, desc):
|
|||
return instance
|
||||
|
||||
|
||||
def ik_model_receiver(fn):
|
||||
@wraps(fn)
|
||||
def receiver(sender, **kwargs):
|
||||
if getattr(sender, '_ik', None):
|
||||
fn(sender, **kwargs)
|
||||
return receiver
|
||||
|
||||
|
||||
def autodiscover():
|
||||
"""
|
||||
Auto-discover INSTALLED_APPS imagespecs.py modules and fail silently when
|
||||
|
|
|
|||
Loading…
Reference in a new issue