mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-04-26 15:54:43 +00:00
Add image cache strategies
This new feature gives the user more control over *when* their images are validated. Image cache backends are now exclusively for controlling the *how*. This means you won't have to write a lot of code when you just want to change one or the other.
This commit is contained in:
parent
f43bd4ec28
commit
ba9bf1f877
9 changed files with 100 additions and 51 deletions
|
|
@ -1,9 +1,10 @@
|
|||
from appconf import AppConf
|
||||
from .imagecache.actions import validate_now, clear_now
|
||||
|
||||
|
||||
class ImageKitConf(AppConf):
|
||||
DEFAULT_IMAGE_CACHE_BACKEND = 'imagekit.imagecache.backends.Simple'
|
||||
CACHE_BACKEND = None
|
||||
VALIDATE_ON_ACCESS = True
|
||||
CACHE_DIR = 'CACHE/images'
|
||||
CACHE_PREFIX = 'ik-'
|
||||
DEFAULT_SPEC_FIELD_IMAGE_CACHE_STRATEGY = 'imagekit.imagecache.strategies.Pessimistic'
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class SpecFileGenerator(object):
|
|||
self.format,
|
||||
pickle.dumps(self.options),
|
||||
str(self.autoconvert),
|
||||
])).hexdigest()
|
||||
]).encode('utf-8')).hexdigest()
|
||||
|
||||
def generate_filename(self, source_file):
|
||||
source_filename = source_file.name
|
||||
|
|
|
|||
23
imagekit/imagecache/actions.py
Normal file
23
imagekit/imagecache/actions.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
def validate_now(file):
|
||||
print 'validate now!'
|
||||
file.validate()
|
||||
|
||||
|
||||
try:
|
||||
from celery.task import task
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
validate_now_task = task(validate_now)
|
||||
|
||||
|
||||
def deferred_validate(file):
|
||||
try:
|
||||
import celery
|
||||
except:
|
||||
raise ImportError("Deferred validation requires the the 'celery' library")
|
||||
validate_now_task.delay(file)
|
||||
|
||||
|
||||
def clear_now(file):
|
||||
file.clear()
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from ...utils import get_singleton
|
||||
from ..utils import get_singleton
|
||||
from django.core.cache import get_cache
|
||||
from django.core.cache.backends.dummy import DummyCache
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
from .base import *
|
||||
from .celery import *
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .base import InvalidImageCacheBackendError, Simple as SimpleBackend
|
||||
|
||||
|
||||
def generate(model, pk, attr):
|
||||
try:
|
||||
instance = model._default_manager.get(pk=pk)
|
||||
except model.DoesNotExist:
|
||||
pass # The model was deleted since the task was scheduled. NEVER MIND!
|
||||
else:
|
||||
field_file = getattr(instance, attr)
|
||||
field_file.delete(save=False)
|
||||
field_file.generate(save=True)
|
||||
|
||||
|
||||
class CeleryBackend(SimpleBackend):
|
||||
"""
|
||||
An image cache backend that uses celery to generate images.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
try:
|
||||
from celery.task import task
|
||||
except:
|
||||
raise InvalidImageCacheBackendError("Celery validation backend requires the 'celery' library")
|
||||
if not getattr(CeleryBackend, '_task', None):
|
||||
CeleryBackend._task = task(generate)
|
||||
|
||||
def invalidate(self, file):
|
||||
self._task.delay(file.instance.__class__, file.instance.pk, file.attname)
|
||||
|
||||
def clear(self, file):
|
||||
file.delete(save=False)
|
||||
57
imagekit/imagecache/strategies.py
Normal file
57
imagekit/imagecache/strategies.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from .actions import validate_now, clear_now
|
||||
from ..utils import get_singleton
|
||||
|
||||
|
||||
class Pessimistic(object):
|
||||
"""
|
||||
A caching strategy that validates the file every time it's accessed.
|
||||
|
||||
"""
|
||||
|
||||
def on_access(self, file):
|
||||
validate_now(file)
|
||||
|
||||
def on_source_delete(self, file):
|
||||
clear_now(file)
|
||||
|
||||
def on_source_change(self, file):
|
||||
validate_now(file)
|
||||
|
||||
|
||||
class Optimistic(object):
|
||||
"""
|
||||
A caching strategy that validates when the source file changes and assumes
|
||||
that the cached file will persist.
|
||||
|
||||
"""
|
||||
|
||||
def on_source_create(self, file):
|
||||
validate_now(file)
|
||||
|
||||
def on_source_delete(self, file):
|
||||
clear_now(file)
|
||||
|
||||
def on_source_change(self, file):
|
||||
validate_now(file)
|
||||
|
||||
|
||||
class DictStrategy(object):
|
||||
def __init__(self, callbacks):
|
||||
for k, v in callbacks.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
|
||||
class StrategyWrapper(object):
|
||||
def __init__(self, strategy):
|
||||
if isinstance(strategy, basestring):
|
||||
strategy = get_singleton(strategy, 'image cache strategy')
|
||||
elif isinstance(strategy, dict):
|
||||
strategy = DictStrategy(strategy)
|
||||
elif callable(strategy):
|
||||
strategy = strategy()
|
||||
self._wrapped = strategy
|
||||
|
||||
def invoke_callback(self, name, *args, **kwargs):
|
||||
func = getattr(self._wrapped, 'on_%s' % name, None)
|
||||
if func:
|
||||
func(*args, **kwargs)
|
||||
|
|
@ -5,6 +5,7 @@ from django.db import models
|
|||
from django.db.models.signals import post_init, post_save, post_delete
|
||||
|
||||
from ...imagecache.backends import get_default_image_cache_backend
|
||||
from ...imagecache.strategies import StrategyWrapper
|
||||
from ...generators import SpecFileGenerator
|
||||
from .files import ImageSpecFieldFile, ProcessedImageFieldFile
|
||||
from .utils import ImageSpecFileDescriptor, ImageKitMeta, BoundImageKitMeta
|
||||
|
|
@ -19,7 +20,7 @@ class ImageSpecField(object):
|
|||
"""
|
||||
def __init__(self, processors=None, format=None, options=None,
|
||||
image_field=None, pre_cache=None, storage=None, autoconvert=True,
|
||||
image_cache_backend=None, validate_on_access=None):
|
||||
image_cache_backend=None, image_cache_strategy=None):
|
||||
"""
|
||||
:param processors: A list of processors to run on the original image.
|
||||
:param format: The format of the output file. If not provided,
|
||||
|
|
@ -38,9 +39,10 @@ class ImageSpecField(object):
|
|||
``prepare_image()`` should be performed prior to saving.
|
||||
:param image_cache_backend: An object responsible for managing the state
|
||||
of cached files. Defaults to an instance of
|
||||
IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND
|
||||
:param validate_on_access: Should the image cache be validated when it's
|
||||
accessed?
|
||||
``IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND``
|
||||
:param image_cache_strategy: A dictionary containing callbacks that
|
||||
allow you to customize how and when the image cache is validated.
|
||||
Defaults to ``IMAGEKIT_DEFAULT_SPEC_FIELD_IMAGE_CACHE_STRATEGY``
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -61,8 +63,9 @@ class ImageSpecField(object):
|
|||
self.storage = storage
|
||||
self.image_cache_backend = image_cache_backend or \
|
||||
get_default_image_cache_backend()
|
||||
self.validate_on_access = settings.IMAGEKIT_VALIDATE_ON_ACCESS if \
|
||||
validate_on_access is None else validate_on_access
|
||||
if image_cache_strategy is None:
|
||||
image_cache_strategy = settings.IMAGEKIT_DEFAULT_SPEC_FIELD_IMAGE_CACHE_STRATEGY
|
||||
self.image_cache_strategy = StrategyWrapper(image_cache_strategy)
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
setattr(cls, name, ImageSpecFileDescriptor(self, name))
|
||||
|
|
@ -101,8 +104,11 @@ class ImageSpecField(object):
|
|||
old_hashes = instance._ik._source_hashes.copy()
|
||||
new_hashes = ImageSpecField._update_source_hashes(instance)
|
||||
for attname in instance._ik.spec_fields:
|
||||
if old_hashes[attname] != new_hashes[attname]:
|
||||
getattr(instance, attname).invalidate()
|
||||
file = getattr(instance, attname)
|
||||
if created:
|
||||
file.field.image_cache_strategy.invoke_callback('source_create', file)
|
||||
elif old_hashes[attname] != new_hashes[attname]:
|
||||
file.field.image_cache_strategy.invoke_callback('source_change', file)
|
||||
|
||||
@staticmethod
|
||||
def _update_source_hashes(instance):
|
||||
|
|
@ -119,7 +125,7 @@ class ImageSpecField(object):
|
|||
@staticmethod
|
||||
def _post_delete_receiver(sender, instance=None, **kwargs):
|
||||
for spec_file in instance._ik.spec_files:
|
||||
spec_file.clear()
|
||||
spec_file.field.image_cache_strategy.invoke_callback('source_delete', spec_file)
|
||||
|
||||
@staticmethod
|
||||
def _post_init_receiver(sender, instance, **kwargs):
|
||||
|
|
|
|||
|
|
@ -34,8 +34,7 @@ class ImageSpecFieldFile(ImageFieldFile):
|
|||
def _require_file(self):
|
||||
if not self.source_file:
|
||||
raise ValueError("The '%s' attribute's image_field has no file associated with it." % self.attname)
|
||||
elif self.field.validate_on_access:
|
||||
self.validate()
|
||||
self.field.image_cache_strategy.invoke_callback('access', self)
|
||||
|
||||
def clear(self):
|
||||
return self.field.image_cache_backend.clear(self)
|
||||
|
|
|
|||
Loading…
Reference in a new issue