mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-04-13 17:51:00 +00:00
Differentiating between when the generated file content is required and when the generated file is just required to exist gives us more flexibility with strategies.
156 lines
5.9 KiB
Python
156 lines
5.9 KiB
Python
from django.conf import settings
|
|
from django.core.files import File
|
|
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 content_required, existence_required
|
|
from ..utils import get_logger, get_singleton, generate, get_by_qname
|
|
|
|
|
|
class ImageCacheFile(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 cache file strategy requires it.
|
|
|
|
"""
|
|
def __init__(self, generator, name=None, storage=None, cachefile_backend=None, cachefile_strategy=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 cachefile_backend: The object responsible for managing the
|
|
state of the file.
|
|
:param cachefile_strategy: The object responsible for handling events
|
|
for this file.
|
|
|
|
"""
|
|
self.generator = generator
|
|
|
|
if not name:
|
|
try:
|
|
name = generator.cachefile_name
|
|
except AttributeError:
|
|
fn = get_by_qname(settings.IMAGEKIT_CACHEFILE_NAMER, 'namer')
|
|
name = fn(generator)
|
|
self.name = name
|
|
|
|
storage = storage or getattr(generator, 'cachefile_storage',
|
|
None) or get_singleton(settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
|
|
'file storage backend')
|
|
self.cachefile_backend = (
|
|
cachefile_backend
|
|
or getattr(generator, 'cachefile_backend', None)
|
|
or get_singleton(settings.IMAGEKIT_DEFAULT_CACHEFILE_BACKEND,
|
|
'cache file backend'))
|
|
self.cachefile_strategy = (
|
|
cachefile_strategy
|
|
or getattr(generator, 'cachefile_strategy', None)
|
|
or get_singleton(settings.IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY,
|
|
'cache file strategy')
|
|
)
|
|
|
|
super(ImageCacheFile, self).__init__(storage=storage)
|
|
|
|
def _require_file(self):
|
|
if not getattr(self, '_file', None):
|
|
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):
|
|
"""
|
|
Generate the file. If ``force`` is ``True``, the file will be generated
|
|
whether the file already exists or not.
|
|
|
|
"""
|
|
if force or not getattr(self, '_file', None):
|
|
self.cachefile_backend.generate(self, force)
|
|
|
|
def _generate(self):
|
|
# Generate the file
|
|
content = generate(self.generator)
|
|
|
|
actual_name = self.storage.save(self.name, content)
|
|
|
|
# We're going to reuse the generated file, so we need to reset the pointer.
|
|
content.seek(0)
|
|
|
|
# Store the generated file. If we don't do this, the next time the
|
|
# "file" attribute is accessed, it will result in a call to the storage
|
|
# backend (in ``BaseIKFile._get_file``). Since we already have the
|
|
# contents of the file, what would the point of that be?
|
|
self.file = File(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 generate() instead of'
|
|
' generate(force=True), 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.cachefile_backend
|
|
)
|
|
)
|
|
|
|
def __nonzero__(self):
|
|
if not self.name:
|
|
return False
|
|
|
|
# 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)
|
|
|
|
|
|
class LazyImageCacheFile(LazyObject):
|
|
def __init__(self, generator_id, *args, **kwargs):
|
|
super(LazyImageCacheFile, self).__init__()
|
|
|
|
def setup():
|
|
generator = generator_registry.get(generator_id, *args, **kwargs)
|
|
self._wrapped = ImageCacheFile(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)
|