mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-03-16 21:30:23 +00:00
Change spec/source registry to generator/cacheable
This commit is contained in:
parent
4ecfa5d35e
commit
a8855d4c27
9 changed files with 104 additions and 94 deletions
|
|
@ -10,4 +10,4 @@ class Thumbnail(ImageSpec):
|
|||
super(Thumbnail, self).__init__(**kwargs)
|
||||
|
||||
|
||||
register.spec('ik:thumbnail', Thumbnail)
|
||||
register.generator('ik:thumbnail', Thumbnail)
|
||||
|
|
|
|||
|
|
@ -14,19 +14,19 @@ class JustInTime(object):
|
|||
|
||||
class Optimistic(object):
|
||||
"""
|
||||
A caching strategy that acts immediately when the source file chages and
|
||||
assumes that the cache files will not be removed (i.e. doesn't revalidate
|
||||
on access).
|
||||
A caching strategy that acts immediately when the cacheable file changes
|
||||
and assumes that the cache files will not be removed (i.e. doesn't
|
||||
revalidate on access).
|
||||
|
||||
"""
|
||||
|
||||
def on_source_created(self, file):
|
||||
def on_cacheable_created(self, file):
|
||||
validate_now(file)
|
||||
|
||||
def on_source_deleted(self, file):
|
||||
def on_cacheable_deleted(self, file):
|
||||
clear_now(file)
|
||||
|
||||
def on_source_changed(self, file):
|
||||
def on_cacheable_changed(self, file):
|
||||
validate_now(file)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
import re
|
||||
from ...files import GeneratedImageCacheFile
|
||||
from ...registry import generator_registry, source_group_registry
|
||||
from ...registry import generator_registry, cacheable_registry
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = ('Warm the image cache for the specified specs (or all specs if none'
|
||||
' was provided). Simple wildcard matching (using asterisks) is'
|
||||
' supported.')
|
||||
args = '[spec_ids]'
|
||||
help = ('Warm the image cache for the specified generators'
|
||||
' (or all generators if none was provided).'
|
||||
' Simple wildcard matching (using asterisks) is supported.')
|
||||
args = '[generator_ids]'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
specs = generator_registry.get_ids()
|
||||
generators = generator_registry.get_ids()
|
||||
|
||||
if args:
|
||||
patterns = self.compile_patterns(args)
|
||||
specs = (id for id in specs if any(p.match(id) for p in patterns))
|
||||
generators = (id for id in generators if any(p.match(id) for p in patterns))
|
||||
|
||||
for spec_id in specs:
|
||||
self.stdout.write('Validating spec: %s\n' % spec_id)
|
||||
for source_group in source_group_registry.get(spec_id):
|
||||
for source in source_group.files():
|
||||
if source:
|
||||
spec = generator_registry.get(spec_id, source=source) # TODO: HINTS! (Probably based on source, so this will need to be moved into loop below.)
|
||||
self.stdout.write(' %s\n' % source)
|
||||
for generator_id in generators:
|
||||
self.stdout.write('Validating generator: %s\n' % generator_id)
|
||||
for cacheables in cacheable_registry.get(generator_id):
|
||||
for cacheable in cacheables.files():
|
||||
if cacheable:
|
||||
generator = generator_registry.get(generator_id, cacheable=cacheable) # TODO: HINTS! (Probably based on cacheable, so this will need to be moved into loop below.)
|
||||
self.stdout.write(' %s\n' % cacheable)
|
||||
try:
|
||||
# TODO: Allow other validation actions through command option
|
||||
GeneratedImageCacheFile(spec).validate()
|
||||
GeneratedImageCacheFile(generator).validate()
|
||||
except Exception, err:
|
||||
# TODO: How should we handle failures? Don't want to error, but should call it out more than this.
|
||||
self.stdout.write(' FAILED: %s\n' % err)
|
||||
|
||||
def compile_patterns(self, spec_ids):
|
||||
return [re.compile('%s$' % '.*'.join(re.escape(part) for part in id.split('*'))) for id in spec_ids]
|
||||
def compile_patterns(self, generator_ids):
|
||||
return [re.compile('%s$' % '.*'.join(re.escape(part) for part in id.split('*'))) for id in generator_ids]
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ class ImageSpecField(SpecHostField):
|
|||
self.set_spec_id(cls, name)
|
||||
|
||||
# Add the model and field as a source for this spec id
|
||||
register.sources(self.spec_id,
|
||||
[ImageFieldSourceGroup(cls, self.source)])
|
||||
register.cacheables(self.spec_id,
|
||||
ImageFieldSourceGroup(cls, self.source))
|
||||
|
||||
|
||||
class ProcessedImageField(models.ImageField, SpecHostField):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from .exceptions import AlreadyRegistered, NotRegistered
|
||||
from .signals import (before_access, source_created, source_changed,
|
||||
source_deleted)
|
||||
from .signals import (before_access, cacheable_created, cacheable_changed,
|
||||
cacheable_deleted)
|
||||
|
||||
|
||||
class GeneratorRegistry(object):
|
||||
"""
|
||||
An object for registering generators (specs). This registry provides
|
||||
An object for registering generators. This registry provides
|
||||
a convenient way for a distributable app to define default generators
|
||||
without locking the users of the app into it.
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ class GeneratorRegistry(object):
|
|||
|
||||
def register(self, id, generator):
|
||||
if id in self._generators:
|
||||
raise AlreadyRegistered('The spec or generator with id %s is'
|
||||
raise AlreadyRegistered('The generator with id %s is'
|
||||
' already registered' % id)
|
||||
self._generators[id] = generator
|
||||
|
||||
|
|
@ -24,14 +24,14 @@ class GeneratorRegistry(object):
|
|||
try:
|
||||
del self._generators[id]
|
||||
except KeyError:
|
||||
raise NotRegistered('The spec or generator with id %s is not'
|
||||
raise NotRegistered('The generator with id %s is not'
|
||||
' registered' % id)
|
||||
|
||||
def get(self, id, **kwargs):
|
||||
try:
|
||||
generator = self._generators[id]
|
||||
except KeyError:
|
||||
raise NotRegistered('The spec or generator with id %s is not'
|
||||
raise NotRegistered('The generator with id %s is not'
|
||||
' registered' % id)
|
||||
if callable(generator):
|
||||
return generator(**kwargs)
|
||||
|
|
@ -42,108 +42,114 @@ class GeneratorRegistry(object):
|
|||
return self._generators.keys()
|
||||
|
||||
|
||||
class SourceGroupRegistry(object):
|
||||
class CacheableRegistry(object):
|
||||
"""
|
||||
An object for registering source groups with specs. The two are
|
||||
An object for registering cacheables with generators. The two are
|
||||
associated with each other via a string id. We do this (as opposed to
|
||||
associating them directly by, for example, putting a ``source_groups``
|
||||
attribute on specs) so that specs can be overridden without losing the
|
||||
associated sources. That way, a distributable app can define its own
|
||||
specs without locking the users of the app into it.
|
||||
associating them directly by, for example, putting a ``cacheables``
|
||||
attribute on generators) so that generators can be overridden without
|
||||
losing the associated cacheables. That way, a distributable app can define
|
||||
its own generators without locking the users of the app into it.
|
||||
|
||||
"""
|
||||
|
||||
_signals = [
|
||||
source_created,
|
||||
source_changed,
|
||||
source_deleted,
|
||||
cacheable_created,
|
||||
cacheable_changed,
|
||||
cacheable_deleted,
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self._source_groups = {}
|
||||
self._cacheables = {}
|
||||
for signal in self._signals:
|
||||
signal.connect(self.source_group_receiver)
|
||||
signal.connect(self.cacheable_receiver)
|
||||
before_access.connect(self.before_access_receiver)
|
||||
|
||||
def register(self, spec_id, source_groups):
|
||||
def register(self, generator_id, cacheables):
|
||||
"""
|
||||
Associates source groups with a spec id
|
||||
Associates cacheables with a generator id
|
||||
|
||||
"""
|
||||
for source_group in source_groups:
|
||||
if source_group not in self._source_groups:
|
||||
self._source_groups[source_group] = set()
|
||||
self._source_groups[source_group].add(spec_id)
|
||||
for cacheable in cacheables:
|
||||
if cacheable not in self._cacheables:
|
||||
self._cacheables[cacheable] = set()
|
||||
self._cacheables[cacheable].add(generator_id)
|
||||
|
||||
def unregister(self, spec_id, source_groups):
|
||||
def unregister(self, generator_id, cacheables):
|
||||
"""
|
||||
Disassociates sources with a spec id
|
||||
Disassociates cacheables with a generator id
|
||||
|
||||
"""
|
||||
for source_group in source_groups:
|
||||
for cacheable in cacheables:
|
||||
try:
|
||||
self._source_groups[source_group].remove(spec_id)
|
||||
self._cacheables[cacheable].remove(generator_id)
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
def get(self, spec_id):
|
||||
return [source_group for source_group in self._source_groups
|
||||
if spec_id in self._source_groups[source_group]]
|
||||
def get(self, generator_id):
|
||||
return [cacheable for cacheable in self._cacheables
|
||||
if generator_id in self._cacheables[cacheable]]
|
||||
|
||||
def before_access_receiver(self, sender, generator, file, **kwargs):
|
||||
generator.image_cache_strategy.invoke_callback('before_access', file)
|
||||
def before_access_receiver(self, sender, generator, cacheable, **kwargs):
|
||||
generator.image_cache_strategy.invoke_callback('before_access', cacheable)
|
||||
|
||||
def source_group_receiver(self, sender, source, signal, info, **kwargs):
|
||||
def cacheable_receiver(self, sender, cacheable, signal, info, **kwargs):
|
||||
"""
|
||||
Redirects signals dispatched on sources to the appropriate specs.
|
||||
Redirects signals dispatched on cacheables
|
||||
to the appropriate generators.
|
||||
|
||||
"""
|
||||
source_group = sender
|
||||
if source_group not in self._source_groups:
|
||||
cacheable = sender
|
||||
if cacheable not in self._cacheables:
|
||||
return
|
||||
|
||||
for spec in (generator_registry.get(id, source=source, **info)
|
||||
for id in self._source_groups[source_group]):
|
||||
for generator in (generator_registry.get(id, cacheable=cacheable, **info)
|
||||
for id in self._cacheables[cacheable]):
|
||||
event_name = {
|
||||
source_created: 'source_created',
|
||||
source_changed: 'source_changed',
|
||||
source_deleted: 'source_deleted',
|
||||
cacheable_created: 'cacheable_created',
|
||||
cacheable_changed: 'cacheable_changed',
|
||||
cacheable_deleted: 'cacheable_deleted',
|
||||
}
|
||||
spec._handle_source_event(event_name, source)
|
||||
generator._handle_cacheable_event(event_name, cacheable)
|
||||
|
||||
|
||||
class Register(object):
|
||||
"""
|
||||
Register specs and sources.
|
||||
Register generators and cacheables.
|
||||
|
||||
"""
|
||||
def spec(self, id, spec=None):
|
||||
if spec is None:
|
||||
def generator(self, id, generator=None):
|
||||
if generator is None:
|
||||
# Return a decorator
|
||||
def decorator(cls):
|
||||
self.spec(id, cls)
|
||||
self.generator(id, cls)
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
generator_registry.register(id, spec)
|
||||
generator_registry.register(id, generator)
|
||||
|
||||
def sources(self, spec_id, sources):
|
||||
source_group_registry.register(spec_id, sources)
|
||||
# iterable that returns kwargs or callable that returns iterable of kwargs
|
||||
def cacheables(self, generator_id, cacheables):
|
||||
if callable(cacheables):
|
||||
cacheables = cacheables()
|
||||
cacheable_registry.register(generator_id, cacheables)
|
||||
|
||||
|
||||
class Unregister(object):
|
||||
"""
|
||||
Unregister specs and sources.
|
||||
Unregister generators and cacheables.
|
||||
|
||||
"""
|
||||
def spec(self, id, spec):
|
||||
generator_registry.unregister(id, spec)
|
||||
def generator(self, id, generator):
|
||||
generator_registry.unregister(id, generator)
|
||||
|
||||
def sources(self, spec_id, sources):
|
||||
source_group_registry.unregister(spec_id, sources)
|
||||
def cacheables(self, generator_id, cacheables):
|
||||
if callable(cacheables):
|
||||
cacheables = cacheables()
|
||||
cacheable_registry.unregister(generator_id, cacheables)
|
||||
|
||||
|
||||
generator_registry = GeneratorRegistry()
|
||||
source_group_registry = SourceGroupRegistry()
|
||||
cacheable_registry = CacheableRegistry()
|
||||
register = Register()
|
||||
unregister = Unregister()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from django.dispatch import Signal
|
||||
|
||||
before_access = Signal()
|
||||
source_created = Signal(providing_args=[])
|
||||
source_changed = Signal()
|
||||
source_deleted = Signal()
|
||||
cacheable_created = Signal(providing_args=[])
|
||||
cacheable_changed = Signal()
|
||||
cacheable_deleted = Signal()
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ class SpecHost(object):
|
|||
|
||||
"""
|
||||
self.spec_id = id
|
||||
register.spec(id, self._original_spec)
|
||||
register.generator(id, self._original_spec)
|
||||
|
||||
def get_spec(self, **kwargs):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
from ..signals import cacheable_created, cacheable_changed, cacheable_deleted
|
||||
|
||||
|
||||
def ik_model_receiver(fn):
|
||||
|
|
@ -58,23 +58,25 @@ class ModelSignalRouter(object):
|
|||
src in self._source_groups if src.model_class is instance.__class__)
|
||||
|
||||
@ik_model_receiver
|
||||
def post_save_receiver(self, sender, instance=None, created=False, raw=False, **kwargs):
|
||||
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, file, sender, instance,
|
||||
attname)
|
||||
self.dispatch_signal(cacheable_created, file, sender,
|
||||
instance, attname)
|
||||
elif old_hashes[attname] != new_hashes[attname]:
|
||||
self.dispatch_signal(source_changed, file, sender, instance,
|
||||
attname)
|
||||
self.dispatch_signal(cacheable_changed, file, sender,
|
||||
instance, attname)
|
||||
|
||||
@ik_model_receiver
|
||||
def post_delete_receiver(self, sender, instance=None, **kwargs):
|
||||
for attname, file in self.get_field_dict(instance).items():
|
||||
self.dispatch_signal(source_deleted, file, sender, instance, attname)
|
||||
self.dispatch_signal(cacheable_deleted, file, sender, instance,
|
||||
attname)
|
||||
|
||||
@ik_model_receiver
|
||||
def post_init_receiver(self, sender, instance=None, **kwargs):
|
||||
|
|
@ -119,5 +121,7 @@ class ImageFieldSourceGroup(object):
|
|||
for instance in self.model_class.objects.all():
|
||||
yield getattr(instance, self.image_field)
|
||||
|
||||
def __call__(self):
|
||||
return self.files()
|
||||
|
||||
signal_router = ModelSignalRouter()
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@ class ResizeTo1PixelSquare(ImageSpec):
|
|||
super(ResizeTo1PixelSquare, self).__init__(**kwargs)
|
||||
|
||||
|
||||
register.spec('testspec', TestSpec)
|
||||
register.spec('1pxsq', ResizeTo1PixelSquare)
|
||||
register.generator('testspec', TestSpec)
|
||||
register.generator('1pxsq', ResizeTo1PixelSquare)
|
||||
|
|
|
|||
Loading…
Reference in a new issue