From 58e1c7f7e0ceaebd7d2515380271dcf2c2428fa8 Mon Sep 17 00:00:00 2001 From: Matthew Tretter Date: Fri, 1 Feb 2013 01:27:54 -0500 Subject: [PATCH] Some docs --- README.rst | 269 +++++++++++++++++++++--------- imagekit/generatedfiles/namers.py | 46 +++++ 2 files changed, 233 insertions(+), 82 deletions(-) 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 - A picture of me + + +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,