mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-05-28 13:58:17 +00:00
Merge branch 'rename-cache-things' into ik-next
This commit is contained in:
commit
2ca4e4b6c2
17 changed files with 262 additions and 267 deletions
|
|
@ -3,11 +3,11 @@ from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class ImageKitConf(AppConf):
|
class ImageKitConf(AppConf):
|
||||||
DEFAULT_IMAGE_CACHE_BACKEND = 'imagekit.imagecache.backends.Simple'
|
DEFAULT_GENERATEDFILE_BACKEND = 'imagekit.generatedfiles.backends.Simple'
|
||||||
CACHE_BACKEND = None
|
CACHE_BACKEND = None
|
||||||
CACHE_DIR = 'CACHE/images'
|
GENERATED_FILE_DIR = 'generated/images'
|
||||||
CACHE_PREFIX = 'imagekit:'
|
CACHE_PREFIX = 'imagekit:'
|
||||||
DEFAULT_IMAGE_CACHE_STRATEGY = 'imagekit.imagecache.strategies.JustInTime'
|
DEFAULT_GENERATEDFILE_STRATEGY = 'imagekit.generatedfiles.strategies.JustInTime'
|
||||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||||
|
|
||||||
def configure_cache_backend(self, value):
|
def configure_cache_backend(self, value):
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
from django.conf import settings
|
from django.core.files.base import File, ContentFile
|
||||||
from django.core.files.base import ContentFile, File
|
|
||||||
from django.core.files.images import ImageFile
|
|
||||||
from django.utils.encoding import smart_str, smart_unicode
|
from django.utils.encoding import smart_str, smart_unicode
|
||||||
from django.utils.functional import LazyObject
|
|
||||||
import os
|
import os
|
||||||
from .registry import generator_registry
|
from .utils import format_to_mimetype, extension_to_mimetype
|
||||||
from .signals import before_access
|
|
||||||
from .utils import (format_to_mimetype, extension_to_mimetype, get_logger,
|
|
||||||
get_singleton, generate)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseIKFile(File):
|
class BaseIKFile(File):
|
||||||
|
|
@ -72,67 +66,6 @@ class BaseIKFile(File):
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
|
|
||||||
class GeneratedImageCacheFile(BaseIKFile, ImageFile):
|
|
||||||
"""
|
|
||||||
A cache file that represents the result of a generator. Creating an instance
|
|
||||||
of this class is not enough to trigger the creation of the cache file. In
|
|
||||||
fact, one of the main points of this class is to allow the creation of the
|
|
||||||
file to be deferred until the time that the image cache strategy requires
|
|
||||||
it.
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, generator, name=None, storage=None, image_cache_backend=None):
|
|
||||||
"""
|
|
||||||
:param generator: The object responsible for generating a new image.
|
|
||||||
:param name: The filename
|
|
||||||
:param storage: A Django storage object that will be used to save the
|
|
||||||
file.
|
|
||||||
:param image_cache_backend: The object responsible for managing the
|
|
||||||
state of the cache file.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.generator = generator
|
|
||||||
|
|
||||||
self.name = name or getattr(generator, 'cache_file_name', None)
|
|
||||||
storage = storage or getattr(generator, 'cache_file_storage',
|
|
||||||
None) or get_singleton(settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
|
|
||||||
'file storage backend')
|
|
||||||
self.image_cache_backend = image_cache_backend or getattr(generator,
|
|
||||||
'image_cache_backend', None)
|
|
||||||
|
|
||||||
super(GeneratedImageCacheFile, self).__init__(storage=storage)
|
|
||||||
|
|
||||||
def _require_file(self):
|
|
||||||
before_access.send(sender=self, file=self)
|
|
||||||
return super(GeneratedImageCacheFile, self)._require_file()
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
return self.image_cache_backend.clear(self)
|
|
||||||
|
|
||||||
def invalidate(self):
|
|
||||||
return self.image_cache_backend.invalidate(self)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
return self.image_cache_backend.validate(self)
|
|
||||||
|
|
||||||
def generate(self):
|
|
||||||
# Generate the file
|
|
||||||
content = generate(self.generator)
|
|
||||||
|
|
||||||
actual_name = self.storage.save(self.name, content)
|
|
||||||
|
|
||||||
if actual_name != self.name:
|
|
||||||
get_logger().warning('The storage backend %s did not save the file'
|
|
||||||
' with the requested name ("%s") and instead used'
|
|
||||||
' "%s". This may be because a file already existed with'
|
|
||||||
' the requested name. If so, you may have meant to call'
|
|
||||||
' validate() instead of generate(), or there may be a'
|
|
||||||
' race condition in the image cache backend %s. The'
|
|
||||||
' saved file will not be used.' % (self.storage,
|
|
||||||
self.name, actual_name,
|
|
||||||
self.image_cache_backend))
|
|
||||||
|
|
||||||
|
|
||||||
class IKContentFile(ContentFile):
|
class IKContentFile(ContentFile):
|
||||||
"""
|
"""
|
||||||
Wraps a ContentFile in a file-like object with a filename and a
|
Wraps a ContentFile in a file-like object with a filename and a
|
||||||
|
|
@ -160,14 +93,3 @@ class IKContentFile(ContentFile):
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return smart_unicode(self.file.name or u'')
|
return smart_unicode(self.file.name or u'')
|
||||||
|
|
||||||
|
|
||||||
class LazyGeneratedImageCacheFile(LazyObject):
|
|
||||||
def __init__(self, generator_id, *args, **kwargs):
|
|
||||||
super(LazyGeneratedImageCacheFile, self).__init__()
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
generator = generator_registry.get(generator_id, *args, **kwargs)
|
|
||||||
self._wrapped = GeneratedImageCacheFile(generator)
|
|
||||||
|
|
||||||
self.__dict__['_setup'] = setup
|
|
||||||
|
|
|
||||||
90
imagekit/generatedfiles/__init__.py
Normal file
90
imagekit/generatedfiles/__init__.py
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.files.images import ImageFile
|
||||||
|
from django.utils.functional import LazyObject
|
||||||
|
from ..files import BaseIKFile
|
||||||
|
from ..registry import generator_registry
|
||||||
|
from ..signals import before_access
|
||||||
|
from ..utils import get_logger, get_singleton, generate
|
||||||
|
|
||||||
|
|
||||||
|
class GeneratedImageFile(BaseIKFile, ImageFile):
|
||||||
|
"""
|
||||||
|
A file that represents the result of a generator. Creating an instance of
|
||||||
|
this class is not enough to trigger the generation of the file. In fact,
|
||||||
|
one of the main points of this class is to allow the creation of the file
|
||||||
|
to be deferred until the time that the generated file strategy requires it.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, generator, name=None, storage=None, generatedfile_backend=None):
|
||||||
|
"""
|
||||||
|
:param generator: The object responsible for generating a new image.
|
||||||
|
:param name: The filename
|
||||||
|
:param storage: A Django storage object that will be used to save the
|
||||||
|
file.
|
||||||
|
:param generatedfile_backend: The object responsible for managing the
|
||||||
|
state of the file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.generator = generator
|
||||||
|
|
||||||
|
self.name = name or getattr(generator, 'generatedfile_name', None)
|
||||||
|
storage = storage or getattr(generator, 'generatedfile_storage',
|
||||||
|
None) or get_singleton(settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
|
||||||
|
'file storage backend')
|
||||||
|
self.generatedfile_backend = generatedfile_backend or getattr(generator,
|
||||||
|
'generatedfile_backend', None)
|
||||||
|
|
||||||
|
super(GeneratedImageFile, self).__init__(storage=storage)
|
||||||
|
|
||||||
|
def _require_file(self):
|
||||||
|
before_access.send(sender=self, file=self)
|
||||||
|
return super(GeneratedImageFile, self)._require_file()
|
||||||
|
|
||||||
|
def generate(self, force=False):
|
||||||
|
if force:
|
||||||
|
self._generate()
|
||||||
|
else:
|
||||||
|
self.generatedfile_backend.ensure_exists(self)
|
||||||
|
|
||||||
|
def _generate(self):
|
||||||
|
# Generate the file
|
||||||
|
content = generate(self.generator)
|
||||||
|
|
||||||
|
actual_name = self.storage.save(self.name, content)
|
||||||
|
|
||||||
|
if actual_name != self.name:
|
||||||
|
get_logger().warning('The storage backend %s did not save the file'
|
||||||
|
' with the requested name ("%s") and instead used'
|
||||||
|
' "%s". This may be because a file already existed with'
|
||||||
|
' the requested name. If so, you may have meant to call'
|
||||||
|
' ensure_exists() instead of generate(), or there may be a'
|
||||||
|
' race condition in the file backend %s. The saved file'
|
||||||
|
' will not be used.' % (self.storage,
|
||||||
|
self.name, actual_name,
|
||||||
|
self.generatedfile_backend))
|
||||||
|
|
||||||
|
|
||||||
|
class LazyGeneratedImageFile(LazyObject):
|
||||||
|
def __init__(self, generator_id, *args, **kwargs):
|
||||||
|
super(LazyGeneratedImageFile, self).__init__()
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
generator = generator_registry.get(generator_id, *args, **kwargs)
|
||||||
|
self._wrapped = GeneratedImageFile(generator)
|
||||||
|
|
||||||
|
self.__dict__['_setup'] = setup
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self._wrapped is None:
|
||||||
|
self._setup()
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self or 'None')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self._wrapped is None:
|
||||||
|
self._setup()
|
||||||
|
return str(self._wrapped)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
if self._wrapped is None:
|
||||||
|
self._setup()
|
||||||
|
return unicode(self._wrapped)
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
def validate_now(file):
|
def generate(file):
|
||||||
file.validate()
|
file.generate()
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -7,15 +7,15 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
validate_now_task = task(validate_now)
|
generate_task = task(generate)
|
||||||
|
|
||||||
|
|
||||||
def deferred_validate(file):
|
def generate_deferred(file):
|
||||||
try:
|
try:
|
||||||
import celery # NOQA
|
import celery # NOQA
|
||||||
except:
|
except:
|
||||||
raise ImportError("Deferred validation requires the the 'celery' library")
|
raise ImportError("Deferred validation requires the the 'celery' library")
|
||||||
validate_now_task.delay(file)
|
generate_task.delay(file)
|
||||||
|
|
||||||
|
|
||||||
def clear_now(file):
|
def clear_now(file):
|
||||||
64
imagekit/generatedfiles/backends.py
Normal file
64
imagekit/generatedfiles/backends.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
from ..utils import get_singleton
|
||||||
|
from django.core.cache import get_cache
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_generatedfile_backend():
|
||||||
|
"""
|
||||||
|
Get the default file backend.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.conf import settings
|
||||||
|
return get_singleton(settings.IMAGEKIT_DEFAULT_GENERATEDFILE_BACKEND,
|
||||||
|
'file backend')
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidFileBackendError(ImproperlyConfigured):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CachedFileBackend(object):
|
||||||
|
@property
|
||||||
|
def cache(self):
|
||||||
|
if not getattr(self, '_cache', None):
|
||||||
|
from django.conf import settings
|
||||||
|
self._cache = get_cache(settings.IMAGEKIT_CACHE_BACKEND)
|
||||||
|
return self._cache
|
||||||
|
|
||||||
|
def get_key(self, file):
|
||||||
|
from django.conf import settings
|
||||||
|
return '%s%s-exists' % (settings.IMAGEKIT_CACHE_PREFIX, file.name)
|
||||||
|
|
||||||
|
def file_exists(self, file):
|
||||||
|
key = self.get_key(file)
|
||||||
|
exists = self.cache.get(key)
|
||||||
|
if exists is None:
|
||||||
|
exists = self._file_exists(file)
|
||||||
|
self.cache.set(key, exists)
|
||||||
|
return exists
|
||||||
|
|
||||||
|
def ensure_exists(self, file):
|
||||||
|
if self.file_exists(file):
|
||||||
|
self.create(file)
|
||||||
|
self.cache.set(self.get_key(file), True)
|
||||||
|
|
||||||
|
|
||||||
|
class Simple(CachedFileBackend):
|
||||||
|
"""
|
||||||
|
The most basic file backend. The storage is consulted to see if the file
|
||||||
|
exists.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _file_exists(self, file):
|
||||||
|
if not getattr(file, '_file', None):
|
||||||
|
# No file on object. Have to check storage.
|
||||||
|
return not file.storage.exists(file.name)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create(self, file):
|
||||||
|
"""
|
||||||
|
Generates a new image by running the processors on the source file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
file.generate(force=True)
|
||||||
|
|
@ -1,34 +1,33 @@
|
||||||
from django.utils.functional import LazyObject
|
from django.utils.functional import LazyObject
|
||||||
from .actions import validate_now, clear_now
|
|
||||||
from ..utils import get_singleton
|
from ..utils import get_singleton
|
||||||
|
|
||||||
|
|
||||||
class JustInTime(object):
|
class JustInTime(object):
|
||||||
"""
|
"""
|
||||||
A caching strategy that validates the file right before it's needed.
|
A strategy that ensures the file exists right before it's needed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def before_access(self, file):
|
def before_access(self, file):
|
||||||
validate_now(file)
|
file.generate()
|
||||||
|
|
||||||
|
|
||||||
class Optimistic(object):
|
class Optimistic(object):
|
||||||
"""
|
"""
|
||||||
A caching strategy that acts immediately when the cacheable file changes
|
A strategy that acts immediately when the source file changes and assumes
|
||||||
and assumes that the cache files will not be removed (i.e. doesn't
|
that the generated files will not be removed (i.e. it doesn't ensure the
|
||||||
revalidate on access).
|
generated file exists when it's accessed).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_source_created(self, file):
|
def on_source_created(self, file):
|
||||||
validate_now(file)
|
file.generate()
|
||||||
|
|
||||||
def on_source_deleted(self, file):
|
def on_source_deleted(self, file):
|
||||||
clear_now(file)
|
file.delete()
|
||||||
|
|
||||||
def on_source_changed(self, file):
|
def on_source_changed(self, file):
|
||||||
validate_now(file)
|
file.generate()
|
||||||
|
|
||||||
|
|
||||||
class DictStrategy(object):
|
class DictStrategy(object):
|
||||||
|
|
@ -40,7 +39,7 @@ class DictStrategy(object):
|
||||||
class StrategyWrapper(LazyObject):
|
class StrategyWrapper(LazyObject):
|
||||||
def __init__(self, strategy):
|
def __init__(self, strategy):
|
||||||
if isinstance(strategy, basestring):
|
if isinstance(strategy, basestring):
|
||||||
strategy = get_singleton(strategy, 'image cache strategy')
|
strategy = get_singleton(strategy, 'generated file strategy')
|
||||||
elif isinstance(strategy, dict):
|
elif isinstance(strategy, dict):
|
||||||
strategy = DictStrategy(strategy)
|
strategy = DictStrategy(strategy)
|
||||||
elif callable(strategy):
|
elif callable(strategy):
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
from ..utils import get_singleton
|
|
||||||
from django.core.cache import get_cache
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_image_cache_backend():
|
|
||||||
"""
|
|
||||||
Get the default image cache backend.
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.conf import settings
|
|
||||||
return get_singleton(settings.IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND,
|
|
||||||
'image cache backend')
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidImageCacheBackendError(ImproperlyConfigured):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CachedValidationBackend(object):
|
|
||||||
@property
|
|
||||||
def cache(self):
|
|
||||||
if not getattr(self, '_cache', None):
|
|
||||||
from django.conf import settings
|
|
||||||
self._cache = get_cache(settings.IMAGEKIT_CACHE_BACKEND)
|
|
||||||
return self._cache
|
|
||||||
|
|
||||||
def get_key(self, file):
|
|
||||||
from django.conf import settings
|
|
||||||
return '%s%s-valid' % (settings.IMAGEKIT_CACHE_PREFIX, file.name)
|
|
||||||
|
|
||||||
def is_invalid(self, file):
|
|
||||||
key = self.get_key(file)
|
|
||||||
cached_value = self.cache.get(key)
|
|
||||||
if cached_value is None:
|
|
||||||
cached_value = self._is_invalid(file)
|
|
||||||
self.cache.set(key, cached_value)
|
|
||||||
return cached_value
|
|
||||||
|
|
||||||
def validate(self, file):
|
|
||||||
if self.is_invalid(file):
|
|
||||||
self._validate(file)
|
|
||||||
self.cache.set(self.get_key(file), True)
|
|
||||||
|
|
||||||
def invalidate(self, file):
|
|
||||||
if not self.is_invalid(file):
|
|
||||||
self._invalidate(file)
|
|
||||||
self.cache.set(self.get_key(file), False)
|
|
||||||
|
|
||||||
|
|
||||||
class Simple(CachedValidationBackend):
|
|
||||||
"""
|
|
||||||
The most basic image cache backend. Files are considered valid if they
|
|
||||||
exist. To invalidate a file, it's deleted; to validate one, it's generated
|
|
||||||
immediately.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _is_invalid(self, file):
|
|
||||||
if not getattr(file, '_file', None):
|
|
||||||
# No file on object. Have to check storage.
|
|
||||||
return not file.storage.exists(file.name)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _validate(self, file):
|
|
||||||
"""
|
|
||||||
Generates a new image by running the processors on the source file.
|
|
||||||
|
|
||||||
"""
|
|
||||||
file.generate()
|
|
||||||
|
|
||||||
def invalidate(self, file):
|
|
||||||
"""
|
|
||||||
Invalidate the file by deleting it. We override ``invalidate()``
|
|
||||||
instead of ``_invalidate()`` because we don't really care to check
|
|
||||||
whether the file is invalid or not.
|
|
||||||
|
|
||||||
"""
|
|
||||||
file.delete(save=False)
|
|
||||||
|
|
||||||
def clear(self, file):
|
|
||||||
file.delete(save=False)
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
import re
|
import re
|
||||||
from ...registry import generator_registry, cacheable_registry
|
from ...registry import generator_registry, generatedfile_registry
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = ("""Warm the image cache for the specified generators (or all generators if
|
help = ("""Generate files for the specified image generators (or all of them if
|
||||||
none was provided). Simple, fnmatch-like wildcards are allowed, with *
|
none was provided). Simple, glob-like wildcards are allowed, with *
|
||||||
matching all characters within a segment, and ** matching across segments.
|
matching all characters within a segment, and ** matching across
|
||||||
(Segments are separated with colons.) So, for example, "a:*:c" will match
|
segments. (Segments are separated with colons.) So, for example,
|
||||||
"a:b:c", but not "a:b:x:c", whereas "a:**:c" will match both. Subsegments
|
"a:*:c" will match "a:b:c", but not "a:b:x:c", whereas "a:**:c" will
|
||||||
are always matched, so "a" will match "a" as well as "a:b" and "a:b:c".""")
|
match both. Subsegments are always matched, so "a" will match "a" as
|
||||||
|
well as "a:b" and "a:b:c".""")
|
||||||
args = '[generator_ids]'
|
args = '[generator_ids]'
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
@ -21,11 +22,11 @@ are always matched, so "a" will match "a" as well as "a:b" and "a:b:c".""")
|
||||||
|
|
||||||
for generator_id in generators:
|
for generator_id in generators:
|
||||||
self.stdout.write('Validating generator: %s\n' % generator_id)
|
self.stdout.write('Validating generator: %s\n' % generator_id)
|
||||||
for cacheable in cacheable_registry.get(generator_id):
|
for file in generatedfile_registry.get(generator_id):
|
||||||
self.stdout.write(' %s\n' % cacheable)
|
self.stdout.write(' %s\n' % file)
|
||||||
try:
|
try:
|
||||||
# TODO: Allow other validation actions through command option
|
# TODO: Allow other validation actions through command option
|
||||||
cacheable.validate()
|
file.generate()
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
# TODO: How should we handle failures? Don't want to error, but should call it out more than this.
|
# 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)
|
self.stdout.write(' FAILED: %s\n' % err)
|
||||||
|
|
@ -26,15 +26,15 @@ class ImageSpecField(SpecHostField):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, processors=None, format=None, options=None,
|
def __init__(self, processors=None, format=None, options=None,
|
||||||
source=None, cache_file_storage=None, autoconvert=None,
|
source=None, generatedfile_storage=None, autoconvert=None,
|
||||||
image_cache_backend=None, image_cache_strategy=None, spec=None,
|
generatedfile_backend=None, generatedfile_strategy=None, spec=None,
|
||||||
id=None):
|
id=None):
|
||||||
|
|
||||||
SpecHost.__init__(self, processors=processors, format=format,
|
SpecHost.__init__(self, processors=processors, format=format,
|
||||||
options=options, cache_file_storage=cache_file_storage,
|
options=options, generatedfile_storage=generatedfile_storage,
|
||||||
autoconvert=autoconvert,
|
autoconvert=autoconvert,
|
||||||
image_cache_backend=image_cache_backend,
|
generatedfile_backend=generatedfile_backend,
|
||||||
image_cache_strategy=image_cache_strategy, spec=spec,
|
generatedfile_strategy=generatedfile_strategy, spec=spec,
|
||||||
spec_id=id)
|
spec_id=id)
|
||||||
|
|
||||||
# TODO: Allow callable for source. See https://github.com/jdriscoll/django-imagekit/issues/158#issuecomment-10921664
|
# TODO: Allow callable for source. See https://github.com/jdriscoll/django-imagekit/issues/158#issuecomment-10921664
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from ...files import GeneratedImageCacheFile
|
from ...generatedfiles import GeneratedImageFile
|
||||||
from django.db.models.fields.files import ImageField
|
from django.db.models.fields.files import ImageField
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ class ImageSpecFileDescriptor(object):
|
||||||
else:
|
else:
|
||||||
source = image_fields[0]
|
source = image_fields[0]
|
||||||
spec = self.field.get_spec(source=source)
|
spec = self.field.get_spec(source=source)
|
||||||
file = GeneratedImageCacheFile(spec)
|
file = GeneratedImageFile(spec)
|
||||||
instance.__dict__[self.attname] = file
|
instance.__dict__[self.attname] = file
|
||||||
return file
|
return file
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ class GeneratorRegistry(object):
|
||||||
|
|
||||||
def before_access_receiver(self, sender, file, **kwargs):
|
def before_access_receiver(self, sender, file, **kwargs):
|
||||||
generator = file.generator
|
generator = file.generator
|
||||||
if generator in self._generators.values():
|
|
||||||
|
# FIXME: I guess this means you can't register functions?
|
||||||
|
if generator.__class__ in self._generators.values():
|
||||||
# Only invoke the strategy method for registered generators.
|
# Only invoke the strategy method for registered generators.
|
||||||
call_strategy_method(generator, 'before_access', file=file)
|
call_strategy_method(generator, 'before_access', file=file)
|
||||||
|
|
||||||
|
|
@ -52,11 +54,11 @@ class GeneratorRegistry(object):
|
||||||
class SourceGroupRegistry(object):
|
class SourceGroupRegistry(object):
|
||||||
"""
|
"""
|
||||||
The source group registry is responsible for listening to source_* signals
|
The source group registry is responsible for listening to source_* signals
|
||||||
on source groups, and relaying them to the image cache strategies of the
|
on source groups, and relaying them to the image generated file strategies
|
||||||
appropriate generators.
|
of the appropriate generators.
|
||||||
|
|
||||||
In addition, registering a new source group also registers its cacheables
|
In addition, registering a new source group also registers its generated
|
||||||
generator with the cacheable registry.
|
files with that registry.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
_signals = {
|
_signals = {
|
||||||
|
|
@ -71,26 +73,26 @@ class SourceGroupRegistry(object):
|
||||||
signal.connect(self.source_group_receiver)
|
signal.connect(self.source_group_receiver)
|
||||||
|
|
||||||
def register(self, generator_id, source_group):
|
def register(self, generator_id, source_group):
|
||||||
from .specs.sourcegroups import SourceGroupCacheablesGenerator
|
from .specs.sourcegroups import SourceGroupFilesGenerator
|
||||||
generator_ids = self._source_groups.setdefault(source_group, set())
|
generator_ids = self._source_groups.setdefault(source_group, set())
|
||||||
generator_ids.add(generator_id)
|
generator_ids.add(generator_id)
|
||||||
cacheable_registry.register(generator_id,
|
generatedfile_registry.register(generator_id,
|
||||||
SourceGroupCacheablesGenerator(source_group, generator_id))
|
SourceGroupFilesGenerator(source_group, generator_id))
|
||||||
|
|
||||||
def unregister(self, generator_id, source_group):
|
def unregister(self, generator_id, source_group):
|
||||||
from .specs.sourcegroups import SourceGroupCacheablesGenerator
|
from .specs.sourcegroups import SourceGroupFilesGenerator
|
||||||
generator_ids = self._source_groups.setdefault(source_group, set())
|
generator_ids = self._source_groups.setdefault(source_group, set())
|
||||||
if generator_id in generator_ids:
|
if generator_id in generator_ids:
|
||||||
generator_ids.remove(generator_id)
|
generator_ids.remove(generator_id)
|
||||||
cacheable_registry.unregister(generator_id,
|
generatedfile_registry.unregister(generator_id,
|
||||||
SourceGroupCacheablesGenerator(source_group, generator_id))
|
SourceGroupFilesGenerator(source_group, generator_id))
|
||||||
|
|
||||||
def source_group_receiver(self, sender, source, signal, **kwargs):
|
def source_group_receiver(self, sender, source, signal, **kwargs):
|
||||||
"""
|
"""
|
||||||
Relay source group signals to the appropriate spec strategy.
|
Relay source group signals to the appropriate spec strategy.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from .files import GeneratedImageCacheFile
|
from .generatedfiles import GeneratedImageFile
|
||||||
source_group = sender
|
source_group = sender
|
||||||
|
|
||||||
# Ignore signals from unregistered groups.
|
# Ignore signals from unregistered groups.
|
||||||
|
|
@ -102,53 +104,53 @@ class SourceGroupRegistry(object):
|
||||||
callback_name = self._signals[signal]
|
callback_name = self._signals[signal]
|
||||||
|
|
||||||
for spec in specs:
|
for spec in specs:
|
||||||
file = GeneratedImageCacheFile(spec)
|
file = GeneratedImageFile(spec)
|
||||||
call_strategy_method(spec, callback_name, file=file)
|
call_strategy_method(spec, callback_name, file=file)
|
||||||
|
|
||||||
|
|
||||||
class CacheableRegistry(object):
|
class GeneratedFileRegistry(object):
|
||||||
"""
|
"""
|
||||||
An object for registering cacheables with generators. The two are
|
An object for registering generated files with image generators. The two are
|
||||||
associated with each other via a string id. We do this (as opposed to
|
associated with each other via a string id. We do this (as opposed to
|
||||||
associating them directly by, for example, putting a ``cacheables``
|
associating them directly by, for example, putting a ``generatedfiles``
|
||||||
attribute on generators) so that generators can be overridden without
|
attribute on image generators) so that image generators can be overridden
|
||||||
losing the associated cacheables. That way, a distributable app can define
|
without losing the associated files. That way, a distributable app can
|
||||||
its own generators without locking the users of the app into it.
|
define its own generators without locking the users of the app into it.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._cacheables = {}
|
self._generatedfiles = {}
|
||||||
|
|
||||||
def register(self, generator_id, cacheables):
|
def register(self, generator_id, generatedfiles):
|
||||||
"""
|
"""
|
||||||
Associates cacheables with a generator id
|
Associates generated files with a generator id
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if cacheables not in self._cacheables:
|
if generatedfiles not in self._generatedfiles:
|
||||||
self._cacheables[cacheables] = set()
|
self._generatedfiles[generatedfiles] = set()
|
||||||
self._cacheables[cacheables].add(generator_id)
|
self._generatedfiles[generatedfiles].add(generator_id)
|
||||||
|
|
||||||
def unregister(self, generator_id, cacheables):
|
def unregister(self, generator_id, generatedfiles):
|
||||||
"""
|
"""
|
||||||
Disassociates cacheables with a generator id
|
Disassociates generated files with a generator id
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self._cacheables[cacheables].remove(generator_id)
|
self._generatedfiles[generatedfiles].remove(generator_id)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get(self, generator_id):
|
def get(self, generator_id):
|
||||||
for k, v in self._cacheables.items():
|
for k, v in self._generatedfiles.items():
|
||||||
if generator_id in v:
|
if generator_id in v:
|
||||||
for cacheable in k():
|
for file in k():
|
||||||
yield cacheable
|
yield file
|
||||||
|
|
||||||
|
|
||||||
class Register(object):
|
class Register(object):
|
||||||
"""
|
"""
|
||||||
Register generators and cacheables.
|
Register generators and generated files.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def generator(self, id, generator=None):
|
def generator(self, id, generator=None):
|
||||||
|
|
@ -162,8 +164,8 @@ class Register(object):
|
||||||
generator_registry.register(id, generator)
|
generator_registry.register(id, generator)
|
||||||
|
|
||||||
# iterable that returns kwargs or callable that returns iterable of kwargs
|
# iterable that returns kwargs or callable that returns iterable of kwargs
|
||||||
def cacheables(self, generator_id, cacheables):
|
def generatedfiles(self, generator_id, generatedfiles):
|
||||||
cacheable_registry.register(generator_id, cacheables)
|
generatedfile_registry.register(generator_id, generatedfiles)
|
||||||
|
|
||||||
def source_group(self, generator_id, source_group):
|
def source_group(self, generator_id, source_group):
|
||||||
source_group_registry.register(generator_id, source_group)
|
source_group_registry.register(generator_id, source_group)
|
||||||
|
|
@ -171,21 +173,21 @@ class Register(object):
|
||||||
|
|
||||||
class Unregister(object):
|
class Unregister(object):
|
||||||
"""
|
"""
|
||||||
Unregister generators and cacheables.
|
Unregister generators and generated files.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def generator(self, id, generator):
|
def generator(self, id, generator):
|
||||||
generator_registry.unregister(id, generator)
|
generator_registry.unregister(id, generator)
|
||||||
|
|
||||||
def cacheables(self, generator_id, cacheables):
|
def generatedfiles(self, generator_id, generatedfiles):
|
||||||
cacheable_registry.unregister(generator_id, cacheables)
|
generatedfile_registry.unregister(generator_id, generatedfiles)
|
||||||
|
|
||||||
def source_group(self, generator_id, source_group):
|
def source_group(self, generator_id, source_group):
|
||||||
source_group_registry.unregister(generator_id, source_group)
|
source_group_registry.unregister(generator_id, source_group)
|
||||||
|
|
||||||
|
|
||||||
generator_registry = GeneratorRegistry()
|
generator_registry = GeneratorRegistry()
|
||||||
cacheable_registry = CacheableRegistry()
|
generatedfile_registry = GeneratedFileRegistry()
|
||||||
source_group_registry = SourceGroupRegistry()
|
source_group_registry = SourceGroupRegistry()
|
||||||
register = Register()
|
register = Register()
|
||||||
unregister = Unregister()
|
unregister = Unregister()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from django.dispatch import Signal
|
from django.dispatch import Signal
|
||||||
|
|
||||||
|
|
||||||
# "Cacheables" (cache file) signals
|
# Generated file signals
|
||||||
before_access = Signal()
|
before_access = Signal()
|
||||||
|
|
||||||
# Source group signals
|
# Source group signals
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ from django.db.models.fields.files import ImageFieldFile
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
from ..imagecache.backends import get_default_image_cache_backend
|
from ..generatedfiles.backends import get_default_generatedfile_backend
|
||||||
from ..imagecache.strategies import StrategyWrapper
|
from ..generatedfiles.strategies import StrategyWrapper
|
||||||
from ..processors import ProcessorPipeline
|
from ..processors import ProcessorPipeline
|
||||||
from ..utils import open_image, img_to_fobj, suggest_extension
|
from ..utils import open_image, img_to_fobj, suggest_extension
|
||||||
from ..registry import generator_registry, register
|
from ..registry import generator_registry, register
|
||||||
|
|
@ -17,27 +17,27 @@ class BaseImageSpec(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cache_file_storage = None
|
generatedfile_storage = None
|
||||||
"""A Django storage system to use to save a generated cache file."""
|
"""A Django storage system to use to save a generated file."""
|
||||||
|
|
||||||
image_cache_backend = None
|
generatedfile_backend = None
|
||||||
"""
|
"""
|
||||||
An object responsible for managing the state of cached files. Defaults to an
|
An object responsible for managing the state of generated files. Defaults to
|
||||||
instance of ``IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND``
|
an instance of ``IMAGEKIT_DEFAULT_GENERATEDFILE_BACKEND``
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
image_cache_strategy = settings.IMAGEKIT_DEFAULT_IMAGE_CACHE_STRATEGY
|
generatedfile_strategy = settings.IMAGEKIT_DEFAULT_GENERATEDFILE_STRATEGY
|
||||||
"""
|
"""
|
||||||
A dictionary containing callbacks that allow you to customize how and when
|
A dictionary containing callbacks that allow you to customize how and when
|
||||||
the image cache is validated. Defaults to
|
the image file is created. Defaults to
|
||||||
``IMAGEKIT_DEFAULT_SPEC_FIELD_IMAGE_CACHE_STRATEGY``.
|
``IMAGEKIT_DEFAULT_GENERATEDFILE_STRATEGY``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.image_cache_backend = self.image_cache_backend or get_default_image_cache_backend()
|
self.generatedfile_backend = self.generatedfile_backend or get_default_generatedfile_backend()
|
||||||
self.image_cache_strategy = StrategyWrapper(self.image_cache_strategy)
|
self.generatedfile_strategy = StrategyWrapper(self.generatedfile_strategy)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
@ -83,16 +83,16 @@ class ImageSpec(BaseImageSpec):
|
||||||
super(ImageSpec, self).__init__()
|
super(ImageSpec, self).__init__()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cache_file_name(self):
|
def generatedfile_name(self):
|
||||||
source_filename = getattr(self.source, 'name', None)
|
source_filename = getattr(self.source, 'name', None)
|
||||||
|
|
||||||
if source_filename is None or os.path.isabs(source_filename):
|
if source_filename is None or os.path.isabs(source_filename):
|
||||||
# Generally, we put the file right in the cache directory.
|
# Generally, we put the file right in the generated file directory.
|
||||||
dir = settings.IMAGEKIT_CACHE_DIR
|
dir = settings.IMAGEKIT_GENERATED_FILE_DIR
|
||||||
else:
|
else:
|
||||||
# For source files with relative names (like Django media files),
|
# For source files with relative names (like Django media files),
|
||||||
# use the source's name to create the new filename.
|
# use the source's name to create the new filename.
|
||||||
dir = os.path.join(settings.IMAGEKIT_CACHE_DIR,
|
dir = os.path.join(settings.IMAGEKIT_GENERATED_FILE_DIR,
|
||||||
os.path.splitext(source_filename)[0])
|
os.path.splitext(source_filename)[0])
|
||||||
|
|
||||||
ext = suggest_extension(source_filename or '', self.format)
|
ext = suggest_extension(source_filename or '', self.format)
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,17 @@ Source groups are the means by which image spec sources are identified. They
|
||||||
have two responsibilities:
|
have two responsibilities:
|
||||||
|
|
||||||
1. To dispatch ``source_created``, ``source_changed``, and ``source_deleted``
|
1. To dispatch ``source_created``, ``source_changed``, and ``source_deleted``
|
||||||
signals. (These will be relayed to the corresponding specs' image cache
|
signals. (These will be relayed to the corresponding specs' generated file
|
||||||
strategies.)
|
strategies.)
|
||||||
2. To provide the source files that they represent, via a generator method named
|
2. To provide the source files that they represent, via a generator method named
|
||||||
``files()``. (This is used by the warmimagecache management command for
|
``files()``. (This is used by the generateimages management command for
|
||||||
"pre-caching" image files.)
|
"pre-caching" image files.)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db.models.signals import post_init, post_save, post_delete
|
from django.db.models.signals import post_init, post_save, post_delete
|
||||||
from django.utils.functional import wraps
|
from django.utils.functional import wraps
|
||||||
from ..files import LazyGeneratedImageCacheFile
|
from ..generatedfiles import LazyGeneratedImageFile
|
||||||
from ..signals import source_created, source_changed, source_deleted
|
from ..signals import source_created, source_changed, source_deleted
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -135,10 +135,9 @@ class ImageFieldSourceGroup(object):
|
||||||
yield getattr(instance, self.image_field)
|
yield getattr(instance, self.image_field)
|
||||||
|
|
||||||
|
|
||||||
class SourceGroupCacheablesGenerator(object):
|
class SourceGroupFilesGenerator(object):
|
||||||
"""
|
"""
|
||||||
A cacheables generator for source groups. The purpose of this class is to
|
A Python generator that yields generated file objects for source groups.
|
||||||
generate cacheables (cache files) from a source group.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, source_group, generator_id):
|
def __init__(self, source_group, generator_id):
|
||||||
|
|
@ -157,7 +156,7 @@ class SourceGroupCacheablesGenerator(object):
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
for source_file in self.source_group.files():
|
for source_file in self.source_group.files():
|
||||||
yield LazyGeneratedImageCacheFile(self.generator_id,
|
yield LazyGeneratedImageFile(self.generator_id,
|
||||||
source=source_file)
|
source=source_file)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from django import template
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from .compat import parse_bits
|
from .compat import parse_bits
|
||||||
from ..files import GeneratedImageCacheFile
|
from ..generatedfiles import GeneratedImageFile
|
||||||
from ..registry import generator_registry
|
from ..registry import generator_registry
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,12 +19,12 @@ _kwarg_map = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_cache_file(context, generator_id, generator_kwargs, source=None):
|
def get_generatedfile(context, generator_id, generator_kwargs, source=None):
|
||||||
generator_id = generator_id.resolve(context)
|
generator_id = generator_id.resolve(context)
|
||||||
kwargs = dict((_kwarg_map.get(k, k), v.resolve(context)) for k,
|
kwargs = dict((_kwarg_map.get(k, k), v.resolve(context)) for k,
|
||||||
v in generator_kwargs.items())
|
v in generator_kwargs.items())
|
||||||
generator = generator_registry.get(generator_id, **kwargs)
|
generator = generator_registry.get(generator_id, **kwargs)
|
||||||
return GeneratedImageCacheFile(generator)
|
return GeneratedImageFile(generator)
|
||||||
|
|
||||||
|
|
||||||
def parse_dimensions(dimensions):
|
def parse_dimensions(dimensions):
|
||||||
|
|
@ -53,7 +53,7 @@ class GenerateImageAssignmentNode(template.Node):
|
||||||
autodiscover()
|
autodiscover()
|
||||||
|
|
||||||
variable_name = self.get_variable_name(context)
|
variable_name = self.get_variable_name(context)
|
||||||
context[variable_name] = get_cache_file(context, self._generator_id,
|
context[variable_name] = get_generatedfile(context, self._generator_id,
|
||||||
self._generator_kwargs)
|
self._generator_kwargs)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ class GenerateImageTagNode(template.Node):
|
||||||
from ..utils import autodiscover
|
from ..utils import autodiscover
|
||||||
autodiscover()
|
autodiscover()
|
||||||
|
|
||||||
file = get_cache_file(context, self._generator_id,
|
file = get_generatedfile(context, self._generator_id,
|
||||||
self._generator_kwargs)
|
self._generator_kwargs)
|
||||||
attrs = dict((k, v.resolve(context)) for k, v in
|
attrs = dict((k, v.resolve(context)) for k, v in
|
||||||
self._html_attrs.items())
|
self._html_attrs.items())
|
||||||
|
|
@ -110,7 +110,7 @@ class ThumbnailAssignmentNode(template.Node):
|
||||||
kwargs.update(parse_dimensions(self._dimensions.resolve(context)))
|
kwargs.update(parse_dimensions(self._dimensions.resolve(context)))
|
||||||
generator = generator_registry.get(generator_id, **kwargs)
|
generator = generator_registry.get(generator_id, **kwargs)
|
||||||
|
|
||||||
context[variable_name] = GeneratedImageCacheFile(generator)
|
context[variable_name] = GeneratedImageFile(generator)
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
@ -136,7 +136,7 @@ class ThumbnailImageTagNode(template.Node):
|
||||||
kwargs.update(dimensions)
|
kwargs.update(dimensions)
|
||||||
generator = generator_registry.get(generator_id, **kwargs)
|
generator = generator_registry.get(generator_id, **kwargs)
|
||||||
|
|
||||||
file = GeneratedImageCacheFile(generator)
|
file = GeneratedImageFile(generator)
|
||||||
|
|
||||||
attrs = dict((k, v.resolve(context)) for k, v in
|
attrs = dict((k, v.resolve(context)) for k, v in
|
||||||
self._html_attrs.items())
|
self._html_attrs.items())
|
||||||
|
|
|
||||||
|
|
@ -425,7 +425,7 @@ def generate(generator):
|
||||||
|
|
||||||
|
|
||||||
def call_strategy_method(generator, method_name, *args, **kwargs):
|
def call_strategy_method(generator, method_name, *args, **kwargs):
|
||||||
strategy = getattr(generator, 'image_cache_strategy', None)
|
strategy = getattr(generator, 'generatedfile_strategy', None)
|
||||||
fn = getattr(strategy, method_name, None)
|
fn = getattr(strategy, method_name, None)
|
||||||
if fn is not None:
|
if fn is not None:
|
||||||
fn(*args, **kwargs)
|
fn(*args, **kwargs)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue