pax_global_header 0000666 0000000 0000000 00000000064 13204511077 0014512 g ustar 00root root 0000000 0000000 52 comment=097999f3cf2db7bccf3640273e2180f7de7a4111
django-imagekit-4.0.2/ 0000775 0000000 0000000 00000000000 13204511077 0014547 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/.gitignore 0000664 0000000 0000000 00000000216 13204511077 0016536 0 ustar 00root root 0000000 0000000 *.db
*.egg*
*.orig
*.pyc
.DS_Store
.tox
.idea
.vscode
MANIFEST
build
dist
/tests/media/*
!/tests/media/reference.png
/venv
/venv3
/.env
/tags
django-imagekit-4.0.2/.travis.yml 0000664 0000000 0000000 00000002374 13204511077 0016666 0 ustar 00root root 0000000 0000000
language: python
sudo: false
install:
- pip install tox
script:
- tox
matrix:
fast_finish: true
include:
- env: TOXENV=py27-django14
python: 2.7
- env: TOXENV=py27-django15
python: 2.7
- env: TOXENV=py27-django16
python: 2.7
- env: TOXENV=py27-django17
python: 2.7
- env: TOXENV=py27-django18
python: 2.7
- env: TOXENV=py27-django19
python: 2.7
- env: TOXENV=py27-django110
python: 2.7
- env: TOXENV=py27-django111
python: 2.7
- env: TOXENV=py33-django15
python: 3.3
- env: TOXENV=py33-django16
python: 3.3
- env: TOXENV=py33-django17
python: 3.3
- env: TOXENV=py33-django18
python: 3.3
- env: TOXENV=py34-django16
python: 3.4
- env: TOXENV=py34-django17
python: 3.4
- env: TOXENV=py34-django18
python: 3.4
- env: TOXENV=py34-django19
python: 3.4
- env: TOXENV=py34-django110
python: 3.4
- env: TOXENV=py34-django111
python: 3.4
- env: TOXENV=py35-django18
python: 3.5
- env: TOXENV=py35-django19
python: 3.5
- env: TOXENV=py35-django110
python: 3.5
- env: TOXENV=py35-django111
python: 3.5
notifications:
irc: "irc.freenode.org#imagekit"
django-imagekit-4.0.2/AUTHORS 0000664 0000000 0000000 00000003054 13204511077 0015621 0 ustar 00root root 0000000 0000000 ImageKit was originally written by `Justin Driscoll`_.
The field-based API and other post-1.0 stuff was written by the bright people at
HZDG_.
Maintainers
-----------
* `Matthew Tretter`_
* `Bryan Veloso`_
* `Chris Drackett`_
* `Greg Newman`_
Contributors
------------
* `Josh Ourisman`_
* `Jonathan Slenders`_
* `Eric Eldredge`_
* `Chris McKenzie`_
* `Markus Kaiserswerth`_
* `Ryan Bagwell`_
* `Alexander Bohn`_
* `Timothée Peignier`_
* `Madis Väin`_
* `Jan Sagemüller`_
* `Clay McClure`_
* `Jannis Leidel`_
* `Sean Bell`_
* `Saul Shanabrook`_
* `Venelin Stoykov`_
.. _Justin Driscoll: http://github.com/jdriscoll
.. _HZDG: http://hzdg.com
.. _Bryan Veloso: http://github.com/bryanveloso
.. _Chris Drackett: http://github.com/chrisdrackett
.. _Greg Newman: http://github.com/gregnewman
.. _Josh Ourisman: http://github.com/joshourisman
.. _Jonathan Slenders: http://github.com/jonathanslenders
.. _Matthew Tretter: http://github.com/matthewwithanm
.. _Eric Eldredge: http://github.com/lettertwo
.. _Chris McKenzie: http://github.com/kenzic
.. _Ryan Bagwell: http://github.com/ryanbagwell
.. _Markus Kaiserswerth: http://github.com/mkai
.. _Alexander Bohn: http://github.com/fish2000
.. _Timothée Peignier: http://github.com/cyberdelia
.. _Madis Väin: http://github.com/madisvain
.. _Jan Sagemüller: https://github.com/version2
.. _Clay McClure: https://github.com/claymation
.. _Jannis Leidel: https://github.com/jezdez
.. _Sean Bell: https://github.com/seanbell
.. _Saul Shanabrook: https://github.com/saulshanabrook
.. _Venelin Stoykov: https://github.com/vstoykov
django-imagekit-4.0.2/CONTRIBUTING.rst 0000664 0000000 0000000 00000002260 13204511077 0017210 0 ustar 00root root 0000000 0000000 Contributing
------------
We love contributions! These guidelines will help make sure we can get your
contributions merged as quickly as possible:
1. Write `good commit messages`__!
2. If you want to add a new feature, talk to us on the `mailing list`__ or
`IRC`__ first. We might already have plans, or be able to offer some advice.
3. Make sure your code passes the tests that ImageKit already has. To run the
tests, first install tox, ``pip install tox``, then use ``tox``. This will let you know about any errors or style
issues.
4. While we're talking about tests, creating new ones for your code makes it
much easier for us to merge your code quickly. ImageKit uses nose_, so
writing tests is painless. Check out `ours`__ for examples.
5. It's a good idea to do your work in a branch; that way, you can work on more
than one contribution at a time without making them interdependent.
__ http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
__ https://groups.google.com/forum/#!forum/django-imagekit
__ irc://irc.freenode.net/imagekit
.. _nose: https://nose.readthedocs.org/en/latest/
__ https://github.com/matthewwithanm/django-imagekit/tree/develop/tests
django-imagekit-4.0.2/LICENSE 0000664 0000000 0000000 00000003076 13204511077 0015562 0 ustar 00root root 0000000 0000000 Copyright (c) 2007-2008, Justin C. Driscoll
Copyright (c) 2011, Justin C. Driscoll and Primary Maintainers
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of django-imagekit nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
django-imagekit-4.0.2/MANIFEST.in 0000664 0000000 0000000 00000000772 13204511077 0016313 0 ustar 00root root 0000000 0000000 include AUTHORS
include LICENSE
include README.rst
include testrunner.py
include setup.cfg
include tests/*.py
include tests/assets/Lenna.png
include tests/assets/lenna-*.jpg
include tests/media/lenna.png
prune tests/media/CACHE
prune tests/media/b
prune tests/media/photos
include docs/Makefile
include docs/conf.py
include docs/make.bat
include docs/*.rst
recursive-include docs/_themes LICENSE README.rst flask_theme_support.py theme.conf *.css_t *.css *.html
recursive-include imagekit/templates *.html
django-imagekit-4.0.2/README.rst 0000664 0000000 0000000 00000041316 13204511077 0016243 0 ustar 00root root 0000000 0000000 |Build Status|_
.. |Build Status| image:: https://travis-ci.org/matthewwithanm/django-imagekit.svg?branch=develop
.. _Build Status: https://travis-ci.org/matthewwithanm/django-imagekit
ImageKit is a Django app for processing images. Need a thumbnail? A
black-and-white version of a user-uploaded image? ImageKit will make them for
you. If you need to programatically generate one image from another, you need
ImageKit.
ImageKit comes with a bunch of image processors for common tasks like resizing
and cropping, but you can also create your own. For an idea of what's possible,
check out the `Instakit`__ project.
**For the complete documentation on the latest stable version of ImageKit, see**
`ImageKit on RTD`_.
.. _`ImageKit on RTD`: http://django-imagekit.readthedocs.org
__ https://github.com/fish2000/instakit
Installation
============
1. Install `PIL`_ or `Pillow`_. (If you're using an ``ImageField`` in Django,
you should have already done this.)
2. ``pip install django-imagekit``
3. Add ``'imagekit'`` to your ``INSTALLED_APPS`` list in your project's settings.py
.. note:: If you've never seen Pillow before, it considers itself a
more-frequently updated "friendly" fork of PIL that's compatible with
setuptools. As such, it shares the same namespace as PIL does and is a
drop-in replacement.
.. _`PIL`: http://pypi.python.org/pypi/PIL
.. _`Pillow`: http://pypi.python.org/pypi/Pillow
Usage Overview
==============
Specs
-----
You have one image and you want to do something to it to create another image.
But how do you tell ImageKit what to do? By defining an image spec.
An **image spec** is a type of **image generator** that generates a new image
from a source image.
Defining Specs In Models
^^^^^^^^^^^^^^^^^^^^^^^^
The easiest way to use define an image spec is by using an ImageSpecField on
your model class:
.. code-block:: python
from django.db import models
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
profile = Profile.objects.all()[0]
print(profile.avatar_thumbnail.url) # > /media/CACHE/images/982d5af84cddddfd0fbf70892b4431e4.jpg
print(profile.avatar_thumbnail.width) # > 100
As you can probably tell, ImageSpecFields work a lot like Django's
ImageFields. The difference is that they're automatically generated by
ImageKit based on the instructions you give. In the example above, the avatar
thumbnail is a resized version of the avatar image, saved as a JPEG with a
quality of 60.
Sometimes, however, you don't need to keep the original image (the avatar in
the above example); when the user uploads an image, you just want to process it
and save the result. In those cases, you can use the ``ProcessedImageField``
class:
.. code-block:: python
from django.db import models
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill
class Profile(models.Model):
avatar_thumbnail = ProcessedImageField(upload_to='avatars',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
profile = Profile.objects.all()[0]
print(profile.avatar_thumbnail.url) # > /media/avatars/MY-avatar.jpg
print(profile.avatar_thumbnail.width) # > 100
This is pretty similar to our previous example. We don't need to specify a
"source" any more since we're not processing another image field, but we do need
to pass an "upload_to" argument. This behaves exactly as it does for Django
ImageFields.
.. note::
You might be wondering why we didn't need an "upload_to" argument for our
ImageSpecField. The reason is that ProcessedImageFields really are just like
ImageFields—they save the file path in the database and you need to run
syncdb (or create a migration) when you add one to your model.
ImageSpecFields, on the other hand, are virtual—they add no fields to your
database and don't require a database. This is handy for a lot of reasons,
but it means that the path to the image file needs to be programmatically
constructed based on the source image and the spec.
Defining Specs Outside of Models
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Defining specs as models fields is one very convenient way to process images,
but it isn't the only way. Sometimes you can't (or don't want to) add fields to
your models, and that's okay. You can define image spec classes and use them
directly. This can be especially useful for doing image processing in views—
particularly when the processing being done depends on user input.
.. code-block:: python
from imagekit import ImageSpec
from imagekit.processors import ResizeToFill
class Thumbnail(ImageSpec):
processors = [ResizeToFill(100, 50)]
format = 'JPEG'
options = {'quality': 60}
It's probably not surprising that this class is capable of processing an image
in the exact same way as our ImageSpecField above. However, unlike with the
image spec model field, this class doesn't define what source the spec is acting
on, or what should be done with the result; that's up to you:
.. code-block:: python
source_file = open('/path/to/myimage.jpg', 'rb')
image_generator = Thumbnail(source=source_file)
result = image_generator.generate()
.. note::
You don't have to use ``open``! You can use whatever File-like object you
want—including a model's ``ImageField``.
The result of calling ``generate()`` on an image spec is a file-like object
containing our resized image, with which you can do whatever you want. For
example, if you wanted to save it to disk:
.. code-block:: python
dest = open('/path/to/dest.jpg', 'wb')
dest.write(result.read())
dest.close()
Using Specs In Templates
^^^^^^^^^^^^^^^^^^^^^^^^
If you have a model with an ImageSpecField or ProcessedImageField, you can
easily use those processed image just as you would a normal image field:
.. code-block:: html
(This is assuming you have a view that's setting a context variable named
"profile" to an instance of our Profile model.)
But you can also generate processed image files directly in your template—from
any image—without adding anything to your model. In order to do this, you'll
first have to define an image generator class (remember, specs are a type of
generator) in your app somewhere, just as we did in the last section. You'll
also need a way of referring to the generator in your template, so you'll need
to register it.
.. code-block:: python
from imagekit import ImageSpec, register
from imagekit.processors import ResizeToFill
class Thumbnail(ImageSpec):
processors = [ResizeToFill(100, 50)]
format = 'JPEG'
options = {'quality': 60}
register.generator('myapp:thumbnail', Thumbnail)
.. note::
You can register your generator with any id you want, but choose wisely!
If you pick something too generic, you could have a conflict with another
third-party app you're using. For this reason, it's a good idea to prefix
your generator ids with the name of your app. Also, ImageKit recognizes
colons as separators when doing pattern matching (e.g. in the generateimages
management command), so it's a good idea to use those too!
.. warning::
This code can go in any file you want—but you need to make sure it's loaded!
In order to keep things simple, ImageKit will automatically try to load an
module named "imagegenerators" in each of your installed apps. So why don't
you just save yourself the headache and put your image specs in there?
Now that we've created an image generator class and registered it with ImageKit,
we can use it in our templates!
generateimage
"""""""""""""
The most generic template tag that ImageKit gives you is called "generateimage".
It requires at least one argument: the id of a registered image generator.
Additional keyword-style arguments are passed to the registered generator class.
As we saw above, image spec constructors expect a source keyword argument, so
that's what we need to pass to use our thumbnail spec:
.. code-block:: html
{% load imagekit %}
{% generateimage 'myapp:thumbnail' source=source_file %}
This will output the following HTML:
.. code-block:: html
You can also add additional HTML attributes; just separate them from your
keyword args using two dashes:
.. code-block:: html
{% load imagekit %}
{% generateimage 'myapp:thumbnail' source=source_file -- alt="A picture of Me" id="mypicture" %}
Not generating HTML image tags? No problem. The tag also functions as an
assignment tag, providing access to the underlying file object:
.. code-block:: html
{% load imagekit %}
{% generateimage 'myapp:thumbnail' source=source_file as th %}
Click to download a cool {{ th.width }} x {{ th.height }} image!
thumbnail
"""""""""
Because it's such a common use case, ImageKit also provides a "thumbnail"
template tag:
.. code-block:: html
{% load imagekit %}
{% thumbnail '100x50' source_file %}
Like the generateimage tag, the thumbnail tag outputs an tag:
.. code-block:: html
Comparing this syntax to the generateimage tag above, you'll notice a few
differences.
First, we didn't have to specify an image generator id; unless we tell it
otherwise, thumbnail tag uses the generator registered with the id
"imagekit:thumbnail". **It's important to note that this tag is *not* using the
Thumbnail spec class we defined earlier**; it's using the generator registered
with the id "imagekit:thumbnail" which, by default, is
``imagekit.generatorlibrary.Thumbnail``.
Second, we're passing two positional arguments (the dimensions and the source
image) as opposed to the keyword arguments we used with the generateimage tag.
Like with the generateimage tag, you can also specify additional HTML attributes
for the thumbnail tag, or use it as an assignment tag:
.. code-block:: html
{% load imagekit %}
{% thumbnail '100x50' source_file -- alt="A picture of Me" id="mypicture" %}
{% thumbnail '100x50' source_file as th %}
Using Specs in Forms
^^^^^^^^^^^^^^^^^^^^
In addition to the model field above, there's also a form field version of the
``ProcessedImageField`` class. The functionality is basically the same (it
processes an image once and saves the result), but it's used in a form class:
.. code-block:: python
from django import forms
from imagekit.forms import ProcessedImageField
from imagekit.processors import ResizeToFill
class ProfileForm(forms.Form):
avatar_thumbnail = ProcessedImageField(spec_id='myapp:profile:avatar_thumbnail',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
The benefit of using ``imagekit.forms.ProcessedImageField`` (as opposed to
``imagekit.models.ProcessedImageField`` above) is that it keeps the logic for
creating the image outside of your model (in which you would use a normal Django
ImageField). You can even create multiple forms, each with their own
ProcessedImageField, that all store their results in the same image field.
Processors
----------
So far, we've only seen one processor: ``imagekit.processors.ResizeToFill``. But
ImageKit is capable of far more than just resizing images, and that power comes
from its processors.
Processors take a PIL image object, do something to it, and return a new one.
A spec can make use of as many processors as you'd like, which will all be run
in order.
.. code-block:: python
from imagekit import ImageSpec
from imagekit.processors import TrimBorderColor, Adjust
class MySpec(ImageSpec):
processors = [
TrimBorderColor(),
Adjust(contrast=1.2, sharpness=1.1),
]
format = 'JPEG'
options = {'quality': 60}
The ``imagekit.processors`` module contains processors for many common
image manipulations, like resizing, rotating, and color adjustments. However,
if they aren't up to the task, you can create your own. All you have to do is
define a class that implements a ``process()`` method:
.. code-block:: python
class Watermark(object):
def process(self, image):
# Code for adding the watermark goes here.
return image
That's all there is to it! To use your fancy new custom processor, just include
it in your spec's ``processors`` list:
.. code-block:: python
from imagekit import ImageSpec
from imagekit.processors import TrimBorderColor, Adjust
from myapp.processors import Watermark
class MySpec(ImageSpec):
processors = [
TrimBorderColor(),
Adjust(contrast=1.2, sharpness=1.1),
Watermark(),
]
format = 'JPEG'
options = {'quality': 60}
Note that when you import a processor from ``imagekit.processors``, imagekit
in turn imports the processor from `PILKit`_. So if you are looking for
available processors, look at PILKit.
.. _`PILKit`: https://github.com/matthewwithanm/pilkit
Admin
-----
ImageKit also contains a class named ``imagekit.admin.AdminThumbnail``
for displaying specs (or even regular ImageFields) in the
`Django admin change list`_. AdminThumbnail is used as a property on
Django admin classes:
.. code-block:: python
from django.contrib import admin
from imagekit.admin import AdminThumbnail
from .models import Photo
class PhotoAdmin(admin.ModelAdmin):
list_display = ('__str__', 'admin_thumbnail')
admin_thumbnail = AdminThumbnail(image_field='thumbnail')
admin.site.register(Photo, PhotoAdmin)
To use specs defined outside of models:
.. code-block:: python
from django.contrib import admin
from imagekit.admin import AdminThumbnail
from imagekit import ImageSpec
from imagekit.processors import ResizeToFill
from imagekit.cachefiles import ImageCacheFile
from .models import Photo
class AdminThumbnailSpec(ImageSpec):
processors = [ResizeToFill(100, 30)]
format = 'JPEG'
options = {'quality': 60 }
def cached_admin_thumb(instance):
# `image` is the name of the image field on the model
cached = ImageCacheFile(AdminThumbnailSpec(instance.image))
# only generates the first time, subsequent calls use cache
cached.generate()
return cached
class PhotoAdmin(admin.ModelAdmin):
list_display = ('__str__', 'admin_thumbnail')
admin_thumbnail = AdminThumbnail(image_field=cached_admin_thumb)
admin.site.register(Photo, PhotoAdmin)
AdminThumbnail can even use a custom template. For more information, see
``imagekit.admin.AdminThumbnail``.
.. _`Django admin change list`: https://docs.djangoproject.com/en/dev/intro/tutorial02/#customize-the-admin-change-list
Management Commands
-------------------
ImageKit has one management command—`generateimages`—which will generate cache
files for all of your registered image generators. You can also pass it a list
of generator ids in order to generate images selectively.
Community
=========
Please use `the GitHub issue tracker `_
to report bugs with django-imagekit. `A mailing list `_
also exists to discuss the project and ask questions, as well as the official
`#imagekit `_ channel on Freenode.
Contributing
============
We love contributions! And you don't have to be an expert with the library—or
even Django—to contribute either: ImageKit's processors are standalone classes
that are completely separate from the more intimidating internals of Django's
ORM. If you've written a processor that you think might be useful to other
people, open a pull request so we can take a look!
You can also check out our list of `open, contributor-friendly issues`__ for
ideas.
Check out our `contributing guidelines`__ for more information about pitching in
with ImageKit.
__ https://github.com/matthewwithanm/django-imagekit/issues?labels=contributor-friendly&state=open
__ https://github.com/matthewwithanm/django-imagekit/blob/develop/CONTRIBUTING.rst
django-imagekit-4.0.2/docs/ 0000775 0000000 0000000 00000000000 13204511077 0015477 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/docs/Makefile 0000664 0000000 0000000 00000012731 13204511077 0017143 0 ustar 00root root 0000000 0000000 # Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python $(shell which sphinx-build)
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ImageKit.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ImageKit.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/ImageKit"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ImageKit"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
django-imagekit-4.0.2/docs/_themes/ 0000775 0000000 0000000 00000000000 13204511077 0017123 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/docs/_themes/LICENSE 0000775 0000000 0000000 00000003507 13204511077 0020140 0 ustar 00root root 0000000 0000000 Modifications:
Copyright (c) 2010 Kenneth Reitz.
Original Project:
Copyright (c) 2010 by Armin Ronacher.
Some rights reserved.
Redistribution and use in source and binary forms of the theme, with or
without modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
We kindly ask you to only use these themes in an unmodified manner just
for Flask and Flask-related products, not for unrelated projects. If you
like the visual style and want to use it for your own projects, please
consider making some larger changes to the themes (such as changing
font faces, sizes, colors or margins).
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
django-imagekit-4.0.2/docs/_themes/README.rst 0000775 0000000 0000000 00000001324 13204511077 0020615 0 ustar 00root root 0000000 0000000 krTheme Sphinx Style
====================
This repository contains sphinx styles Kenneth Reitz uses in most of
his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related
projects. To use this style in your Sphinx documentation, follow
this guide:
1. put this folder as _themes into your docs folder. Alternatively
you can also use git submodules to check out the contents there.
2. add this to your conf.py: ::
sys.path.append(os.path.abspath('_themes'))
html_theme_path = ['_themes']
html_theme = 'kr'
The following themes exist:
**kr**
the standard flask documentation theme for large projects
**kr_small**
small one-page theme. Intended to be used by very small addon libraries.
django-imagekit-4.0.2/docs/_themes/flask_theme_support.py 0000775 0000000 0000000 00000011413 13204511077 0023556 0 ustar 00root root 0000000 0000000 # flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
class FlaskyStyle(Style):
background_color = "#f8f8f8"
default_style = ""
styles = {
# No corresponding class for the following:
#Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p'
# because special names such as Name.Class, Name.Function, etc.
# are not recognized as such later in the parsing, we choose them
# to look the same as ordinary variables.
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
}
django-imagekit-4.0.2/docs/_themes/kr/ 0000775 0000000 0000000 00000000000 13204511077 0017537 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/docs/_themes/kr/layout.html 0000775 0000000 0000000 00000001141 13204511077 0021742 0 ustar 00root root 0000000 0000000 {%- extends "basic/layout.html" %}
{%- block extrahead %}
{{ super() }}
{% if theme_touch_icon %}
{% endif %}
{% endblock %}
{%- block relbar2 %}{% endblock %}
{%- block footer %}
{%- endblock %}
django-imagekit-4.0.2/docs/_themes/kr/relations.html 0000775 0000000 0000000 00000001116 13204511077 0022427 0 ustar 00root root 0000000 0000000
{% endif %}
{% endblock %}
{# do not display relbars #}
{% block relbar1 %}{% endblock %}
{% block relbar2 %}
{% if theme_github_fork %}
{% endif %}
{% endblock %}
{% block sidebar1 %}{% endblock %}
{% block sidebar2 %}{% endblock %}
django-imagekit-4.0.2/docs/_themes/kr_small/static/ 0000775 0000000 0000000 00000000000 13204511077 0022216 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/docs/_themes/kr_small/static/flasky.css_t 0000775 0000000 0000000 00000011001 13204511077 0024540 0 ustar 00root root 0000000 0000000 /*
* flasky.css_t
* ~~~~~~~~~~~~
*
* Sphinx stylesheet -- flasky theme based on nature theme.
*
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'Georgia', serif;
font-size: 17px;
color: #000;
background: white;
margin: 0;
padding: 0;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 40px auto 0 auto;
width: 700px;
}
hr {
border: 1px solid #B1B4B6;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 30px 30px;
}
img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
div.footer {
text-align: right;
color: #888;
padding: 10px;
font-size: 14px;
width: 650px;
margin: 0 auto 40px auto;
}
div.footer a {
color: #888;
text-decoration: underline;
}
div.related {
line-height: 32px;
color: #888;
}
div.related ul {
padding: 0 0 0 10px;
}
div.related a {
color: #444;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #004B6B;
text-decoration: underline;
}
a:hover {
color: #6D4100;
text-decoration: underline;
}
div.body {
padding-bottom: 40px; /* saved for footer */
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
margin: 30px 0px 10px 0px;
padding: 0;
}
{% if theme_index_logo %}
div.indexwrapper h1 {
text-indent: -999999px;
background: url({{ theme_index_logo }}) no-repeat center center;
height: {{ theme_index_logo_height }};
}
{% endif %}
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: white;
padding: 0 4px;
text-decoration: none;
}
a.headerlink:hover {
color: #444;
background: #eaeaea;
}
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
div.admonition {
background: #fafafa;
margin: 20px -30px;
padding: 10px 30px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
div.admonition p.admonition-title {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
font-size: 24px;
margin: 0 0 10px 0;
padding: 0;
line-height: 1;
}
div.admonition p.last {
margin-bottom: 0;
}
div.highlight{
background-color: white;
}
dt:target, .highlight {
background: #FAF3E8;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre, tt {
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
font-size: 0.85em;
}
img.screenshot {
}
tt.descname, tt.descclassname {
font-size: 0.95em;
}
tt.descname {
padding-right: 0.08em;
}
img.screenshot {
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils {
border: 1px solid #888;
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils td, table.docutils th {
border: 1px solid #888;
padding: 0.25em 0.7em;
}
table.field-list, table.footnote {
border: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
table.footnote {
margin: 15px 0;
width: 100%;
border: 1px solid #eee;
}
table.field-list th {
padding: 0 0.8em 0 0;
}
table.field-list td {
padding: 0;
}
table.footnote td {
padding: 0.5em;
}
dl {
margin: 0;
padding: 0;
}
dl dd {
margin-left: 30px;
}
pre {
padding: 0;
margin: 15px -30px;
padding: 8px;
line-height: 1.3em;
padding: 7px 30px;
background: #eee;
border-radius: 2px;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
}
dl pre {
margin-left: -60px;
padding-left: 60px;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
}
tt.xref, a tt {
background-color: #FBFBFB;
}
a:hover tt {
background: #EEE;
}
django-imagekit-4.0.2/docs/_themes/kr_small/theme.conf 0000775 0000000 0000000 00000000270 13204511077 0022702 0 ustar 00root root 0000000 0000000 [theme]
inherit = basic
stylesheet = flasky.css
nosidebar = true
pygments_style = flask_theme_support.FlaskyStyle
[options]
index_logo = ''
index_logo_height = 120px
github_fork = ''
django-imagekit-4.0.2/docs/advanced_usage.rst 0000664 0000000 0000000 00000016004 13204511077 0021163 0 ustar 00root root 0000000 0000000 Advanced Usage
**************
Models
======
The ``ImageSpecField`` Shorthand Syntax
---------------------------------------
If you've read the README, you already know what an ``ImageSpecField`` is and
the basics of defining one:
.. code-block:: python
from django.db import models
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
This will create an ``avatar_thumbnail`` field which is a resized version of the
image stored in the ``avatar`` image field. But this is actually just shorthand
for creating an ``ImageSpec``, registering it, and associating it with an
``ImageSpecField``:
.. code-block:: python
from django.db import models
from imagekit import ImageSpec, register
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class AvatarThumbnail(ImageSpec):
processors = [ResizeToFill(100, 50)]
format = 'JPEG'
options = {'quality': 60}
register.generator('myapp:profile:avatar_thumbnail', AvatarThumbnail)
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
id='myapp:profile:avatar_thumbnail')
Obviously, the shorthand version is a lot, well…shorter. So why would you ever
want to go through the trouble of using the long form? The answer is that the
long form—creating an image spec class and registering it—gives you a lot more
power over the generated image.
.. _dynamic-specs:
Specs That Change
-----------------
As you'll remember from the README, an image spec is just a type of image
generator that generates a new image from a source image. How does the image
spec get access to the source image? Simple! It's passed to the constructor as
a keyword argument and stored as an attribute of the spec. Normally, we don't
have to concern ourselves with this; the ``ImageSpec`` knows what to do with the
source image and we're happy to let it do its thing. However, having access to
the source image in our spec class can be very useful…
Often, when using an ``ImageSpecField``, you may want the spec to vary based on
properties of a model. (For example, you might want to store image dimensions on
the model and then use them to generate your thumbnail.) Now that we know how to
access the source image from our spec, it's a simple matter to extract its model
and use it to create our processors list. In fact, ImageKit includes a utility
for getting this information.
.. code-block:: python
:emphasize-lines: 11-14
from django.db import models
from imagekit import ImageSpec, register
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
from imagekit.utils import get_field_info
class AvatarThumbnail(ImageSpec):
format = 'JPEG'
options = {'quality': 60}
@property
def processors(self):
model, field_name = get_field_info(self.source)
return [ResizeToFill(model.thumbnail_width, model.thumbnail_height)]
register.generator('myapp:profile:avatar_thumbnail', AvatarThumbnail)
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
id='myapp:profile:avatar_thumbnail')
thumbnail_width = models.PositiveIntegerField()
thumbnail_height = models.PositiveIntegerField()
Now each avatar thumbnail will be resized according to the dimensions stored on
the model!
Of course, processors aren't the only thing that can vary based on the model of
the source image; spec behavior can change in any way you want.
.. _source-groups:
Source Groups
=============
When you run the ``generateimages`` management command, how does ImageKit know
which source images to use with which specs? Obviously, when you define an
ImageSpecField, the source image is being connected to a spec, but what's going
on underneath the hood?
The answer is that, when you define an ImageSpecField, ImageKit automatically
creates and registers an object called a *source group*. Source groups are
responsible for two things:
1. They dispatch signals when a source is saved, and
2. They expose a generator method that enumerates source files.
When these objects are registered (using ``imagekit.register.source_group()``),
their signals will trigger callbacks on the cache file strategies associated
with image specs that use the source. (So, for example, you can chose to
generate a file every time the source image changes.) In addition, the generator
method is used (indirectly) to create the list of files to generate with the
``generateimages`` management command.
Currently, there is only one source group class bundled with ImageKit—the one
used by ImageSpecFields. This source group
(``imagekit.specs.sourcegroups.ImageFieldSourceGroup``) represents an ImageField
on every instance of a particular model. In terms of the above description, the
instance ``ImageFieldSourceGroup(Profile, 'avatar')`` 1) dispatches a signal
every time the image in Profile's avatar ImageField changes, and 2) exposes a
generator method that iterates over every Profile's "avatar" image.
Chances are, this is the only source group you will ever need to use, however,
ImageKit lets you define and register custom source groups easily. This may be
useful, for example, if you're using the template tags "generateimage" and
"thumbnail" and the optimistic cache file strategy. Again, the purpose is
to tell ImageKit which specs are used with which sources (so the
"generateimages" management command can generate those files) and when the
source image has been created or changed (so that the strategy has the
opportunity to act on it).
A simple example of a custom source group class is as follows:
.. code-block:: python
import glob
import os
class JpegsInADirectory(object):
def __init__(self, dir):
self.dir = dir
def files(self):
os.chdir(self.dir)
for name in glob.glob('*.jpg'):
yield open(name, 'rb')
Instances of this class could then be registered with one or more spec id:
.. code-block:: python
from imagekit import register
register.source_group('myapp:profile:avatar_thumbnail', JpegsInADirectory('/path/to/some/pics'))
Running the "generateimages" management command would now cause thumbnails to be
generated (using the "myapp:profile:avatar_thumbnail" spec) for each of the
JPEGs in `/path/to/some/pics`.
Note that, since this source group doesnt send the `source_saved` signal, the
corresponding cache file strategy callbacks would not be called for them.
django-imagekit-4.0.2/docs/caching.rst 0000664 0000000 0000000 00000023410 13204511077 0017625 0 ustar 00root root 0000000 0000000 Caching
*******
Default Backend Workflow
================
``ImageSpec``
-------------
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.
``ImageCacheFile``
------------------
However, an image spec by itself would be vastly inefficient. Every time an
an image was accessed in some way, it would have be regenerated and saved.
Most of the time, you want to re-use a previously generated image, based on the
input image and spec, instead of generating a new one. That's where
``ImageCacheFile`` comes in. ``ImageCacheFile`` is a File-like object that
wraps an image generator. They look and feel just like regular file
objects, but they've got a little trick up their sleeve: they represent files
that may not actually exist!
Cache File Strategy
-------------------
Each ``ImageCacheFile`` has a cache file strategy, which abstracts away when
image is actually generated. It can implement the following three methods:
* ``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_saved`` - called when the source of a spec is saved
The default strategy only defines the first two of these, as follows:
.. code-block:: python
class JustInTime(object):
def on_content_required(self, file):
file.generate()
def on_existence_required(self, file):
file.generate()
Cache File Backend
------------------
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``
If file doesn't exist, generates it immediately and synchronously
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
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
----------------------------------
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``.
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`.
Deferring Image Generation
--------------------------
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:
1) install `celery`__ (or `django-celery`__ if you are bound to Celery<3.1)
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 %}
{% else %}
{% 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'
.. 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()
__ https://pypi.python.org/pypi/django-celery
__ http://www.celeryproject.org
Removing Safeguards
-------------------
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.
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.
To use this cache file strategy for all specs, set the
``IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY`` in your settings:
.. code-block:: python
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = 'imagekit.cachefiles.strategies.Optimistic'
If you have specs that :ref:`change based on attributes of the source
`, 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.
django-imagekit-4.0.2/docs/conf.py 0000664 0000000 0000000 00000020207 13204511077 0016777 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
#
# ImageKit documentation build configuration file, created by
# sphinx-quickstart on Sun Sep 25 17:05:55 2011.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import re, sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
sys.path.append(os.path.abspath('_themes'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'ImageKit'
copyright = u'2011, Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'
pkgmeta = {}
execfile(os.path.join(os.path.dirname(__file__), '..', 'imagekit',
'pkgmeta.py'), pkgmeta)
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = re.match('\d+\.\d+', pkgmeta['__version__']).group()
# The full version, including alpha/beta/rc tags.
release = pkgmeta['__version__']
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'kr'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
html_show_copyright = False
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'ImageKitdoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'ImageKit.tex', u'ImageKit Documentation',
u'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett \\& Matthew Tretter', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'imagekit', u'ImageKit Documentation',
[u'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'ImageKit', u'ImageKit Documentation', u'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter',
'ImageKit', 'One line description of project.', 'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
autoclass_content = 'both'
django-imagekit-4.0.2/docs/configuration.rst 0000664 0000000 0000000 00000004502 13204511077 0021101 0 ustar 00root root 0000000 0000000 .. _settings:
Configuration
=============
Settings
--------
.. currentmodule:: django.conf.settings
.. attribute:: IMAGEKIT_CACHEFILE_DIR
:default: ``'CACHE/images'``
The directory to which image files will be cached.
.. attribute:: IMAGEKIT_DEFAULT_FILE_STORAGE
:default: ``None``
The qualified class name of a Django storage backend to use to save the
cached images. If no value is provided for ``IMAGEKIT_DEFAULT_FILE_STORAGE``,
and none is specified by the spec definition, `your default file storage`__
will be used.
.. attribute:: IMAGEKIT_DEFAULT_CACHEFILE_BACKEND
:default: ``'imagekit.cachefiles.backends.Simple'``
Specifies the class that will be used to validate cached image files.
.. attribute:: IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY
:default: ``'imagekit.cachefiles.strategies.JustInTime'``
The class responsible for specifying how and when cache files are
generated.
.. attribute:: IMAGEKIT_CACHE_BACKEND
:default: ``'default'``
The Django cache backend alias to retrieve the shared cache instance defined
in your settings, as described in the `Django cache section`_.
The cache is then used to store information like the state of cached
images (i.e. validated or not).
.. _`Django cache section`: https://docs.djangoproject.com/en/1.8/topics/cache/#accessing-the-cache
.. attribute:: IMAGEKIT_CACHE_TIMEOUT
:default: ``None``
Use when you need to override the timeout used to cache file state.
By default it is "cache forever".
It's highly recommended that you use a very high timeout.
.. attribute:: IMAGEKIT_CACHE_PREFIX
:default: ``'imagekit:'``
A cache prefix to be used when values are stored in ``IMAGEKIT_CACHE_BACKEND``
.. attribute:: IMAGEKIT_CACHEFILE_NAMER
:default: ``'imagekit.cachefiles.namers.hash'``
A function responsible for generating file names for non-spec cache files.
.. attribute:: IMAGEKIT_SPEC_CACHEFILE_NAMER
:default: ``'imagekit.cachefiles.namers.source_name_as_path'``
A function responsible for generating file names for cache files that
correspond to image specs. Since you will likely want to base the name of
your cache files on the name of the source, this extra setting is provided.
__ https://docs.djangoproject.com/en/dev/ref/settings/#default-file-storage
django-imagekit-4.0.2/docs/index.rst 0000664 0000000 0000000 00000000415 13204511077 0017340 0 ustar 00root root 0000000 0000000 .. include:: ../README.rst
Authors
=======
.. include:: ../AUTHORS
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. toctree::
:glob:
:maxdepth: 2
configuration
advanced_usage
caching
upgrading
django-imagekit-4.0.2/docs/make.bat 0000664 0000000 0000000 00000011753 13204511077 0017113 0 ustar 00root root 0000000 0000000 @ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^` where ^ is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ImageKit.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ImageKit.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end
django-imagekit-4.0.2/docs/upgrading.rst 0000664 0000000 0000000 00000010502 13204511077 0020207 0 ustar 00root root 0000000 0000000 Upgrading from 2.x
==================
ImageKit 3.0 introduces new APIs and tools that augment, improve, and in some
cases entirely replace old IK workflows. Below, you'll find some useful guides
for migrating your ImageKit 2.0 apps over to the shiny new IK3.
Model Specs
-----------
IK3 is chock full of new features and better tools for even the most
sophisticated use cases. Despite this, not too much has changed when it
comes to the most common of use cases: processing an ``ImageField`` on a model.
In IK2, you may have used an ``ImageSpecField`` on a model to process an
existing ``ImageField``:
.. code-block:: python
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(image_field='avatar',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
In IK3, things look much the same:
.. code-block:: python
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
The major difference is that ``ImageSpecField`` no longer takes an
``image_field`` kwarg. Instead, you define a ``source``.
Image Cache Backends
--------------------
In IK2, you could gain some control over how your cached images were generated
by providing an ``image_cache_backend``:
.. code-block:: python
class Photo(models.Model):
...
thumbnail = ImageSpecField(..., image_cache_backend=MyImageCacheBackend())
This gave you great control over *how* your images are generated and stored,
but it could be difficult to control *when* they were generated and stored.
IK3 retains the image cache backend concept (now called cache file backends),
but separates the 'when' control out to cache file strategies:
.. code-block:: python
class Photo(models.Model):
...
thumbnail = ImageSpecField(...,
cachefile_backend=MyCacheFileBackend(),
cachefile_strategy=MyCacheFileStrategy())
If you are using the IK2 default image cache backend setting:
.. code-block:: python
IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND = 'path.to.MyImageCacheBackend'
IK3 provides analogous settings for cache file backends and strategies:
.. code-block:: python
IMAGEKIT_DEFAULT_CACHEFILE_BACKEND = 'path.to.MyCacheFileBackend'
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = 'path.to.MyCacheFileStrategy'
See the documentation on `cache file backends`_ and `cache file strategies`_
for more details.
.. _`cache file backends`:
.. _`cache file strategies`:
Conditional model ``processors``
--------------------------------
In IK2, an ``ImageSpecField`` could take a ``processors`` callable instead of
an iterable, which allowed processing decisions to made based on other
properties of the model. IK3 does away with this feature for consistency's sake
(if one kwarg could be callable, why not all?), but provides a much more robust
solution: the custom ``spec``. See the `advanced usage`_ documentation for more.
.. _`advanced usage`:
Conditonal ``cache_to`` file names
----------------------------------
IK2 provided a means of specifying custom cache file names for your
image specs by passing a ``cache_to`` callable to an ``ImageSpecField``.
IK3 does away with this feature, again, for consistency.
There is a way to achieve custom file names by overriding your spec's
``cachefile_name``, but it is not recommended, as the spec's default
behavior is to hash the combination of ``source``, ``processors``, ``format``,
and other spec options to ensure that changes to the spec always result in
unique file names. See the documentation on `specs`_ for more.
.. _`specs`:
Processors have moved to PILKit
-------------------------------
Processors have moved to a separate project: `PILKit`_. You should not have to
make any changes to an IK2 project to use PILKit--it should be installed with
IK3, and importing from ``imagekit.processors`` will still work.
.. _`PILKit`: https://github.com/matthewwithanm/pilkit
django-imagekit-4.0.2/imagekit/ 0000775 0000000 0000000 00000000000 13204511077 0016341 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/__init__.py 0000664 0000000 0000000 00000000240 13204511077 0020446 0 ustar 00root root 0000000 0000000 # flake8: noqa
from . import conf
from . import generatorlibrary
from .specs import ImageSpec
from .pkgmeta import *
from .registry import register, unregister
django-imagekit-4.0.2/imagekit/admin.py 0000664 0000000 0000000 00000002464 13204511077 0020011 0 ustar 00root root 0000000 0000000 from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
class AdminThumbnail(object):
"""
A convenience utility for adding thumbnails to Django's admin change list.
"""
short_description = _('Thumbnail')
allow_tags = True
def __init__(self, image_field, template=None):
"""
:param image_field: The name of the ImageField or ImageSpecField on the
model to use for the thumbnail.
:param template: The template with which to render the thumbnail
"""
self.image_field = image_field
self.template = template
def __call__(self, obj):
if callable(self.image_field):
thumbnail = self.image_field(obj)
else:
try:
thumbnail = getattr(obj, self.image_field)
except AttributeError:
raise Exception('The property %s is not defined on %s.' %
(self.image_field, obj.__class__.__name__))
original_image = getattr(thumbnail, 'source', None) or thumbnail
template = self.template or 'imagekit/admin/thumbnail.html'
return render_to_string(template, {
'model': obj,
'thumbnail': thumbnail,
'original_image': original_image,
})
django-imagekit-4.0.2/imagekit/cachefiles/ 0000775 0000000 0000000 00000000000 13204511077 0020427 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/cachefiles/__init__.py 0000664 0000000 0000000 00000014644 13204511077 0022551 0 ustar 00root root 0000000 0000000 from copy import copy
from django.conf import settings
from django.core.files import File
from django.core.files.images import ImageFile
from django.utils.functional import SimpleLazyObject
from django.utils.encoding import smart_str
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 getattr(self, '_file', None) is 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 getattr(self, '_file', None) is 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 getattr(self, '_file', None) is 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 __bool__(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)
try:
check = self.cachefile_strategy.should_verify_existence(self)
except AttributeError:
# All synchronous backends should have created the file as part of
# `existence_required` if they wanted to.
check = getattr(self.cachefile_backend, 'is_async', False)
return self.cachefile_backend.exists(self) if check else True
def __getstate__(self):
state = copy(self.__dict__)
# file is hidden link to "file" attribute
state.pop('_file', None)
return state
def __nonzero__(self):
# Python 2 compatibility
return self.__bool__()
def __repr__(self):
return smart_str("<%s: %s>" % (
self.__class__.__name__, self if self.name else "None")
)
class LazyImageCacheFile(SimpleLazyObject):
def __init__(self, generator_id, *args, **kwargs):
def setup():
generator = generator_registry.get(generator_id, *args, **kwargs)
return ImageCacheFile(generator)
super(LazyImageCacheFile, self).__init__(setup)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, str(self) or 'None')
django-imagekit-4.0.2/imagekit/cachefiles/backends.py 0000664 0000000 0000000 00000014163 13204511077 0022560 0 ustar 00root root 0000000 0000000 from ..utils import get_singleton, get_cache, sanitize_cache_key
import warnings
from copy import copy
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
class CacheFileState(object):
EXISTS = 'exists'
GENERATING = 'generating'
DOES_NOT_EXIST = 'does_not_exist'
def get_default_cachefile_backend():
"""
Get the default file backend.
"""
from django.conf import settings
return get_singleton(settings.IMAGEKIT_DEFAULT_CACHEFILE_BACKEND,
'file backend')
class InvalidFileBackendError(ImproperlyConfigured):
pass
class AbstractCacheFileBackend(object):
"""
An abstract cache file backend. This isn't used by any internal classes and
is included simply to illustrate the minimum interface of a cache file
backend for users who wish to implement their own.
"""
def generate(self, file, force=False):
raise NotImplementedError
def exists(self, file):
raise NotImplementedError
class CachedFileBackend(object):
existence_check_timeout = 5
"""
The number of seconds to wait before rechecking to see if the file exists.
If the image is found to exist, that information will be cached using the
timeout specified in your CACHES setting (which should be very high).
However, when the file does not exist, you probably want to check again
in a relatively short amount of time. This attribute allows you to do that.
"""
@property
def cache(self):
if not getattr(self, '_cache', None):
self._cache = get_cache()
return self._cache
def get_key(self, file):
from django.conf import settings
return sanitize_cache_key('%s%s-state' %
(settings.IMAGEKIT_CACHE_PREFIX, file.name))
def get_state(self, file, check_if_unknown=True):
key = self.get_key(file)
state = self.cache.get(key)
if state is None and check_if_unknown:
exists = self._exists(file)
state = CacheFileState.EXISTS if exists else CacheFileState.DOES_NOT_EXIST
self.set_state(file, state)
return state
def set_state(self, file, state):
key = self.get_key(file)
if state == CacheFileState.DOES_NOT_EXIST:
self.cache.set(key, state, self.existence_check_timeout)
else:
self.cache.set(key, state, settings.IMAGEKIT_CACHE_TIMEOUT)
def __getstate__(self):
state = copy(self.__dict__)
# Don't include the cache when pickling. It'll be reconstituted based
# on the settings.
state.pop('_cache', None)
return state
def exists(self, file):
return self.get_state(file) == CacheFileState.EXISTS
def generate(self, file, force=False):
raise NotImplementedError
def generate_now(self, file, force=False):
if force or self.get_state(file) not in (CacheFileState.GENERATING, CacheFileState.EXISTS):
self.set_state(file, CacheFileState.GENERATING)
file._generate()
self.set_state(file, CacheFileState.EXISTS)
file.close()
class Simple(CachedFileBackend):
"""
The most basic file backend. The storage is consulted to see if the file
exists. Files are generated synchronously.
"""
def generate(self, file, force=False):
self.generate_now(file, force=force)
def _exists(self, file):
return bool(getattr(file, '_file', None)
or file.storage.exists(file.name))
def _generate_file(backend, file, force=False):
backend.generate_now(file, force=force)
class BaseAsync(Simple):
"""
Base class for cache file backends that generate files asynchronously.
"""
is_async = True
def generate(self, file, force=False):
# Schedule the file for generation, unless we know for sure we don't
# need to. If an already-generated file sneaks through, that's okay;
# ``generate_now`` will catch it. We just want to make sure we don't
# schedule anything we know is unnecessary--but we also don't want to
# force a costly existence check.
state = self.get_state(file, check_if_unknown=False)
if state not in (CacheFileState.GENERATING, CacheFileState.EXISTS):
self.schedule_generation(file, force=force)
def schedule_generation(self, file, force=False):
# overwrite this to have the file generated in the background,
# e. g. in a worker queue.
raise NotImplementedError
try:
from celery import task
except ImportError:
pass
else:
_celery_task = task(ignore_result=True, serializer='pickle')(_generate_file)
class Celery(BaseAsync):
"""
A backend that uses Celery to generate the images.
"""
def __init__(self, *args, **kwargs):
try:
import celery # noqa
except ImportError:
raise ImproperlyConfigured('You must install celery to use'
' imagekit.cachefiles.backends.Celery.')
super(Celery, self).__init__(*args, **kwargs)
def schedule_generation(self, file, force=False):
_celery_task.delay(self, file, force=force)
# Stub class to preserve backwards compatibility and issue a warning
class Async(Celery):
def __init__(self, *args, **kwargs):
message = '{path}.Async is deprecated. Use {path}.Celery instead.'
warnings.warn(message.format(path=__name__), DeprecationWarning)
super(Async, self).__init__(*args, **kwargs)
try:
from django_rq import job
except ImportError:
pass
else:
_rq_job = job('default', result_ttl=0)(_generate_file)
class RQ(BaseAsync):
"""
A backend that uses RQ to generate the images.
"""
def __init__(self, *args, **kwargs):
try:
import django_rq # noqa
except ImportError:
raise ImproperlyConfigured('You must install django-rq to use'
' imagekit.cachefiles.backends.RQ.')
super(RQ, self).__init__(*args, **kwargs)
def schedule_generation(self, file, force=False):
_rq_job.delay(self, file, force=force)
django-imagekit-4.0.2/imagekit/cachefiles/namers.py 0000664 0000000 0000000 00000006113 13204511077 0022267 0 ustar 00root root 0000000 0000000 """
Functions responsible for returning filenames for the given image generator.
Users are free to define their own functions; these are just some some sensible
choices.
"""
from django.conf import settings
import os
from ..utils import format_to_extension, suggest_extension
def source_name_as_path(generator):
"""
A namer that, given the following source file name::
photos/thumbnails/bulldog.jpg
will generate a name like this::
/path/to/generated/images/photos/thumbnails/bulldog/5ff3233527c5ac3e4b596343b440ff67.jpg
where "/path/to/generated/images/" is the value specified by the
``IMAGEKIT_CACHEFILE_DIR`` setting.
"""
source_filename = getattr(generator.source, 'name', None)
if source_filename is None or os.path.isabs(source_filename):
# Generally, we put the file right in the cache file directory.
dir = settings.IMAGEKIT_CACHEFILE_DIR
else:
# For source files with relative names (like Django media files),
# use the source's name to create the new filename.
dir = os.path.join(settings.IMAGEKIT_CACHEFILE_DIR,
os.path.splitext(source_filename)[0])
ext = suggest_extension(source_filename or '', generator.format)
return os.path.normpath(os.path.join(dir,
'%s%s' % (generator.get_hash(), ext)))
def source_name_dot_hash(generator):
"""
A namer that, given the following source file name::
photos/thumbnails/bulldog.jpg
will generate a name like this::
/path/to/generated/images/photos/thumbnails/bulldog.5ff3233527c5.jpg
where "/path/to/generated/images/" is the value specified by the
``IMAGEKIT_CACHEFILE_DIR`` setting.
"""
source_filename = getattr(generator.source, 'name', None)
if source_filename is None or os.path.isabs(source_filename):
# Generally, we put the file right in the cache file directory.
dir = settings.IMAGEKIT_CACHEFILE_DIR
else:
# For source files with relative names (like Django media files),
# use the source's name to create the new filename.
dir = os.path.join(settings.IMAGEKIT_CACHEFILE_DIR,
os.path.dirname(source_filename))
ext = suggest_extension(source_filename or '', generator.format)
basename = os.path.basename(source_filename)
return os.path.normpath(os.path.join(dir, '%s.%s%s' % (
os.path.splitext(basename)[0], generator.get_hash()[:12], ext)))
def hash(generator):
"""
A namer that, given the following source file name::
photos/thumbnails/bulldog.jpg
will generate a name like this::
/path/to/generated/images/5ff3233527c5ac3e4b596343b440ff67.jpg
where "/path/to/generated/images/" is the value specified by the
``IMAGEKIT_CACHEFILE_DIR`` setting.
"""
format = getattr(generator, 'format', None)
ext = format_to_extension(format) if format else ''
return os.path.normpath(os.path.join(settings.IMAGEKIT_CACHEFILE_DIR,
'%s%s' % (generator.get_hash(), ext)))
django-imagekit-4.0.2/imagekit/cachefiles/strategies.py 0000664 0000000 0000000 00000002231 13204511077 0023151 0 ustar 00root root 0000000 0000000 import six
from django.utils.functional import LazyObject
from ..lib import force_text
from ..utils import get_singleton
class JustInTime(object):
"""
A strategy that ensures the file exists right before it's needed.
"""
def on_existence_required(self, file):
file.generate()
def on_content_required(self, file):
file.generate()
class Optimistic(object):
"""
A strategy that acts immediately when the source file changes and assumes
that the cache files will not be removed (i.e. it doesn't ensure the
cache file exists when it's accessed).
"""
def on_source_saved(self, file):
file.generate()
def should_verify_existence(self, file):
return False
class DictStrategy(object):
def __init__(self, callbacks):
for k, v in callbacks.items():
setattr(self, k, v)
def load_strategy(strategy):
if isinstance(strategy, six.string_types):
strategy = get_singleton(strategy, 'cache file strategy')
elif isinstance(strategy, dict):
strategy = DictStrategy(strategy)
elif callable(strategy):
strategy = strategy()
return strategy
django-imagekit-4.0.2/imagekit/compat.py 0000664 0000000 0000000 00000014461 13204511077 0020204 0 ustar 00root root 0000000 0000000 # flake8: noqa
"""
This module contains code from django.template.base
(sha 90d3af380e8efec0301dd91600c6686232de3943). Bundling this code allows us to
support older versions of Django that did not contain it (< 1.4).
Copyright (c) Django Software Foundation and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Django nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
from django.template import TemplateSyntaxError
import re
# Regex for token keyword arguments
kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
def token_kwargs(bits, parser, support_legacy=False):
"""
A utility method for parsing token keyword arguments.
:param bits: A list containing remainder of the token (split by spaces)
that is to be checked for arguments. Valid arguments will be removed
from this list.
:param support_legacy: If set to true ``True``, the legacy format
``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
format is allowed.
:returns: A dictionary of the arguments retrieved from the ``bits`` token
list.
There is no requirement for all remaining token ``bits`` to be keyword
arguments, so the dictionary will be returned as soon as an invalid
argument format is reached.
"""
if not bits:
return {}
match = kwarg_re.match(bits[0])
kwarg_format = match and match.group(1)
if not kwarg_format:
if not support_legacy:
return {}
if len(bits) < 3 or bits[1] != 'as':
return {}
kwargs = {}
while bits:
if kwarg_format:
match = kwarg_re.match(bits[0])
if not match or not match.group(1):
return kwargs
key, value = match.groups()
del bits[:1]
else:
if len(bits) < 3 or bits[1] != 'as':
return kwargs
key, value = bits[2], bits[0]
del bits[:3]
kwargs[key] = parser.compile_filter(value)
if bits and not kwarg_format:
if bits[0] != 'and':
return kwargs
del bits[:1]
return kwargs
def parse_bits(parser, bits, params, varargs, varkw, defaults,
takes_context, name):
"""
Parses bits for template tag helpers (simple_tag, include_tag and
assignment_tag), in particular by detecting syntax errors and by
extracting positional and keyword arguments.
"""
if takes_context:
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError(
"'%s' is decorated with takes_context=True so it must "
"have a first argument of 'context'" % name)
args = []
kwargs = {}
unhandled_params = list(params)
for bit in bits:
# First we try to extract a potential kwarg from the bit
kwarg = token_kwargs([bit], parser)
if kwarg:
# The kwarg was successfully extracted
param, value = list(kwarg.items())[0]
if param not in params and varkw is None:
# An unexpected keyword argument was supplied
raise TemplateSyntaxError(
"'%s' received unexpected keyword argument '%s'" %
(name, param))
elif param in kwargs:
# The keyword argument has already been supplied once
raise TemplateSyntaxError(
"'%s' received multiple values for keyword argument '%s'" %
(name, param))
else:
# All good, record the keyword argument
kwargs[str(param)] = value
if param in unhandled_params:
# If using the keyword syntax for a positional arg, then
# consume it.
unhandled_params.remove(param)
else:
if kwargs:
raise TemplateSyntaxError(
"'%s' received some positional argument(s) after some "
"keyword argument(s)" % name)
else:
# Record the positional argument
args.append(parser.compile_filter(bit))
try:
# Consume from the list of expected positional arguments
unhandled_params.pop(0)
except IndexError:
if varargs is None:
raise TemplateSyntaxError(
"'%s' received too many positional arguments" %
name)
if defaults is not None:
# Consider the last n params handled, where n is the
# number of defaults.
unhandled_params = unhandled_params[:-len(defaults)]
if unhandled_params:
# Some positional arguments were not supplied
raise TemplateSyntaxError(
"'%s' did not receive value(s) for the argument(s): %s" %
(name, ", ".join(["'%s'" % p for p in unhandled_params])))
return args, kwargs
django-imagekit-4.0.2/imagekit/conf.py 0000664 0000000 0000000 00000002556 13204511077 0017650 0 ustar 00root root 0000000 0000000 from appconf import AppConf
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
class ImageKitConf(AppConf):
CACHEFILE_NAMER = 'imagekit.cachefiles.namers.hash'
SPEC_CACHEFILE_NAMER = 'imagekit.cachefiles.namers.source_name_as_path'
CACHEFILE_DIR = 'CACHE/images'
DEFAULT_CACHEFILE_BACKEND = 'imagekit.cachefiles.backends.Simple'
DEFAULT_CACHEFILE_STRATEGY = 'imagekit.cachefiles.strategies.JustInTime'
DEFAULT_FILE_STORAGE = None
CACHE_BACKEND = None
CACHE_PREFIX = 'imagekit:'
CACHE_TIMEOUT = None
USE_MEMCACHED_SAFE_CACHE_KEY = True
def configure_cache_backend(self, value):
if value is None:
from django.core.cache import DEFAULT_CACHE_ALIAS
return DEFAULT_CACHE_ALIAS
if value not in settings.CACHES:
raise ImproperlyConfigured("{0} is not present in settings.CACHES".format(value))
return value
def configure_cache_timeout(self, value):
if value is None and settings.DEBUG:
# If value is not configured and is DEBUG set it to 5 minutes
return 300
# Otherwise leave it as is. If it is None then valies will never expire
return value
def configure_default_file_storage(self, value):
if value is None:
value = settings.DEFAULT_FILE_STORAGE
return value
django-imagekit-4.0.2/imagekit/exceptions.py 0000664 0000000 0000000 00000000550 13204511077 0021074 0 ustar 00root root 0000000 0000000 from pilkit.exceptions import UnknownExtension, UnknownFormat
class AlreadyRegistered(Exception):
pass
class NotRegistered(Exception):
pass
class MissingGeneratorId(Exception):
pass
class MissingSource(ValueError):
pass
# Aliases for backwards compatibility
UnknownExtensionError = UnknownExtension
UnknownFormatError = UnknownFormat
django-imagekit-4.0.2/imagekit/files.py 0000664 0000000 0000000 00000006134 13204511077 0020021 0 ustar 00root root 0000000 0000000 from __future__ import unicode_literals
import os
from django.core.files.base import File, ContentFile
from django.utils.encoding import smart_str
from .lib import smart_text
from .utils import format_to_mimetype, extension_to_mimetype
class BaseIKFile(File):
"""
This class contains all of the methods we need from
django.db.models.fields.files.FieldFile, but with the model stuff ripped
out. It's only extended by one class, but we keep it separate for
organizational reasons.
"""
def __init__(self, storage):
self.storage = storage
def _require_file(self):
if not self:
raise ValueError()
def _get_file(self):
self._require_file()
if not hasattr(self, '_file') or self._file is None:
self._file = self.storage.open(self.name, 'rb')
return self._file
def _set_file(self, file):
self._file = file
def _del_file(self):
del self._file
file = property(_get_file, _set_file, _del_file)
def _get_path(self):
self._require_file()
return self.storage.path(self.name)
path = property(_get_path)
def _get_url(self):
self._require_file()
return self.storage.url(self.name)
url = property(_get_url)
def _get_size(self):
self._require_file()
if not self._committed:
return self.file.size
return self.storage.size(self.name)
size = property(_get_size)
def open(self, mode='rb'):
self._require_file()
try:
self.file.open(mode)
except ValueError:
# if the underlaying file can't be reopened
# then we will use the storage to try to open it again
if self.file.closed:
# clear cached file instance
del self.file
# Because file is a property we can acces it after
# we deleted it
return self.file.open(mode)
raise
def _get_closed(self):
file = getattr(self, '_file', None)
return file is None or file.closed
closed = property(_get_closed)
def close(self):
file = getattr(self, '_file', None)
if file is not None:
file.close()
class IKContentFile(ContentFile):
"""
Wraps a ContentFile in a file-like object with a filename and a
content_type. A PIL image format can be optionally be provided as a content
type hint.
"""
def __init__(self, filename, content, format=None):
self.file = ContentFile(content)
self.file.name = filename
mimetype = getattr(self.file, 'content_type', None)
if format and not mimetype:
mimetype = format_to_mimetype(format)
if not mimetype:
ext = os.path.splitext(filename or '')[1]
mimetype = extension_to_mimetype(ext)
self.file.content_type = mimetype
@property
def name(self):
return self.file.name
def __str__(self):
return smart_str(self.file.name or '')
def __unicode__(self):
# Python 2
return smart_text(self.file.name or '')
django-imagekit-4.0.2/imagekit/forms/ 0000775 0000000 0000000 00000000000 13204511077 0017467 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/forms/__init__.py 0000664 0000000 0000000 00000000070 13204511077 0021575 0 ustar 00root root 0000000 0000000 # flake8: noqa
from .fields import ProcessedImageField
django-imagekit-4.0.2/imagekit/forms/fields.py 0000664 0000000 0000000 00000002444 13204511077 0021313 0 ustar 00root root 0000000 0000000 from django.forms import ImageField
from ..specs import SpecHost
from ..utils import generate
class ProcessedImageField(ImageField, SpecHost):
def __init__(self, processors=None, format=None, options=None,
autoconvert=True, spec_id=None, spec=None, *args, **kwargs):
if spec_id is None:
# Unlike model fields, form fields are never told their field name.
# (Model fields are done so via `contribute_to_class()`.) Therefore
# we can't really generate a good spec id automatically.
raise TypeError('You must provide a spec_id')
SpecHost.__init__(self, processors=processors, format=format,
options=options, autoconvert=autoconvert, spec=spec,
spec_id=spec_id)
super(ProcessedImageField, self).__init__(*args, **kwargs)
def clean(self, data, initial=None):
data = super(ProcessedImageField, self).clean(data, initial)
if data and data != initial:
spec = self.get_spec(source=data)
f = generate(spec)
# Name is required in Django 1.4. When we drop support for it
# then we can dirrectly return the result from `generate(spec)`
f.name = data.name
return f
return data
django-imagekit-4.0.2/imagekit/generatorlibrary.py 0000664 0000000 0000000 00000000762 13204511077 0022273 0 ustar 00root root 0000000 0000000 from .registry import register
from .processors import Thumbnail as ThumbnailProcessor
from .specs import ImageSpec
class Thumbnail(ImageSpec):
def __init__(self, width=None, height=None, anchor=None, crop=None, upscale=None, **kwargs):
self.processors = [ThumbnailProcessor(width, height, anchor=anchor,
crop=crop, upscale=upscale)]
super(Thumbnail, self).__init__(**kwargs)
register.generator('imagekit:thumbnail', Thumbnail)
django-imagekit-4.0.2/imagekit/hashers.py 0000664 0000000 0000000 00000001456 13204511077 0020356 0 ustar 00root root 0000000 0000000 from copy import copy
from hashlib import md5
from pickle import MARK, DICT
try:
from pickle import _Pickler
except ImportError:
# Python 2 compatible
from pickle import Pickler as _Pickler
from .lib import StringIO
class CanonicalizingPickler(_Pickler):
dispatch = copy(_Pickler.dispatch)
def save_set(self, obj):
rv = obj.__reduce_ex__(0)
rv = (rv[0], (sorted(rv[1][0]),), rv[2])
self.save_reduce(obj=obj, *rv)
dispatch[set] = save_set
def save_dict(self, obj):
write = self.write
write(MARK + DICT)
self.memoize(obj)
self._batch_setitems(sorted(obj.items()))
dispatch[dict] = save_dict
def pickle(obj):
file = StringIO()
CanonicalizingPickler(file, 0).dump(obj)
return md5(file.getvalue()).hexdigest()
django-imagekit-4.0.2/imagekit/lib.py 0000664 0000000 0000000 00000003364 13204511077 0017467 0 ustar 00root root 0000000 0000000 # flake8: noqa
# Required PIL classes may or may not be available from the root namespace
# depending on the installation method used.
try:
from PIL import Image, ImageColor, ImageChops, ImageEnhance, ImageFile, \
ImageFilter, ImageDraw, ImageStat
except ImportError:
try:
import Image
import ImageColor
import ImageChops
import ImageEnhance
import ImageFile
import ImageFilter
import ImageDraw
import ImageStat
except ImportError:
raise ImportError('ImageKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')
try:
from io import BytesIO as StringIO
except:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
try:
from logging import NullHandler
except ImportError:
from logging import Handler
class NullHandler(Handler):
def emit(self, record):
pass
# Try to import `force_text` available from Django 1.5
# This function will replace `unicode` used in the code
# If Django version is under 1.5 then use `force_unicde`
# It is used for compatibility between Python 2 and Python 3
try:
from django.utils.encoding import force_text, force_bytes, smart_text
except ImportError:
# Django < 1.5
from django.utils.encoding import (force_unicode as force_text,
smart_str as force_bytes,
smart_unicode as smart_text)
__all__ = ['Image', 'ImageColor', 'ImageChops', 'ImageEnhance', 'ImageFile',
'ImageFilter', 'ImageDraw', 'ImageStat', 'StringIO', 'NullHandler',
'force_text', 'force_bytes', 'smart_text']
django-imagekit-4.0.2/imagekit/management/ 0000775 0000000 0000000 00000000000 13204511077 0020455 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/management/__init__.py 0000664 0000000 0000000 00000000000 13204511077 0022554 0 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/management/commands/ 0000775 0000000 0000000 00000000000 13204511077 0022256 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/management/commands/__init__.py 0000664 0000000 0000000 00000000000 13204511077 0024355 0 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/management/commands/generateimages.py 0000664 0000000 0000000 00000004331 13204511077 0025611 0 ustar 00root root 0000000 0000000 from django.core.management.base import BaseCommand
import re
from ...registry import generator_registry, cachefile_registry
from ...exceptions import MissingSource
class Command(BaseCommand):
help = ("""Generate files for the specified image generators (or all of them if
none was provided). Simple, glob-like wildcards are allowed, with *
matching all characters within a segment, and ** matching across
segments. (Segments are separated with colons.) So, for example,
"a:*:c" will match "a:b:c", but not "a:b:x:c", whereas "a:**:c" will
match both. Subsegments are always matched, so "a" will match "a" as
well as "a:b" and "a:b:c".""")
args = '[generator_ids]'
def add_arguments(self, parser):
parser.add_argument('generator_id', nargs='*', help=':: for model specs')
def handle(self, *args, **options):
generators = generator_registry.get_ids()
generator_ids = options['generator_id'] if 'generator_id' in options else args
if generator_ids:
patterns = self.compile_patterns(generator_ids)
generators = (id for id in generators if any(p.match(id) for p in patterns))
for generator_id in generators:
self.stdout.write('Validating generator: %s\n' % generator_id)
for image_file in cachefile_registry.get(generator_id):
if image_file.name:
self.stdout.write(' %s\n' % image_file.name)
try:
image_file.generate()
except MissingSource as err:
self.stdout.write('\t No source associated with\n')
except Exception as err:
self.stdout.write('\tFailed %s\n' % (err))
def compile_patterns(self, generator_ids):
return [self.compile_pattern(id) for id in generator_ids]
def compile_pattern(self, generator_id):
parts = re.split(r'(\*{1,2})', generator_id)
pattern = ''
for part in parts:
if part == '*':
pattern += '[^:]*'
elif part == '**':
pattern += '.*'
else:
pattern += re.escape(part)
return re.compile('^%s(:.*)?$' % pattern)
django-imagekit-4.0.2/imagekit/models/ 0000775 0000000 0000000 00000000000 13204511077 0017624 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/models/__init__.py 0000664 0000000 0000000 00000000134 13204511077 0021733 0 ustar 00root root 0000000 0000000 # flake8: noqa
from .. import conf
from .fields import ImageSpecField, ProcessedImageField
django-imagekit-4.0.2/imagekit/models/fields/ 0000775 0000000 0000000 00000000000 13204511077 0021072 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/models/fields/__init__.py 0000664 0000000 0000000 00000012216 13204511077 0023205 0 ustar 00root root 0000000 0000000 from __future__ import unicode_literals
from django.conf import settings
from django.db import models
from django.db.models.signals import class_prepared
from .files import ProcessedImageFieldFile
from .utils import ImageSpecFileDescriptor
from ...specs import SpecHost
from ...specs.sourcegroups import ImageFieldSourceGroup
from ...registry import register
class SpecHostField(SpecHost):
def _set_spec_id(self, cls, name):
spec_id = getattr(self, 'spec_id', None)
# Generate a spec_id to register the spec with. The default spec id is
# ":_"
if not spec_id:
spec_id = ('%s:%s:%s' % (cls._meta.app_label,
cls._meta.object_name, name)).lower()
# Register the spec with the id. This allows specs to be overridden
# later, from outside of the model definition.
super(SpecHostField, self).set_spec_id(spec_id)
class ImageSpecField(SpecHostField):
"""
The heart and soul of the ImageKit library, ImageSpecField allows you to add
variants of uploaded images to your models.
"""
def __init__(self, processors=None, format=None, options=None,
source=None, cachefile_storage=None, autoconvert=None,
cachefile_backend=None, cachefile_strategy=None, spec=None,
id=None):
SpecHost.__init__(self, processors=processors, format=format,
options=options, cachefile_storage=cachefile_storage,
autoconvert=autoconvert,
cachefile_backend=cachefile_backend,
cachefile_strategy=cachefile_strategy, spec=spec,
spec_id=id)
# TODO: Allow callable for source. See https://github.com/matthewwithanm/django-imagekit/issues/158#issuecomment-10921664
self.source = source
def contribute_to_class(self, cls, name):
# If the source field name isn't defined, figure it out.
def register_source_group(source):
setattr(cls, name, ImageSpecFileDescriptor(self, name, source))
self._set_spec_id(cls, name)
# Add the model and field as a source for this spec id
register.source_group(self.spec_id, ImageFieldSourceGroup(cls, source))
if self.source:
register_source_group(self.source)
else:
# The source argument is not defined
# Then we need to see if there is only one ImageField in that model
# But we need to do that after full model initialization
def handle_model_preparation(sender, **kwargs):
image_fields = [f.attname for f in cls._meta.fields if
isinstance(f, models.ImageField)]
if len(image_fields) == 0:
raise Exception(
'%s does not define any ImageFields, so your %s'
' ImageSpecField has no image to act on.' %
(cls.__name__, name))
elif len(image_fields) > 1:
raise Exception(
'%s defines multiple ImageFields, but you have not'
' specified a source for your %s ImageSpecField.' %
(cls.__name__, name))
register_source_group(image_fields[0])
class_prepared.connect(handle_model_preparation, sender=cls, weak=False)
class ProcessedImageField(models.ImageField, SpecHostField):
"""
ProcessedImageField is an ImageField that runs processors on the uploaded
image *before* saving it to storage. This is in contrast to specs, which
maintain the original. Useful for coercing fileformats or keeping images
within a reasonable size.
"""
attr_class = ProcessedImageFieldFile
def __init__(self, processors=None, format=None, options=None,
verbose_name=None, name=None, width_field=None, height_field=None,
autoconvert=None, spec=None, spec_id=None, **kwargs):
"""
The ProcessedImageField constructor accepts all of the arguments that
the :class:`django.db.models.ImageField` constructor accepts, as well
as the ``processors``, ``format``, and ``options`` arguments of
:class:`imagekit.models.ImageSpecField`.
"""
# if spec is not provided then autoconvert will be True by default
if spec is None and autoconvert is None:
autoconvert = True
SpecHost.__init__(self, processors=processors, format=format,
options=options, autoconvert=autoconvert, spec=spec,
spec_id=spec_id)
models.ImageField.__init__(self, verbose_name, name, width_field,
height_field, **kwargs)
def contribute_to_class(self, cls, name):
self._set_spec_id(cls, name)
return super(ProcessedImageField, self).contribute_to_class(cls, name)
# If the project does not use south, then we will not try to add introspection
if 'south' in settings.INSTALLED_APPS:
try:
from south.modelsinspector import add_introspection_rules
except ImportError:
pass
else:
add_introspection_rules([], [r'^imagekit\.models\.fields\.ProcessedImageField$'])
django-imagekit-4.0.2/imagekit/models/fields/files.py 0000664 0000000 0000000 00000001007 13204511077 0022544 0 ustar 00root root 0000000 0000000 from django.db.models.fields.files import ImageFieldFile
import os
from ...utils import suggest_extension, generate
class ProcessedImageFieldFile(ImageFieldFile):
def save(self, name, content, save=True):
filename, ext = os.path.splitext(name)
spec = self.field.get_spec(source=content)
ext = suggest_extension(name, spec.format)
new_name = '%s%s' % (filename, ext)
content = generate(spec)
return super(ProcessedImageFieldFile, self).save(new_name, content, save)
django-imagekit-4.0.2/imagekit/models/fields/utils.py 0000664 0000000 0000000 00000001255 13204511077 0022607 0 ustar 00root root 0000000 0000000 from ...cachefiles import ImageCacheFile
class ImageSpecFileDescriptor(object):
def __init__(self, field, attname, source_field_name):
self.attname = attname
self.field = field
self.source_field_name = source_field_name
def __get__(self, instance, owner):
if instance is None:
return self.field
else:
source = getattr(instance, self.source_field_name)
spec = self.field.get_spec(source=source)
file = ImageCacheFile(spec)
instance.__dict__[self.attname] = file
return file
def __set__(self, instance, value):
instance.__dict__[self.attname] = value
django-imagekit-4.0.2/imagekit/pkgmeta.py 0000664 0000000 0000000 00000000407 13204511077 0020344 0 ustar 00root root 0000000 0000000 __title__ = 'django-imagekit'
__author__ = 'Matthew Tretter, Venelin Stoykov, Eric Eldredge, Bryan Veloso, Greg Newman, Chris Drackett, Justin Driscoll'
__version__ = '4.0.2'
__license__ = 'BSD'
__all__ = ['__title__', '__author__', '__version__', '__license__']
django-imagekit-4.0.2/imagekit/processors/ 0000775 0000000 0000000 00000000000 13204511077 0020543 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/processors/__init__.py 0000664 0000000 0000000 00000000472 13204511077 0022657 0 ustar 00root root 0000000 0000000 from pilkit.processors import *
__all__ = [
# Base
'ProcessorPipeline', 'Adjust', 'Reflection', 'Transpose',
'Anchor', 'MakeOpaque',
# Crop
'TrimBorderColor', 'Crop', 'SmartCrop',
# Resize
'Resize', 'ResizeToCover', 'ResizeToFill', 'SmartResize',
'ResizeCanvas', 'AddBorder', 'ResizeToFit', 'Thumbnail'
]
django-imagekit-4.0.2/imagekit/processors/base.py 0000664 0000000 0000000 00000000401 13204511077 0022022 0 ustar 00root root 0000000 0000000 import warnings
from pilkit.processors.base import *
warnings.warn('imagekit.processors.base is deprecated use imagekit.processors instead', DeprecationWarning)
__all__ = ['ProcessorPipeline', 'Adjust', 'Reflection', 'Transpose', 'Anchor', 'MakeOpaque']
django-imagekit-4.0.2/imagekit/processors/crop.py 0000664 0000000 0000000 00000000327 13204511077 0022062 0 ustar 00root root 0000000 0000000 import warnings
from pilkit.processors.crop import *
warnings.warn('imagekit.processors.crop is deprecated use imagekit.processors instead', DeprecationWarning)
__all__ = ['TrimBorderColor', 'Crop', 'SmartCrop']
django-imagekit-4.0.2/imagekit/processors/resize.py 0000664 0000000 0000000 00000000446 13204511077 0022422 0 ustar 00root root 0000000 0000000 import warnings
from pilkit.processors.resize import *
warnings.warn('imagekit.processors.resize is deprecated use imagekit.processors instead', DeprecationWarning)
__all__ = ['Resize', 'ResizeToCover', 'ResizeToFill', 'SmartResize', 'ResizeCanvas', 'AddBorder', 'ResizeToFit', 'Thumbnail']
django-imagekit-4.0.2/imagekit/processors/utils.py 0000664 0000000 0000000 00000000251 13204511077 0022253 0 ustar 00root root 0000000 0000000 import warnings
from pilkit.processors.utils import *
warnings.warn('imagekit.processors.utils is deprecated use pilkit.processors.utils instead', DeprecationWarning)
django-imagekit-4.0.2/imagekit/registry.py 0000664 0000000 0000000 00000015143 13204511077 0020567 0 ustar 00root root 0000000 0000000 from .exceptions import AlreadyRegistered, NotRegistered
from .signals import content_required, existence_required, source_saved
from .utils import autodiscover, call_strategy_method
class GeneratorRegistry(object):
"""
An object for registering generators. This registry provides
a convenient way for a distributable app to define default generators
without locking the users of the app into it.
"""
def __init__(self):
self._generators = {}
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)
if registered_generator and generator != self._generators[id]:
raise AlreadyRegistered('The generator with id %s is'
' already registered' % id)
self._generators[id] = generator
def unregister(self, id):
try:
del self._generators[id]
except KeyError:
raise NotRegistered('The generator with id %s is not'
' registered' % id)
def get(self, id, **kwargs):
autodiscover()
try:
generator = self._generators[id]
except KeyError:
raise NotRegistered('The generator with id %s is not'
' registered' % id)
if callable(generator):
return generator(**kwargs)
else:
return generator
def get_ids(self):
autodiscover()
return self._generators.keys()
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, callback)
class SourceGroupRegistry(object):
"""
The source group registry is responsible for listening to source_* signals
on source groups, and relaying them to the image generated file strategies
of the appropriate generators.
In addition, registering a new source group also registers its generated
files with that registry.
"""
_signals = {
source_saved: 'on_source_saved',
}
def __init__(self):
self._source_groups = {}
for signal in self._signals.keys():
signal.connect(self.source_group_receiver)
def register(self, generator_id, source_group):
from .specs.sourcegroups import SourceGroupFilesGenerator
generator_ids = self._source_groups.setdefault(source_group, set())
generator_ids.add(generator_id)
cachefile_registry.register(generator_id,
SourceGroupFilesGenerator(source_group, generator_id))
def unregister(self, generator_id, source_group):
from .specs.sourcegroups import SourceGroupFilesGenerator
generator_ids = self._source_groups.setdefault(source_group, set())
if generator_id in generator_ids:
generator_ids.remove(generator_id)
cachefile_registry.unregister(generator_id,
SourceGroupFilesGenerator(source_group, generator_id))
def source_group_receiver(self, sender, source, signal, **kwargs):
"""
Relay source group signals to the appropriate spec strategy.
"""
from .cachefiles import ImageCacheFile
source_group = sender
# Ignore signals from unregistered groups.
if source_group not in self._source_groups:
return
specs = [generator_registry.get(id, source=source) for id in
self._source_groups[source_group]]
callback_name = self._signals[signal]
for spec in specs:
file = ImageCacheFile(spec)
call_strategy_method(file, callback_name)
class CacheFileRegistry(object):
"""
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
associating them directly by, for example, putting a ``cachefiles``
attribute on image generators) so that image generators can be overridden
without losing the associated files. That way, a distributable app can
define its own generators without locking the users of the app into it.
"""
def __init__(self):
self._cachefiles = {}
def register(self, generator_id, cachefiles):
"""
Associates generated files with a generator id
"""
if cachefiles not in self._cachefiles:
self._cachefiles[cachefiles] = set()
self._cachefiles[cachefiles].add(generator_id)
def unregister(self, generator_id, cachefiles):
"""
Disassociates generated files with a generator id
"""
try:
self._cachefiles[cachefiles].remove(generator_id)
except KeyError:
pass
def get(self, generator_id):
for k, v in self._cachefiles.items():
if generator_id in v:
for file in k():
yield file
class Register(object):
"""
Register generators and generated files.
"""
def generator(self, id, generator=None):
if generator is None:
# Return a decorator
def decorator(cls):
self.generator(id, cls)
return cls
return decorator
generator_registry.register(id, generator)
# iterable that returns kwargs or callable that returns iterable of kwargs
def cachefiles(self, generator_id, cachefiles):
cachefile_registry.register(generator_id, cachefiles)
def source_group(self, generator_id, source_group):
source_group_registry.register(generator_id, source_group)
class Unregister(object):
"""
Unregister generators and generated files.
"""
def generator(self, id):
generator_registry.unregister(id)
def cachefiles(self, generator_id, cachefiles):
cachefile_registry.unregister(generator_id, cachefiles)
def source_group(self, generator_id, source_group):
source_group_registry.unregister(generator_id, source_group)
generator_registry = GeneratorRegistry()
cachefile_registry = CacheFileRegistry()
source_group_registry = SourceGroupRegistry()
register = Register()
unregister = Unregister()
django-imagekit-4.0.2/imagekit/signals.py 0000664 0000000 0000000 00000000250 13204511077 0020350 0 ustar 00root root 0000000 0000000 from django.dispatch import Signal
# Generated file signals
content_required = Signal()
existence_required = Signal()
# Source group signals
source_saved = Signal()
django-imagekit-4.0.2/imagekit/specs/ 0000775 0000000 0000000 00000000000 13204511077 0017456 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/specs/__init__.py 0000664 0000000 0000000 00000020421 13204511077 0021566 0 ustar 00root root 0000000 0000000 from copy import copy
from django.conf import settings
from django.db.models.fields.files import ImageFieldFile
from ..cachefiles.backends import get_default_cachefile_backend
from ..cachefiles.strategies import load_strategy
from .. import hashers
from ..exceptions import AlreadyRegistered, MissingSource
from ..utils import open_image, get_by_qname, process_image
from ..registry import generator_registry, register
class BaseImageSpec(object):
"""
An object that defines how an new image should be generated from a source
image.
"""
cachefile_storage = None
"""A Django storage system to use to save a cache file."""
cachefile_backend = None
"""
An object responsible for managing the state of cache files. Defaults to
an instance of ``IMAGEKIT_DEFAULT_CACHEFILE_BACKEND``
"""
cachefile_strategy = settings.IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY
"""
A dictionary containing callbacks that allow you to customize how and when
the image file is created. Defaults to
``IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY``.
"""
def __init__(self):
self.cachefile_backend = self.cachefile_backend or get_default_cachefile_backend()
self.cachefile_strategy = load_strategy(self.cachefile_strategy)
def generate(self):
raise NotImplementedError
MissingSource = MissingSource
"""
Raised when an operation requiring a source is attempted on a spec that has
no source.
"""
class ImageSpec(BaseImageSpec):
"""
An object that defines how to generate a new image from a source file using
PIL-based processors. (See :mod:`imagekit.processors`)
"""
processors = []
"""A list of processors to run on the original image."""
format = None
"""
The format of the output file. If not provided, ImageSpecField will try to
guess the appropriate format based on the extension of the filename and the
format of the input image.
"""
options = None
"""
A dictionary that will be passed to PIL's ``Image.save()`` method as keyword
arguments. Valid options vary between formats, but some examples include
``quality``, ``optimize``, and ``progressive`` for JPEGs. See the PIL
documentation for others.
"""
autoconvert = True
"""
Specifies whether automatic conversion using ``prepare_image()`` should be
performed prior to saving.
"""
def __init__(self, source):
self.source = source
super(ImageSpec, self).__init__()
@property
def cachefile_name(self):
if not self.source:
return None
fn = get_by_qname(settings.IMAGEKIT_SPEC_CACHEFILE_NAMER, 'namer')
return fn(self)
@property
def source(self):
src = getattr(self, '_source', None)
if not src:
field_data = getattr(self, '_field_data', None)
if field_data:
src = self._source = getattr(field_data['instance'], field_data['attname'])
del self._field_data
return src
@source.setter
def source(self, value):
self._source = value
def __getstate__(self):
state = copy(self.__dict__)
# Unpickled ImageFieldFiles won't work (they're missing a storage
# object). Since they're such a common use case, we special case them.
# Unfortunately, this also requires us to add the source getter to
# lazily retrieve the source on the reconstructed object; simply trying
# to look up the source in ``__setstate__`` would require us to get the
# model instance but, if ``__setstate__`` was called as part of
# deserializing that model, the model wouldn't be fully reconstructed
# yet, preventing us from accessing the source field.
# (This is issue #234.)
if isinstance(self.source, ImageFieldFile):
field = getattr(self.source, 'field')
state['_field_data'] = {
'instance': getattr(self.source, 'instance', None),
'attname': getattr(field, 'name', None),
}
state.pop('_source', None)
return state
def get_hash(self):
return hashers.pickle([
self.source.name,
self.processors,
self.format,
self.options,
self.autoconvert,
])
def generate(self):
if not self.source:
raise MissingSource("The spec '%s' has no source file associated"
" with it." % self)
# TODO: Move into a generator base class
# TODO: Factor out a generate_image function so you can create a generator and only override the PIL.Image creating part. (The tricky part is how to deal with original_format since generator base class won't have one.)
closed = self.source.closed
if closed:
# Django file object should know how to reopen itself if it was closed
# https://code.djangoproject.com/ticket/13750
self.source.open()
try:
img = open_image(self.source)
new_image = process_image(img,
processors=self.processors,
format=self.format,
autoconvert=self.autoconvert,
options=self.options)
finally:
if closed:
# We need to close the file if it was opened by us
self.source.close()
return new_image
def create_spec_class(class_attrs):
class DynamicSpecBase(ImageSpec):
def __reduce__(self):
try:
getstate = self.__getstate__
except AttributeError:
state = self.__dict__
else:
state = getstate()
return (create_spec, (class_attrs, state))
return type('DynamicSpec', (DynamicSpecBase,), class_attrs)
def create_spec(class_attrs, state):
cls = create_spec_class(class_attrs)
instance = cls.__new__(cls) # Create an instance without calling the __init__ (which may have required args).
try:
setstate = instance.__setstate__
except AttributeError:
instance.__dict__ = state
else:
setstate(state)
return instance
class SpecHost(object):
"""
An object that ostensibly has a spec attribute but really delegates to the
spec registry.
"""
def __init__(self, spec=None, spec_id=None, **kwargs):
spec_attrs = dict((k, v) for k, v in kwargs.items() if v is not None)
if spec_attrs:
if spec:
raise TypeError('You can provide either an image spec or'
' arguments for the ImageSpec constructor, but not both.')
else:
spec = create_spec_class(spec_attrs)
self._original_spec = spec
if spec_id:
self.set_spec_id(spec_id)
def set_spec_id(self, id):
"""
Sets the spec id for this object. Useful for when the id isn't
known when the instance is constructed (e.g. for ImageSpecFields whose
generated `spec_id`s are only known when they are contributed to a
class). If the object was initialized with a spec, it will be registered
under the provided id.
"""
self.spec_id = id
if self._original_spec:
try:
register.generator(id, self._original_spec)
except AlreadyRegistered:
# Fields should not cause AlreadyRegistered exceptions. If a
# spec is already registered, that should be used. It is
# especially important that an error is not thrown here because
# of South, which will create duplicate models as part of its
# "fake orm," therefore re-registering specs.
pass
def get_spec(self, source):
"""
Look up the spec by the spec id. We do this (instead of storing the
spec as an attribute) so that users can override apps' specs--without
having to edit model definitions--simply by registering another spec
with the same id.
"""
if not getattr(self, 'spec_id', None):
raise Exception('Object %s has no spec id.' % self)
return generator_registry.get(self.spec_id, source=source)
django-imagekit-4.0.2/imagekit/specs/sourcegroups.py 0000664 0000000 0000000 00000014354 13204511077 0022577 0 ustar 00root root 0000000 0000000 """
Source groups are the means by which image spec sources are identified. They
have two responsibilities:
1. To dispatch ``source_saved`` signals. (These will be relayed to the
corresponding specs' cache file strategies.)
2. To provide the source files that they represent, via a generator method named
``files()``. (This is used by the generateimages management command for
"pre-caching" image files.)
"""
from django.db.models.signals import post_init, post_save
from django.utils.functional import wraps
import inspect
from ..cachefiles import LazyImageCacheFile
from ..signals import source_saved
from ..utils import get_nonabstract_descendants
def ik_model_receiver(fn):
"""
A method decorator that filters out signals coming from models that don't
have fields that function as ImageFieldSourceGroup sources.
"""
@wraps(fn)
def receiver(self, sender, **kwargs):
if not inspect.isclass(sender):
return
for src in self._source_groups:
if issubclass(sender, src.model_class):
fn(self, sender=sender, **kwargs)
# If we find a match, return. We don't want to handle the signal
# more than once.
return
return receiver
class ModelSignalRouter(object):
"""
Normally, ``ImageFieldSourceGroup`` would be directly responsible for
watching for changes on the model field it represents. However, Django does
not dispatch events for abstract base classes. Therefore, we must listen for
the signals on all models and filter out those that aren't represented by
``ImageFieldSourceGroup``s. This class encapsulates that functionality.
Related:
https://github.com/matthewwithanm/django-imagekit/issues/126
https://code.djangoproject.com/ticket/9318
"""
def __init__(self):
self._source_groups = []
uid = 'ik_spec_field_receivers'
post_init.connect(self.post_init_receiver, dispatch_uid=uid)
post_save.connect(self.post_save_receiver, dispatch_uid=uid)
def add(self, source_group):
self._source_groups.append(source_group)
def init_instance(self, instance):
instance._ik = getattr(instance, '_ik', {})
def update_source_hashes(self, instance):
"""
Stores hashes of the source image files so that they can be compared
later to see whether the source image has changed (and therefore whether
the spec file needs to be regenerated).
"""
self.init_instance(instance)
instance._ik['source_hashes'] = dict(
(attname, hash(getattr(instance, attname)))
for attname in self.get_source_fields(instance))
return instance._ik['source_hashes']
def get_source_fields(self, instance):
"""
Returns a list of the source fields for the given instance.
"""
return set(src.image_field
for src in self._source_groups
if isinstance(instance, src.model_class))
@ik_model_receiver
def post_save_receiver(self, sender, instance=None, created=False, update_fields=None, raw=False, **kwargs):
if not raw:
self.init_instance(instance)
old_hashes = instance._ik.get('source_hashes', {}).copy()
new_hashes = self.update_source_hashes(instance)
for attname in self.get_source_fields(instance):
if update_fields and attname not in update_fields:
continue
file = getattr(instance, attname)
if file and old_hashes.get(attname) != new_hashes[attname]:
self.dispatch_signal(source_saved, file, sender, instance,
attname)
@ik_model_receiver
def post_init_receiver(self, sender, instance=None, **kwargs):
self.init_instance(instance)
source_fields = self.get_source_fields(instance)
local_fields = dict((field.name, field)
for field in instance._meta.local_fields
if field.name in source_fields)
instance._ik['source_hashes'] = dict(
(attname, hash(file_field))
for attname, file_field in local_fields.items())
def dispatch_signal(self, signal, file, model_class, instance, attname):
"""
Dispatch the signal for each of the matching source groups. Note that
more than one source can have the same model and image_field; it's
important that we dispatch the signal for each.
"""
for source_group in self._source_groups:
if issubclass(model_class, source_group.model_class) and source_group.image_field == attname:
signal.send(sender=source_group, source=file)
class ImageFieldSourceGroup(object):
"""
A source group that repesents a particular field across all instances of a
model and its subclasses.
"""
def __init__(self, model_class, image_field):
self.model_class = model_class
self.image_field = image_field
signal_router.add(self)
def files(self):
"""
A generator that returns the source files that this source group
represents; in this case, a particular field of every instance of a
particular model and its subclasses.
"""
for model in get_nonabstract_descendants(self.model_class):
for instance in model.objects.all().iterator():
yield getattr(instance, self.image_field)
class SourceGroupFilesGenerator(object):
"""
A Python generator that yields cache file objects for source groups.
"""
def __init__(self, source_group, generator_id):
self.source_group = source_group
self.generator_id = generator_id
def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash((self.source_group, self.generator_id))
def __call__(self):
for source_file in self.source_group.files():
yield LazyImageCacheFile(self.generator_id,
source=source_file)
signal_router = ModelSignalRouter()
django-imagekit-4.0.2/imagekit/templates/ 0000775 0000000 0000000 00000000000 13204511077 0020337 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/templates/imagekit/ 0000775 0000000 0000000 00000000000 13204511077 0022131 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/templates/imagekit/admin/ 0000775 0000000 0000000 00000000000 13204511077 0023221 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/templates/imagekit/admin/thumbnail.html 0000664 0000000 0000000 00000000212 13204511077 0026065 0 ustar 00root root 0000000 0000000 {% if thumbnail %}
{% endif %} django-imagekit-4.0.2/imagekit/templatetags/ 0000775 0000000 0000000 00000000000 13204511077 0021033 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/templatetags/__init__.py 0000664 0000000 0000000 00000000000 13204511077 0023132 0 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/imagekit/templatetags/imagekit.py 0000664 0000000 0000000 00000023174 13204511077 0023206 0 ustar 00root root 0000000 0000000 from __future__ import unicode_literals
from django import template
from django.utils.html import escape
from django.utils.safestring import mark_safe
from ..compat import parse_bits
from ..cachefiles import ImageCacheFile
from ..registry import generator_registry
from ..lib import force_text
register = template.Library()
ASSIGNMENT_DELIMETER = 'as'
HTML_ATTRS_DELIMITER = '--'
DEFAULT_THUMBNAIL_GENERATOR = 'imagekit:thumbnail'
def get_cachefile(context, generator_id, generator_kwargs, source=None):
generator_id = generator_id.resolve(context)
kwargs = dict((k, v.resolve(context)) for k, v in generator_kwargs.items())
generator = generator_registry.get(generator_id, **kwargs)
return ImageCacheFile(generator)
def parse_dimensions(dimensions):
"""
Parse the width and height values from a dimension string. Valid values are
'1x1', '1x', and 'x1'. If one of the dimensions is omitted, the parse result
will be None for that value.
"""
width, height = [d.strip() and int(d) or None for d in dimensions.split('x')]
return dict(width=width, height=height)
class GenerateImageAssignmentNode(template.Node):
def __init__(self, variable_name, generator_id, generator_kwargs):
self._generator_id = generator_id
self._generator_kwargs = generator_kwargs
self._variable_name = variable_name
def get_variable_name(self, context):
return force_text(self._variable_name)
def render(self, context):
variable_name = self.get_variable_name(context)
context[variable_name] = get_cachefile(context, self._generator_id,
self._generator_kwargs)
return ''
class GenerateImageTagNode(template.Node):
def __init__(self, generator_id, generator_kwargs, html_attrs):
self._generator_id = generator_id
self._generator_kwargs = generator_kwargs
self._html_attrs = html_attrs
def render(self, context):
file = get_cachefile(context, self._generator_id,
self._generator_kwargs)
attrs = dict((k, v.resolve(context)) for k, v in
self._html_attrs.items())
# Only add width and height if neither is specified (to allow for
# proportional in-browser scaling).
if not 'width' in attrs and not 'height' in attrs:
attrs.update(width=file.width, height=file.height)
attrs['src'] = file.url
attr_str = ' '.join('%s="%s"' % (escape(k), escape(v)) for k, v in
attrs.items())
return mark_safe('' % attr_str)
class ThumbnailAssignmentNode(template.Node):
def __init__(self, variable_name, generator_id, dimensions, source, generator_kwargs):
self._variable_name = variable_name
self._generator_id = generator_id
self._dimensions = dimensions
self._source = source
self._generator_kwargs = generator_kwargs
def get_variable_name(self, context):
return force_text(self._variable_name)
def render(self, context):
variable_name = self.get_variable_name(context)
generator_id = self._generator_id.resolve(context) if self._generator_id else DEFAULT_THUMBNAIL_GENERATOR
kwargs = dict((k, v.resolve(context)) for k, v in
self._generator_kwargs.items())
kwargs['source'] = self._source.resolve(context)
kwargs.update(parse_dimensions(self._dimensions.resolve(context)))
generator = generator_registry.get(generator_id, **kwargs)
context[variable_name] = ImageCacheFile(generator)
return ''
class ThumbnailImageTagNode(template.Node):
def __init__(self, generator_id, dimensions, source, generator_kwargs, html_attrs):
self._generator_id = generator_id
self._dimensions = dimensions
self._source = source
self._generator_kwargs = generator_kwargs
self._html_attrs = html_attrs
def render(self, context):
generator_id = self._generator_id.resolve(context) if self._generator_id else DEFAULT_THUMBNAIL_GENERATOR
dimensions = parse_dimensions(self._dimensions.resolve(context))
kwargs = dict((k, v.resolve(context)) for k, v in
self._generator_kwargs.items())
kwargs['source'] = self._source.resolve(context)
kwargs.update(dimensions)
generator = generator_registry.get(generator_id, **kwargs)
file = ImageCacheFile(generator)
attrs = dict((k, v.resolve(context)) for k, v in
self._html_attrs.items())
# Only add width and height if neither is specified (to allow for
# proportional in-browser scaling).
if not 'width' in attrs and not 'height' in attrs:
attrs.update(width=file.width, height=file.height)
attrs['src'] = file.url
attr_str = ' '.join('%s="%s"' % (escape(k), escape(v)) for k, v in
attrs.items())
return mark_safe('' % attr_str)
def parse_ik_tag_bits(parser, bits):
"""
Parses the tag name, html attributes and variable name (for assignment tags)
from the provided bits. The preceding bits may vary and are left to be
parsed by specific tags.
"""
varname = None
html_attrs = {}
tag_name = bits.pop(0)
if len(bits) >= 2 and bits[-2] == ASSIGNMENT_DELIMETER:
varname = bits[-1]
bits = bits[:-2]
if HTML_ATTRS_DELIMITER in bits:
if varname:
raise template.TemplateSyntaxError('Do not specify html attributes'
' (using "%s") when using the "%s" tag as an assignment'
' tag.' % (HTML_ATTRS_DELIMITER, tag_name))
index = bits.index(HTML_ATTRS_DELIMITER)
html_bits = bits[index + 1:]
bits = bits[:index]
if not html_bits:
raise template.TemplateSyntaxError('Don\'t use "%s" unless you\'re'
' setting html attributes.' % HTML_ATTRS_DELIMITER)
args, html_attrs = parse_bits(parser, html_bits, [], 'args',
'kwargs', None, False, tag_name)
if len(args):
raise template.TemplateSyntaxError('All "%s" tag arguments after'
' the "%s" token must be named.' % (tag_name,
HTML_ATTRS_DELIMITER))
return (tag_name, bits, html_attrs, varname)
#@register.tag
def generateimage(parser, token):
"""
Creates an image based on the provided arguments.
By default::
{% generateimage 'myapp:thumbnail' source=mymodel.profile_image %}
generates an ```` tag::
You can add additional attributes to the tag using "--". For example,
this::
{% generateimage 'myapp:thumbnail' source=mymodel.profile_image -- alt="Hello!" %}
will result in the following markup::
For more flexibility, ``generateimage`` also works as an assignment tag::
{% generateimage 'myapp:thumbnail' source=mymodel.profile_image as th %}
"""
bits = token.split_contents()
tag_name, bits, html_attrs, varname = parse_ik_tag_bits(parser, bits)
args, kwargs = parse_bits(parser, bits, ['generator_id'], 'args', 'kwargs',
None, False, tag_name)
if len(args) != 1:
raise template.TemplateSyntaxError('The "%s" tag requires exactly one'
' unnamed argument (the generator id).' % tag_name)
generator_id = args[0]
if varname:
return GenerateImageAssignmentNode(varname, generator_id, kwargs)
else:
return GenerateImageTagNode(generator_id, kwargs, html_attrs)
#@register.tag
def thumbnail(parser, token):
"""
A convenient shortcut syntax for generating a thumbnail. The following::
{% thumbnail '100x100' mymodel.profile_image %}
is equivalent to::
{% generateimage 'imagekit:thumbnail' source=mymodel.profile_image width=100 height=100 %}
The thumbnail tag supports the "--" and "as" bits for adding html
attributes and assigning to a variable, respectively. It also accepts the
kwargs "anchor", and "crop".
To use "smart cropping" (the ``SmartResize`` processor)::
{% thumbnail '100x100' mymodel.profile_image %}
To crop, anchoring the image to the top right (the ``ResizeToFill``
processor)::
{% thumbnail '100x100' mymodel.profile_image anchor='tr' %}
To resize without cropping (using the ``ResizeToFit`` processor)::
{% thumbnail '100x100' mymodel.profile_image crop=0 %}
"""
bits = token.split_contents()
tag_name, bits, html_attrs, varname = parse_ik_tag_bits(parser, bits)
args, kwargs = parse_bits(parser, bits, [], 'args', 'kwargs',
None, False, tag_name)
if len(args) < 2:
raise template.TemplateSyntaxError('The "%s" tag requires at least two'
' unnamed arguments: the dimensions and the source image.'
% tag_name)
elif len(args) > 3:
raise template.TemplateSyntaxError('The "%s" tag accepts at most three'
' unnamed arguments: a generator id, the dimensions, and the'
' source image.' % tag_name)
dimensions, source = args[-2:]
generator_id = args[0] if len(args) > 2 else None
if varname:
return ThumbnailAssignmentNode(varname, generator_id, dimensions,
source, kwargs)
else:
return ThumbnailImageTagNode(generator_id, dimensions, source, kwargs,
html_attrs)
generateimage = register.tag(generateimage)
thumbnail = register.tag(thumbnail)
django-imagekit-4.0.2/imagekit/utils.py 0000664 0000000 0000000 00000013552 13204511077 0020061 0 ustar 00root root 0000000 0000000 from __future__ import unicode_literals
import logging
import re
from tempfile import NamedTemporaryFile
from hashlib import md5
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files import File
try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
from pilkit.utils import *
from .lib import NullHandler, force_bytes
bad_memcached_key_chars = re.compile('[\u0000-\u001f\\s]+')
_autodiscovered = False
def get_nonabstract_descendants(model):
""" Returns all non-abstract descendants of the model. """
if not model._meta.abstract:
yield model
for s in model.__subclasses__():
for m in get_nonabstract_descendants(s):
yield m
def get_by_qname(path, desc):
try:
dot = path.rindex('.')
except ValueError:
raise ImproperlyConfigured("%s isn't a %s module." % (path, desc))
module, objname = path[:dot], path[dot + 1:]
try:
mod = import_module(module)
except ImportError as e:
raise ImproperlyConfigured('Error importing %s module %s: "%s"' %
(desc, module, e))
try:
obj = getattr(mod, objname)
return obj
except AttributeError:
raise ImproperlyConfigured('%s module "%s" does not define "%s"'
% (desc[0].upper() + desc[1:], module, objname))
_singletons = {}
def get_singleton(class_path, desc):
global _singletons
cls = get_by_qname(class_path, desc)
instance = _singletons.get(cls)
if not instance:
instance = _singletons[cls] = cls()
return instance
def autodiscover():
"""
Auto-discover INSTALLED_APPS imagegenerators.py modules and fail silently
when not present. This forces an import on them to register any admin bits
they may want.
Copied from django.contrib.admin
"""
global _autodiscovered
if _autodiscovered:
return
try:
from django.utils.module_loading import autodiscover_modules
except ImportError:
# Django<1.7
_autodiscover_modules_fallback()
else:
autodiscover_modules('imagegenerators')
_autodiscovered = True
def _autodiscover_modules_fallback():
"""
Auto-discover INSTALLED_APPS imagegenerators.py modules and fail silently
when not present. This forces an import on them to register any admin bits
they may want.
Copied from django.contrib.admin
Used for Django versions < 1.7
"""
from django.conf import settings
try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
for app in settings.INSTALLED_APPS:
# As of Django 1.7, settings.INSTALLED_APPS may contain classes instead of modules, hence the try/except
# See here: https://docs.djangoproject.com/en/dev/releases/1.7/#introspecting-applications
try:
mod = import_module(app)
# Attempt to import the app's admin module.
try:
import_module('%s.imagegenerators' % app)
except:
# Decide whether to bubble up this error. If the app just
# doesn't have an imagegenerators module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, 'imagegenerators'):
raise
except ImportError:
pass
def get_logger(logger_name='imagekit', add_null_handler=True):
logger = logging.getLogger(logger_name)
if add_null_handler:
logger.addHandler(NullHandler())
return logger
def get_field_info(field_file):
"""
A utility for easily extracting information about the host model from a
Django FileField (or subclass). This is especially useful for when you want
to alter processors based on a property of the source model. For example::
class MySpec(ImageSpec):
def __init__(self, source):
instance, attname = get_field_info(source)
self.processors = [SmartResize(instance.thumbnail_width,
instance.thumbnail_height)]
"""
return (
getattr(field_file, 'instance', None),
getattr(getattr(field_file, 'field', None), 'attname', None),
)
def generate(generator):
"""
Calls the ``generate()`` method of a generator instance, and then wraps the
result in a Django File object so Django knows how to save it.
"""
content = generator.generate()
f = File(content)
# The size of the File must be known or Django will try to open a file
# without a name and raise an Exception.
f.size = len(content.read())
# After getting the size reset the file pointer for future reads.
content.seek(0)
return f
def call_strategy_method(file, method_name):
strategy = getattr(file, 'cachefile_strategy', None)
fn = getattr(strategy, method_name, None)
if fn is not None:
fn(file)
def get_cache():
try:
from django.core.cache import caches
except ImportError:
# Django < 1.7
from django.core.cache import get_cache
return get_cache(settings.IMAGEKIT_CACHE_BACKEND)
return caches[settings.IMAGEKIT_CACHE_BACKEND]
def sanitize_cache_key(key):
if settings.IMAGEKIT_USE_MEMCACHED_SAFE_CACHE_KEY:
# Memcached keys can't contain whitespace or control characters.
new_key = bad_memcached_key_chars.sub('', key)
# The also can't be > 250 chars long. Since we don't know what the
# user's cache ``KEY_FUNCTION`` setting is like, we'll limit it to 200.
if len(new_key) >= 200:
new_key = '%s:%s' % (new_key[:200-33], md5(force_bytes(key)).hexdigest())
key = new_key
return key
django-imagekit-4.0.2/setup.cfg 0000664 0000000 0000000 00000000033 13204511077 0016364 0 ustar 00root root 0000000 0000000 [bdist_wheel]
universal = 1 django-imagekit-4.0.2/setup.py 0000664 0000000 0000000 00000004433 13204511077 0016265 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import codecs
import os
from setuptools import setup, find_packages
import sys
# Workaround for multiprocessing/nose issue. See http://bugs.python.org/msg170215
try:
import multiprocessing # NOQA
except ImportError:
pass
if 'publish' in sys.argv:
os.system('python setup.py sdist bdist_wheel upload')
sys.exit()
read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read()
def exec_file(filepath, globalz=None, localz=None):
exec(read(filepath), globalz, localz)
# Load package meta from the pkgmeta module without loading imagekit.
pkgmeta = {}
exec_file(os.path.join(os.path.dirname(__file__),
'imagekit', 'pkgmeta.py'), pkgmeta)
setup(
name='django-imagekit',
version=pkgmeta['__version__'],
description='Automated image processing for Django models.',
long_description=read(os.path.join(os.path.dirname(__file__), 'README.rst')),
author='Matthew Tretter',
author_email='m@tthewwithanm.com',
maintainer='Bryan Veloso',
maintainer_email='bryan@revyver.com',
license='BSD',
url='http://github.com/matthewwithanm/django-imagekit/',
packages=find_packages(exclude=['*.tests', '*.tests.*', 'tests.*', 'tests']),
zip_safe=False,
include_package_data=True,
tests_require=[
'beautifulsoup4>=4.4.0',
'nose>=1.3.6',
'nose-progressive>=1.5.1',
'django-nose>=1.4',
'Pillow',
'mock>=1.0.1',
],
test_suite='testrunner.run_tests',
install_requires=[
'django-appconf>=0.5',
'pilkit>=0.2.0',
'six',
],
extras_require={
'async': ['django-celery>=3.0'],
'async_rq': ['django-rq>=0.6.0'],
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Topic :: Utilities'
],
)
django-imagekit-4.0.2/testrunner.py 0000664 0000000 0000000 00000001245 13204511077 0017334 0 ustar 00root root 0000000 0000000 # A wrapper for Django's test runner.
# See http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/
# and http://gremu.net/blog/2010/enable-setuppy-test-your-django-apps/
import os
import sys
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'
test_dir = os.path.dirname(__file__)
sys.path.insert(0, test_dir)
from django.test.utils import get_runner
from django.conf import settings
def run_tests():
cls = get_runner(settings)
runner = cls()
failures = runner.run_tests(['tests'])
# Clean autogenerated junk before exit
from tests.utils import clear_imagekit_test_files
clear_imagekit_test_files()
sys.exit(failures)
django-imagekit-4.0.2/tests/ 0000775 0000000 0000000 00000000000 13204511077 0015711 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/tests/__init__.py 0000664 0000000 0000000 00000000000 13204511077 0020010 0 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/tests/imagegenerators.py 0000664 0000000 0000000 00000000701 13204511077 0021435 0 ustar 00root root 0000000 0000000 from imagekit import ImageSpec, register
from imagekit.processors import ResizeToFill
class TestSpec(ImageSpec):
pass
class ResizeTo1PixelSquare(ImageSpec):
def __init__(self, width=None, height=None, anchor=None, crop=None, **kwargs):
self.processors = [ResizeToFill(1, 1)]
super(ResizeTo1PixelSquare, self).__init__(**kwargs)
register.generator('testspec', TestSpec)
register.generator('1pxsq', ResizeTo1PixelSquare)
django-imagekit-4.0.2/tests/media/ 0000775 0000000 0000000 00000000000 13204511077 0016770 5 ustar 00root root 0000000 0000000 django-imagekit-4.0.2/tests/media/reference.png 0000664 0000000 0000000 00000351176 13204511077 0021451 0 ustar 00root root 0000000 0000000 PNG
IHDR ?1 IDATxlYmr&}_s{K^I"KF(A2,0/~'aTK%Tl.yo;