diff --git a/docs/caching.rst b/docs/caching.rst index 7bf7ea6..00d9ae8 100644 --- a/docs/caching.rst +++ b/docs/caching.rst @@ -35,18 +35,25 @@ Cache File Strategy Each ``ImageCacheFile`` has a cache file strategy, which abstracts away when image is actually generated. It can implement the following four methods: -* ``before_access`` - called by ``ImageCacheFile`` when you access its url, - width, or height attribute. +* ``on_content_required`` - called by ``ImageCacheFile`` when it requires the + contents of the generated image. For example, when you call ``read()`` or + try to access information contained in the file. +* ``on_existence_required`` - called by ``ImageCacheFile`` when it requires the + generated image to exist but may not be concerned with its contents. For + example, when you access its ``url`` or ``path`` attribute. * ``on_source_created`` - called when the source of a spec is created * ``on_source_changed`` - called when the source of a spec is changed * ``on_source_deleted`` - called when the source of a spec is deleted -The default strategy only defines the first of these, as follows: +The default strategy only defines the first two of these, as follows: .. code-block:: python class JustInTime(object): - def before_access(self, file): + def on_content_required(self, file): + file.generate() + + def on_existence_required(self, file): file.generate() diff --git a/imagekit/cachefiles/__init__.py b/imagekit/cachefiles/__init__.py index 3e67420..806b960 100644 --- a/imagekit/cachefiles/__init__.py +++ b/imagekit/cachefiles/__init__.py @@ -4,7 +4,7 @@ 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 ..signals import content_required, existence_required from ..utils import get_logger, get_singleton, generate, get_by_qname @@ -57,7 +57,30 @@ class ImageCacheFile(BaseIKFile, ImageFile): def _require_file(self): if not getattr(self, '_file', None): - before_access.send(sender=self, file=self) + content_required.send(sender=self, file=self) + self._file = self.storage.open(self.name, 'rb') + + # The ``path`` and ``url`` properties are overridden so as to not call + # ``_require_file``, which is only meant to be called when the file object + # will be directly interacted with (e.g. when using ``read()``). These only + # require the file to exist; they do not need its contents to work. This + # distinction gives the user the flexibility to create a cache file + # strategy that assumes the existence of a file, but can still make the file + # available when its contents are required. + + def _storage_attr(self, attr): + if not getattr(self, '_file', None): + existence_required.send(sender=self, file=self) + fn = getattr(self.storage, attr) + return fn(self.name) + + @property + def path(self): + return self._storage_attr('path') + + @property + def url(self): + return self._storage_attr('url') def generate(self, force=False): """ @@ -101,9 +124,9 @@ class ImageCacheFile(BaseIKFile, ImageFile): if not self.name: return False - # Dispatch the before_access signal before checking to see if the file - # exists. This gives the strategy a chance to create the file. - before_access.send(sender=self, file=self) + # Dispatch the existence_required signal before checking to see if the + # file exists. This gives the strategy a chance to create the file. + existence_required.send(sender=self, file=self) return self.cachefile_backend.exists(self) diff --git a/imagekit/cachefiles/strategies.py b/imagekit/cachefiles/strategies.py index 1104de6..af678ba 100644 --- a/imagekit/cachefiles/strategies.py +++ b/imagekit/cachefiles/strategies.py @@ -8,7 +8,10 @@ class JustInTime(object): """ - def before_access(self, file): + def on_existence_required(self, file): + file.generate() + + def on_content_required(self, file): file.generate() diff --git a/imagekit/registry.py b/imagekit/registry.py index a881aa3..d781b06 100644 --- a/imagekit/registry.py +++ b/imagekit/registry.py @@ -1,5 +1,6 @@ from .exceptions import AlreadyRegistered, NotRegistered -from .signals import before_access, source_created, source_changed, source_deleted +from .signals import (content_required, existence_required, source_created, + source_changed, source_deleted) from .utils import call_strategy_method @@ -12,7 +13,8 @@ class GeneratorRegistry(object): """ def __init__(self): self._generators = {} - before_access.connect(self.before_access_receiver) + content_required.connect(self.content_required_receiver) + existence_required.connect(self.existence_required_receiver) def register(self, id, generator): registered_generator = self._generators.get(id) @@ -42,13 +44,19 @@ class GeneratorRegistry(object): def get_ids(self): return self._generators.keys() - def before_access_receiver(self, sender, file, **kwargs): + def content_required_receiver(self, sender, file, **kwargs): + self._receive(file, 'on_content_required') + + def existence_required_receiver(self, sender, file, **kwargs): + self._receive(file, 'on_existence_required') + + def _receive(self, file, callback): generator = file.generator # 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. - call_strategy_method(file, 'before_access') + call_strategy_method(file, callback) class SourceGroupRegistry(object): diff --git a/imagekit/signals.py b/imagekit/signals.py index 36c915b..12a0042 100644 --- a/imagekit/signals.py +++ b/imagekit/signals.py @@ -2,7 +2,8 @@ from django.dispatch import Signal # Generated file signals -before_access = Signal() +content_required = Signal() +existence_required = Signal() # Source group signals source_created = Signal() diff --git a/tests/models.py b/tests/models.py index f1e20c2..758f2cb 100644 --- a/tests/models.py +++ b/tests/models.py @@ -29,12 +29,16 @@ class ProcessedImageFieldModel(models.Model): class CountingCacheFileStrategy(object): def __init__(self): - self.before_access_count = 0 + self.on_existence_required_count = 0 + self.on_content_required_count = 0 self.on_source_changed_count = 0 self.on_source_created_count = 0 - def before_access(self, file): - self.before_access_count += 1 + def on_existence_required(self, file): + self.on_existence_required_count += 1 + + def on_content_required(self, file): + self.on_content_required_count += 1 def on_source_changed(self, file): self.on_source_changed_count += 1