2013-04-30 22:30:51 +00:00
|
|
|
Caching
|
|
|
|
|
*******
|
|
|
|
|
|
2013-05-01 01:56:55 +00:00
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
Default Backend Workflow
|
2020-02-21 03:25:29 +00:00
|
|
|
========================
|
2013-04-30 22:30:51 +00:00
|
|
|
|
2013-05-01 01:56:55 +00:00
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
``ImageSpec``
|
|
|
|
|
-------------
|
|
|
|
|
|
2013-05-01 01:56:55 +00:00
|
|
|
At the heart of ImageKit are image generators. These are classes with a
|
|
|
|
|
``generate()`` method which returns an image file. An image spec is a type of
|
|
|
|
|
image generator. The thing that makes specs special is that they accept a source
|
|
|
|
|
image. So an image spec is just an image generator that makes an image from some
|
|
|
|
|
other image.
|
|
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
|
|
|
|
|
``ImageCacheFile``
|
|
|
|
|
------------------
|
|
|
|
|
|
|
|
|
|
However, an image spec by itself would be vastly inefficient. Every time an
|
2013-05-01 01:56:55 +00:00
|
|
|
an image was accessed in some way, it would have be regenerated and saved.
|
2013-04-30 22:30:51 +00:00
|
|
|
Most of the time, you want to re-use a previously generated image, based on the
|
2013-05-01 01:56:55 +00:00
|
|
|
input image and spec, instead of generating a new one. That's where
|
2013-04-30 22:30:51 +00:00
|
|
|
``ImageCacheFile`` comes in. ``ImageCacheFile`` is a File-like object that
|
2013-05-01 01:56:55 +00:00
|
|
|
wraps an image generator. They look and feel just like regular file
|
2013-04-30 22:30:51 +00:00
|
|
|
objects, but they've got a little trick up their sleeve: they represent files
|
|
|
|
|
that may not actually exist!
|
|
|
|
|
|
2013-05-01 01:56:55 +00:00
|
|
|
|
2020-02-21 03:25:29 +00:00
|
|
|
.. _cache-file-strategy:
|
|
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
Cache File Strategy
|
|
|
|
|
-------------------
|
2013-05-01 01:56:55 +00:00
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
Each ``ImageCacheFile`` has a cache file strategy, which abstracts away when
|
2013-05-25 04:50:59 +00:00
|
|
|
image is actually generated. It can implement the following three methods:
|
2013-04-30 22:30:51 +00:00
|
|
|
|
2013-05-10 08:39:46 +00:00
|
|
|
* ``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.
|
2013-05-25 03:21:30 +00:00
|
|
|
* ``on_source_saved`` - called when the source of a spec is saved
|
2013-04-30 22:30:51 +00:00
|
|
|
|
2013-05-10 08:39:46 +00:00
|
|
|
The default strategy only defines the first two of these, as follows:
|
2013-04-30 22:30:51 +00:00
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
class JustInTime(object):
|
2013-05-10 08:39:46 +00:00
|
|
|
def on_content_required(self, file):
|
|
|
|
|
file.generate()
|
|
|
|
|
|
|
|
|
|
def on_existence_required(self, file):
|
2013-04-30 22:30:51 +00:00
|
|
|
file.generate()
|
|
|
|
|
|
|
|
|
|
|
2020-02-21 03:25:29 +00:00
|
|
|
.. _cache-file-backend:
|
|
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
Cache File Backend
|
|
|
|
|
------------------
|
2013-05-01 01:56:55 +00:00
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
The ``generate`` method on the ``ImageCacheFile`` is further delegated to the
|
|
|
|
|
cache file backend, which abstracts away how an image is generated.
|
|
|
|
|
|
|
|
|
|
The cache file backend defaults to the setting
|
|
|
|
|
``IMAGEKIT_DEFAULT_CACHEFILE_BACKEND`` and can be set explicitly on a spec with
|
|
|
|
|
the ``cachefile_backend`` attribute.
|
|
|
|
|
|
|
|
|
|
The default works like this:
|
|
|
|
|
|
|
|
|
|
* Checks the file storage to see if a file exists
|
|
|
|
|
* If not, caches that information for 5 seconds
|
|
|
|
|
* If it does, caches that information in the ``IMAGEKIT_CACHE_BACKEND``
|
|
|
|
|
|
2015-01-27 12:57:48 +00:00
|
|
|
If file doesn't exist, generates it immediately and synchronously
|
2013-04-30 22:30:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
That pretty much covers the architecture of the caching layer, and its default
|
|
|
|
|
behavior. I like the default behavior. When will an image be regenerated?
|
|
|
|
|
Whenever it needs to be! When will your storage backend get hit? Depending on
|
2013-05-01 01:56:55 +00:00
|
|
|
your ``IMAGEKIT_CACHE_BACKEND`` settings, as little as twice per file (once for the
|
|
|
|
|
existence check and once to save the generated file). What if you want to change
|
|
|
|
|
a spec? The generated file name (which is used as part of the cache keys) vary
|
|
|
|
|
with the source file name and spec attributes, so if you change any of those, a
|
|
|
|
|
new file will be generated. The default behavior is easy!
|
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
Like regular Django ImageFields, IK doesn't currently cache width and height
|
|
|
|
|
values, so accessing those will always result in a read. That will probably
|
|
|
|
|
change soon though.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Optimizing
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
There are several ways to improve the performance (reduce I/O operations) of
|
|
|
|
|
ImageKit. Each has its own pros and cons.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Caching Data About Generated Files
|
|
|
|
|
----------------------------------
|
|
|
|
|
|
2017-02-08 22:44:32 +00:00
|
|
|
Generally, once a file is generated, you will never be removing it, so by
|
|
|
|
|
default ImageKit will use default cache to cache the state of generated
|
|
|
|
|
files "forever" (or only 5 minutes when ``DEBUG = True``).
|
|
|
|
|
|
|
|
|
|
The time for which ImageKit will cache state is configured with
|
|
|
|
|
``IMAGEKIT_CACHE_TIMEOUT``. If set to ``None`` this means "never expire"
|
|
|
|
|
(default when ``DEBUG = False``). You can reduce this timeout if you want
|
|
|
|
|
or set it to some numeric value in seconds if your cache backend behaves
|
|
|
|
|
differently and for example do not cache values if timeout is ``None``.
|
|
|
|
|
|
|
|
|
|
If you clear your cache durring deployment or some other reason probably
|
|
|
|
|
you do not want to lose the cache for generated images especcialy if you
|
|
|
|
|
are using some slow remote storage (like Amazon S3). Then you can configure
|
|
|
|
|
seprate cache (for example redis) in your ``CACHES`` config and tell ImageKit
|
|
|
|
|
to use it instead of the default cache by setting ``IMAGEKIT_CACHE_BACKEND``.
|
2013-05-01 01:56:55 +00:00
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
|
2013-05-01 01:56:55 +00:00
|
|
|
Pre-Generating Images
|
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
|
|
The default cache file backend generates images immediately and synchronously.
|
|
|
|
|
If you don't do anything special, that will be when they are first requested—as
|
|
|
|
|
part of request-response cycle. This means that the first visitor to your page
|
|
|
|
|
will have to wait for the file to be created before they see any HTML.
|
|
|
|
|
|
|
|
|
|
This can be mitigated, though, by simply generating the images ahead of time, by
|
|
|
|
|
running the ``generateimages`` management command.
|
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
If using with template tags, be sure to read :ref:`source-groups`.
|
2013-04-30 22:30:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
Deferring Image Generation
|
2013-05-01 01:56:55 +00:00
|
|
|
--------------------------
|
|
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
As mentioned above, image generation is normally done synchronously. through
|
|
|
|
|
the default cache file backend. However, you can also take advantage of
|
|
|
|
|
deferred generation. In order to do this, you'll need to do two things:
|
|
|
|
|
|
2015-02-20 16:58:53 +00:00
|
|
|
1) install `celery`__ (or `django-celery`__ if you are bound to Celery<3.1)
|
2013-04-30 22:30:51 +00:00
|
|
|
2) tell ImageKit to use the async cachefile backend.
|
|
|
|
|
To do this for all specs, set the ``IMAGEKIT_DEFAULT_CACHEFILE_BACKEND`` in
|
|
|
|
|
your settings
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
IMAGEKIT_DEFAULT_CACHEFILE_BACKEND = 'imagekit.cachefiles.backends.Async'
|
|
|
|
|
|
|
|
|
|
Images will now be generated asynchronously. But watch out! Asynchrounous
|
|
|
|
|
generation means you'll have to account for images that haven't been generated
|
|
|
|
|
yet. You can do this by checking the truthiness of your files; if an image
|
|
|
|
|
hasn't been generated, it will be falsy:
|
|
|
|
|
|
|
|
|
|
.. code-block:: html
|
|
|
|
|
|
|
|
|
|
{% if not profile.avatar_thumbnail %}
|
|
|
|
|
<img src="/path/to/placeholder.jpg" />
|
|
|
|
|
{% else %}
|
|
|
|
|
<img src="{{ profile.avatar_thumbnail.url }}" />
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
Or, in Python:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
profile = Profile.objects.all()[0]
|
|
|
|
|
if profile.avatar_thumbnail:
|
|
|
|
|
url = profile.avatar_thumbnail.url
|
|
|
|
|
else:
|
|
|
|
|
url = '/path/to/placeholder.jpg'
|
|
|
|
|
|
2015-02-20 16:58:53 +00:00
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
If you are using an "async" backend in combination with the "optimistic"
|
|
|
|
|
cache file strategy (see `Removing Safeguards`_ below), checking for
|
|
|
|
|
thruthiness as described above will not work. The "optimistic" backend is
|
|
|
|
|
very optimistic so to say, and removes the check. Create and use the
|
|
|
|
|
following strategy to a) have images only created on save, and b) retain
|
|
|
|
|
the ability to check whether the images have already been created::
|
|
|
|
|
|
|
|
|
|
class ImagekitOnSaveStrategy(object):
|
|
|
|
|
def on_source_saved(self, file):
|
|
|
|
|
file.generate()
|
|
|
|
|
|
Fix pickle serialization for ImageCacheFile
When Celery CachedFileBackend used with filesystem storage (django.core.files.storage.FileSystemStorage), everything works fine.
But there are issues with storages.backends.s3boto3.S3Boto3Storage (and it's fix from #391), as well as with django_s3_storage.storage.S3Storage.
Exception was:
```
Traceback (most recent call last):
...
File "/src/django-imagekit/imagekit/cachefiles/__init__.py", line 131, in __bool__
existence_required.send(sender=self, file=self)
...
File "/libs/utils.py", line 380, in on_existence_required
file.generate()
File "/src/django-imagekit/imagekit/cachefiles/__init__.py", line 94, in generate
self.cachefile_backend.generate(self, force)
File "/src/django-imagekit/imagekit/cachefiles/backends.py", line 136, in generate
self.schedule_generation(file, force=force)
File "/src/django-imagekit/imagekit/cachefiles/backends.py", line 165, in schedule_generation
_celery_task.delay(self, file.generator, force=force)
...
File "/lib/python3.6/site-packages/kombu/serialization.py", line 221, in dumps
payload = encoder(data)
File "/lib/python3.6/site-packages/kombu/serialization.py", line 350, in pickle_dumps
return dumper(obj, protocol=pickle_protocol)
kombu.exceptions.EncodeError: can't pickle _thread._local objects
```
2017-10-20 11:26:37 +00:00
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
If you use custom storage backend for some specs,
|
|
|
|
|
(storage passed to the field different than configured one)
|
|
|
|
|
it's required the storage to be pickleable
|
2015-02-20 16:58:53 +00:00
|
|
|
|
2013-04-30 22:30:51 +00:00
|
|
|
|
|
|
|
|
__ https://pypi.python.org/pypi/django-celery
|
2015-02-20 16:58:53 +00:00
|
|
|
__ http://www.celeryproject.org
|
2013-04-30 22:30:51 +00:00
|
|
|
|
|
|
|
|
|
2013-05-01 01:56:55 +00:00
|
|
|
Removing Safeguards
|
|
|
|
|
-------------------
|
2013-04-30 22:30:51 +00:00
|
|
|
|
2013-05-01 01:56:55 +00:00
|
|
|
Even with pre-generating images, ImageKit will still try to ensure that your
|
|
|
|
|
image exists when you access it by default. This is for your benefit: if you
|
|
|
|
|
forget to generate your images, ImageKit will see that and generate it for you.
|
|
|
|
|
If the state of the file is cached (see above), this is a pretty cheap
|
|
|
|
|
operation. However, if the state isn't cached, ImageKit will need to query the
|
|
|
|
|
storage backend.
|
2013-04-30 22:30:51 +00:00
|
|
|
|
2013-05-01 01:56:55 +00:00
|
|
|
For those who aren't willing to accept that cost (and who never want ImageKit
|
|
|
|
|
to generate images in the request-responce cycle), there's the "optimistic"
|
|
|
|
|
cache file strategy. This strategy only generates a new image when a spec's
|
|
|
|
|
source image is created or changed. Unlike with the "just in time" strategy,
|
|
|
|
|
accessing the file won't cause it to be generated, ImageKit will just assume
|
|
|
|
|
that it already exists.
|
2013-04-30 22:30:51 +00:00
|
|
|
|
|
|
|
|
To use this cache file strategy for all specs, set the
|
2013-05-01 01:56:55 +00:00
|
|
|
``IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY`` in your settings:
|
2013-04-30 22:30:51 +00:00
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = 'imagekit.cachefiles.strategies.Optimistic'
|
|
|
|
|
|
|
|
|
|
If you have specs that :ref:`change based on attributes of the source
|
|
|
|
|
<dynamic-specs>`, that's not going to cut it, though; the file will also need to
|
|
|
|
|
be generated when those attributes change. Likewise, image generators that don't
|
|
|
|
|
have sources (i.e. generators that aren't specs) won't cause files to be
|
|
|
|
|
generated automatically when using the optimistic strategy. (ImageKit can't know
|
|
|
|
|
when those need to be generated, if not on access.) In both cases, you'll have
|
|
|
|
|
to trigger the file generation yourself—either by generating the file in code
|
|
|
|
|
when necessary, or by periodically running the ``generateimages`` management
|
|
|
|
|
command. Luckily, ImageKit makes this pretty easy:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
from imagekit.cachefiles import LazyImageCacheFile
|
|
|
|
|
|
|
|
|
|
file = LazyImageCacheFile('myapp:profile:avatar_thumbnail', source=source_file)
|
|
|
|
|
file.generate()
|
|
|
|
|
|
|
|
|
|
One final situation in which images won't be generated automatically when using
|
|
|
|
|
the optimistic strategy is when you use a spec with a source that hasn't been
|
|
|
|
|
registered with it. Unlike the previous two examples, this situation cannot be
|
|
|
|
|
rectified by running the ``generateimages`` management command, for the simple
|
|
|
|
|
reason that the command has no way of knowing it needs to generate a file for
|
|
|
|
|
that spec from that source. Typically, this situation would arise when using the
|
|
|
|
|
template tags. Unlike ImageSpecFields, which automatically register all the
|
|
|
|
|
possible source images with the spec you define, the template tags
|
|
|
|
|
("generateimage" and "thumbnail") let you use any spec with any source.
|
|
|
|
|
Therefore, in order to generate the appropriate files using the
|
|
|
|
|
``generateimages`` management command, you'll need to first register a source
|
|
|
|
|
group that represents all of the sources you wish to use with the corresponding
|
|
|
|
|
specs. See :ref:`source-groups` for more information.
|