diff --git a/README.rst b/README.rst
index db024c3..3e6e97b 100644
--- a/README.rst
+++ b/README.rst
@@ -36,10 +36,87 @@ Specs
-----
You have one image and you want to do something to it to create another image.
-That's the basic use case of ImageKit. But how do you tell ImageKit what to do?
-By defining an "image spec." Specs are instructions for creating a new image
-from an existing one, and there are a few ways to define one. The most basic
-way is by defining an ``ImageSpec`` subclass:
+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/generated/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
+
+ 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
+``ImageField``s.
+
+.. 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
@@ -51,51 +128,109 @@ way is by defining an ``ImageSpec`` subclass:
format = 'JPEG'
options = {'quality': 60}
-Now that you've defined a spec, it's time to use it. The nice thing about specs
-is that they can be used in many different contexts.
-
-Sometimes, you may want to just use a spec to generate a new image file. This
-might be useful, for example, in view code, or in scripts:
+It's probaby 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
- spec = Thumbnail()
- new_file = spec.apply(source_file)
+ source_file = open('/path/to/myimage.jpg')
+ image_generator = Thumbnail(source=source_file)
+ result = image_generator.generate()
-More often, however, you'll want to register your spec with ImageKit:
+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
- from imagekit import specs
- specs.register(Thumbnail, 'myapp:fancy_thumbnail')
-
-Once a spec is registered with a unique name, you can start to take advantage of
-ImageKit's powerful utilities to automatically generate images for you...
-
-.. note:: You might be wondering why we bother with the id string instead of
- just passing the spec itself. The reason is that these ids allow users to
- easily override specs defined in third party apps. That way, it doesn't
- matter if "django-badblog" says its thumbnails are 200x200, you can just
- register your own spec (using the same id the app uses) and have whatever
- size thumbnails you want.
+ dest = open('/path/to/dest.jpg', 'w')
+ dest.write(result.read())
+ dest.close()
-In Templates
-^^^^^^^^^^^^
+Using Specs In Templates
+^^^^^^^^^^^^^^^^^^^^^^^^
-One utility ImageKit provides for processing images is a template tag:
+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
+ 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 %}
- {% spec 'myapp:fancy_thumbnail' source_image alt='A picture of me' %}
+ {% generateimage 'myapp:thumbnail' source=source_image %}
-Output:
+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_image -- 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:
@@ -104,72 +239,42 @@ assignment tag, providing access to the underlying file object:
{% load imagekit %}
- {% spec 'myapp:fancy_thumbnail' source_image as th %}
+ {% generateimage 'myapp:thumbnail' source=source_image as th %}
Click to download a cool {{ th.width }} x {{ th.height }} image!
-In Models
-^^^^^^^^^
+thumbnail
+"""""""""
-Specs can also be used to add ``ImageField``-like fields that expose the result
-of applying a spec to another one of your model's fields:
+Because it's such a common use case, ImageKit also provides a "thumbnail"
+template tag.
-.. code-block:: python
+.. code-block:: html
- from django.db import models
- from imagekit.models import ImageSpecField
+ {% load imagekit %}
- class Photo(models.Model):
- avatar = models.ImageField(upload_to='avatars')
- avatar_thumbnail = ImageSpecField(id='myapp:fancy_thumbnail', source='avatar')
+ {% thumbnail '100x50' source_image %}
- photo = Photo.objects.all()[0]
- print photo.avatar_thumbnail.url # > /media/CACHE/ik/982d5af84cddddfd0fbf70892b4431e4.jpg
- print photo.avatar_thumbnail.width # > 100
+.. note::
-Since defining a spec, registering it, and using it in a single model field is
-such a common usage, ImakeKit provides a shortcut that allow you to skip
-writing a subclass of ``ImageSpec``:
+ Comparing this syntax to the generateimage tag above, you'll notice a few
+ differences.
-.. code-block:: python
+ 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". (A custom id can be specified by passing an argument
+ before the dimensions.) **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``.
- from django.db import models
- from imagekit.models import ImageSpecField
- from imagekit.processors import ResizeToFill
+ 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. Interally, however, the tag is parsing our positional
+ arguments and passing them as keyword arguments to our generator class.
- class Photo(models.Model):
- avatar = models.ImageField(upload_to='avatars')
- avatar_thumbnail = ImageSpecField(processors=[ResizeToFill(100, 50)],
- format='JPEG',
- options={'quality': 60},
- source='avatar')
- photo = Photo.objects.all()[0]
- print photo.avatar_thumbnail.url # > /media/CACHE/ik/982d5af84cddddfd0fbf70892b4431e4.jpg
- print photo.avatar_thumbnail.width # > 100
-This has the exact same behavior as before, but the spec definition is inlined.
-Since no ``id`` is provided, one is automatically generated based on the app
-name, model, and field.
-
-Specs can also be used in models to add ``ImageField``-like fields that process
-a user-provided image without saving the original:
-
-.. code-block:: python
-
- from django.db import models
- from imagekit.models import ProcessedImageField
-
- class Photo(models.Model):
- avatar_thumbnail = ProcessedImageField(spec_id='myapp:fancy_thumbnail',
- upload_to='avatars')
-
- photo = Photo.objects.all()[0]
- print photo.avatar_thumbnail.url # > /media/avatars/MY-avatar_3.jpg
- print photo.avatar_thumbnail.width # > 100
-
-Like with ``ImageSpecField``, the ``ProcessedImageField`` constructor also
-has a shortcut version that allows you to inline spec definitions.
In Forms
diff --git a/imagekit/generatedfiles/namers.py b/imagekit/generatedfiles/namers.py
index 062fb7a..2a9f9b7 100644
--- a/imagekit/generatedfiles/namers.py
+++ b/imagekit/generatedfiles/namers.py
@@ -1,9 +1,29 @@
+"""
+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_GENERATEDFILE_DIR`` setting.
+
+ """
source_filename = getattr(generator.source, 'name', None)
if source_filename is None or os.path.isabs(source_filename):
@@ -21,6 +41,19 @@ def source_name_as_path(generator):
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_GENERATEDFILE_DIR`` setting.
+
+ """
source_filename = getattr(generator.source, 'name', None)
if source_filename is None or os.path.isabs(source_filename):
@@ -39,6 +72,19 @@ def source_name_dot_hash(generator):
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_GENERATEDFILE_DIR`` setting.
+
+ """
format = getattr(generator, 'format', None)
ext = format_to_extension(format) if format else ''
return os.path.normpath(os.path.join(settings.IMAGEKIT_GENERATEDFILE_DIR,