From 5fe5a73cb17a55c1351fee7d57e5c56ce77b58ea Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Sun, 14 Oct 2012 22:28:48 -0400 Subject: [PATCH] Update docs This will be great when 3.0 is ready, but it'll also serve as a nice guide for us as we develop. --- README.rst | 287 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 189 insertions(+), 98 deletions(-) diff --git a/README.rst b/README.rst index 041d02d..9b38119 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,7 @@ -ImageKit is a Django app that helps you to add variations of uploaded images -to your models. These variations are called "specs" and can include things -like different sizes (e.g. thumbnails) and black and white versions. +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. **For the complete documentation on the latest stable version of ImageKit, see** `ImageKit on RTD`_. Our `changelog is also available`_. @@ -10,10 +11,10 @@ like different sizes (e.g. thumbnails) and black and white versions. Installation ------------- +============ -1. Install `PIL`_ or `Pillow`_. If you're using an ``ImageField`` in Django, - you should have already done this. +1. Install `PIL`_ or `Pillow`_. (If you're using an ``ImageField`` in Django, + you should have already done this.) 2. ``pip install django-imagekit`` (or clone the source and put the imagekit module on your path) 3. Add ``'imagekit'`` to your ``INSTALLED_APPS`` list in your project's settings.py @@ -27,11 +28,90 @@ Installation .. _`Pillow`: http://pypi.python.org/pypi/Pillow -Adding Specs to a Model ------------------------ +Usage Overview +============== -Much like ``django.db.models.ImageField``, Specs are defined as properties -of a model class: + +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: + +.. code-block:: python + + from imagekit import ImageSpec + from imagekit.processors import ResizeToFill + + class Thumbnail(ImageSpec): + processors = [ResizeToFill(100, 50)] + 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: + +.. code-block:: python + + ???????? + +More often, however, you'll want to register your spec with ImageKit: + +.. code-block:: python + + from imagekit import specs + specs.register('myapp:fancy_thumbnail', 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. + + +In Templates +^^^^^^^^^^^^ + +One utility ImageKit provides for processing images is a template tag: + +.. code-block:: html + + {% load imagekit %} + + {% spec 'myapp:fancy_thumbnail' source_image alt='A picture of me' %} + +Output: + +.. code-block:: html + + A picture of me + +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 %} + + {% spec 'myapp:fancy_thumbnail' source_image as th %} + Click to download a cool {{ th.width }} x {{ th.height }} image! + + +In Models +^^^^^^^^^ + +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: .. code-block:: python @@ -39,73 +119,111 @@ of a model class: from imagekit.models import ImageSpecField class Photo(models.Model): - original_image = models.ImageField(upload_to='photos') - formatted_image = ImageSpecField(image_field='original_image', format='JPEG', - options={'quality': 90}) - -Accessing the spec through a model instance will create the image and return -an ImageFile-like object (just like with a normal -``django.db.models.ImageField``): - -.. code-block:: python + avatar = models.ImageField(upload_to='avatars') + avatar_thumbnail = ImageSpecField(id='myapp:fancy_thumbnail', image_field='avatar') photo = Photo.objects.all()[0] - photo.original_image.url # > '/media/photos/birthday.tiff' - photo.formatted_image.url # > '/media/cache/photos/birthday_formatted_image.jpeg' + print photo.avatar_thumbnail.url # > /static/CACHE/ik/982d5af84cddddfd0fbf70892b4431e4.jpg + print photo.avatar_thumbnail.width # > 100 -Check out ``imagekit.models.ImageSpecField`` for more information. - -If you only want to save the processed image (without maintaining the original), -you can use a ``ProcessedImageField``: +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``: .. code-block:: python from django.db import models - from imagekit.models.fields import ProcessedImageField + from imagekit.models import ImageSpecField + from imagekit.processors import ResizeToFill class Photo(models.Model): - processed_image = ProcessedImageField(format='JPEG', options={'quality': 90}) + avatar = models.ImageField(upload_to='avatars') + avatar_thumbnail = ImageSpecField(processors=[ResizeToFill(100, 50)], + format='JPEG', + options={'quality': 60}, + image_field='avatar') -See the class documentation for details. + photo = Photo.objects.all()[0] + print photo.avatar_thumbnail.url # > /static/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 # > /static/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 +^^^^^^^^ + +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 + + class AvatarForm(forms.Form): + avatar_thumbnail = ProcessedImageField(spec_id='myapp:fancy_thumbnail') + +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. + +As with the model field classes, ``imagekit.forms.ProcessedImageField`` also +has a shortcut version that allows you to inline spec definitions. Processors ---------- -The real power of ImageKit comes from processors. Processors take an image, do -something to it, and return the result. By providing a list of processors to -your spec, you can expose different versions of the original image: +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 django.db import models - from imagekit.models import ImageSpecField - from imagekit.processors import ResizeToFill, Adjust + from imagekit import ImageSpec + from imagekit.processors import TrimBorderColor, Adjust - class Photo(models.Model): - original_image = models.ImageField(upload_to='photos') - thumbnail = ImageSpecField([Adjust(contrast=1.2, sharpness=1.1), - ResizeToFill(50, 50)], image_field='original_image', - format='JPEG', options={'quality': 90}) - -The ``thumbnail`` property will now return a cropped image: - -.. code-block:: python - - photo = Photo.objects.all()[0] - photo.thumbnail.url # > '/media/cache/photos/birthday_thumbnail.jpeg' - photo.thumbnail.width # > 50 - photo.original_image.width # > 1000 - -The original image is not modified; ``thumbnail`` is a new file that is the -result of running the ``imagekit.processors.ResizeToFill`` processor on the -original. (If you only need to save the processed image, and not the original, -pass processors to a ``ProcessedImageField`` instead of an ``ImageSpecField``.) + 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 -implement a ``process()`` method: +define a class that implements a ``process()`` method: .. code-block:: python @@ -114,10 +232,23 @@ implement a ``process()`` method: # Code for adding the watermark goes here. return image - class Photo(models.Model): - original_image = models.ImageField(upload_to='photos') - watermarked_image = ImageSpecField([Watermark()], image_field='original_image', - format='JPEG', options={'quality': 90}) +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} Admin @@ -134,12 +265,10 @@ Django admin classes: 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) AdminThumbnail can even use a custom template. For more information, see @@ -148,40 +277,6 @@ AdminThumbnail can even use a custom template. For more information, see .. _`Django admin change list`: https://docs.djangoproject.com/en/dev/intro/tutorial02/#customize-the-admin-change-list -Image Cache Backends --------------------- - -Whenever you access properties like ``url``, ``width`` and ``height`` of an -``ImageSpecField``, its cached image is validated; whenever you save a new image -to the ``ImageField`` your spec uses as a source, the spec image is invalidated. -The default way to validate a cache image is to check to see if the file exists -and, if not, generate a new one; the default way to invalidate the cache is to -delete the image. This is a very simple and straightforward way to handle cache -validation, but it has its drawbacks—for example, checking to see if the image -exists means frequently hitting the storage backend. - -Because of this, ImageKit allows you to define custom image cache backends. To -be a valid image cache backend, a class must implement three methods: -``validate``, ``invalidate``, and ``clear`` (which is called when the image is -no longer needed in any form, i.e. the model is deleted). Each of these methods -must accept a file object, but the internals are up to you. For example, you -could store the state (valid, invalid) of the cache in a database to avoid -filesystem access. You can then specify your image cache backend on a per-field -basis: - -.. code-block:: python - - class Photo(models.Model): - ... - thumbnail = ImageSpecField(..., image_cache_backend=MyImageCacheBackend()) - -Or in your ``settings.py`` file if you want to use it as the default: - -.. code-block:: python - - IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND = 'path.to.MyImageCacheBackend' - - Community --------- @@ -199,7 +294,3 @@ 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! - -ImageKit's image cache backends are also fairly isolated from the ImageKit guts. -If you've fine-tuned one to work perfectly for a popular file storage backend, -let us take a look! Maybe other people could use it.