Index: django/contrib/thumbnails/base.py
===================================================================
--- django/contrib/thumbnails/base.py	(revision 0)
+++ django/contrib/thumbnails/base.py	(revision 0)
@@ -0,0 +1,87 @@
+from django.conf import settings
+import os
+from StringIO import StringIO
+from PIL import Image
+from exceptions import ThumbnailNoData, ThumbnailInvalidImage
+from methods import scale
+
+__all__ = ('Thumbnail',)
+
+class Thumbnail(object):
+    method = scale
+    base_url = settings.MEDIA_URL
+    root = settings.MEDIA_ROOT
+
+    def __init__(self, filename='', data=None, overwrite=False, size=None):
+        self._data = data
+        if size:
+            self.size = size
+        self.filename = filename
+        self.overwrite = overwrite
+        if data:
+            # If data was given and the thumbnail does not already exist,
+            # generate thumbnail image now.
+            self.make_thumbnail(data)
+
+    def get_filename(self):
+        return self._filename
+    def set_filename(self, filename):
+        if filename:
+            filename = filename % {'x': self.size[0], 'y': self.size[1], 'method': self.method.__name__}
+            filename = os.path.normpath(filename).lstrip(os.sep)
+            if os.path.splitext(filename)[1] != '.jpg':
+                filename.append('.jpg')
+            filename = os.path.join(self.root, filename)
+        self._filename = filename
+    filename = property(get_filename, set_filename)
+
+    def get_thumbnail(self):
+        if hasattr(self, '_thumbnail'):
+            return self._thumbnail
+        try:
+            img = Image.open(self.filename)
+        except IOError, msg:
+            raise ThumbnailInvalidImage(msg)
+        self._thumbnail = img
+        return img
+    thumbnail = property(get_thumbnail)
+
+    def make_thumbnail(self, data):
+        if self.overwrite or not os.path.isfile(self.filename):
+            try:
+                original = Image.open(StringIO(data))
+            except IOError, msg:
+                raise ThumbnailInvalidImage(msg)
+            self._original_image = original
+            thumbnail = self.method()
+            self._thumbnail = thumbnail
+            if self.filename:
+                thumbnail.save(self.filename, "JPEG")
+
+    def get_url(self):
+        if hasattr(self, '_url'):
+            return self._url
+        filename = self.filename
+        if not os.path.isfile(filename):
+            raise ThumbnailNoData
+        url = os.path.normpath(filename[len(self.root):]).lstrip(os.sep)
+        url = os.path.join(self.base_url, url)
+        if os.sep != '/':
+            url = url.replace(os.sep, '/')
+        self._url = url
+        return url
+    url = property(get_url)
+
+    # Make the object will output it's url to Django templates.
+    __str__ = get_url
+
+    def get_original_image(self):
+        if hasattr(self, '_original_image'):
+            return self._original_image
+        raise ThumbnailNoData
+    original_image = property(get_original_image)
+
+    def delete(self):
+        if os.path.isfile(self.filename):
+            os.remove(self.filename)
+        self.filename = ''
Index: django/contrib/thumbnails/__init__.py
===================================================================
--- django/contrib/thumbnails/__init__.py	(revision 0)
+++ django/contrib/thumbnails/__init__.py	(revision 0)
@@ -0,0 +1,3 @@
+from base import *
+from exceptions import *
+from methods import *
\ No newline at end of file
Index: django/contrib/thumbnails/exceptions.py
===================================================================
--- django/contrib/thumbnails/exceptions.py	(revision 0)
+++ django/contrib/thumbnails/exceptions.py	(revision 0)
@@ -0,0 +1,11 @@
+class ThumbnailException(Exception):
+    pass
+
+class ThumbnailNoData(ThumbnailException):
+    pass
+
+class ThumbnailTooSmall(ThumbnailException):
+    pass
+
+class ThumbnailInvalidImage(ThumbnailException):
+    pass
Index: django/contrib/thumbnails/methods.py
===================================================================
--- django/contrib/thumbnails/methods.py	(revision 0)
+++ django/contrib/thumbnails/methods.py	(revision 0)
@@ -0,0 +1,53 @@
+from PIL import Image
+from exceptions import ThumbnailTooSmall
+
+
+def scale(thumbnail):
+    """ Normal PIL thumbnail """
+    img = thumbnail.original_image
+    size = thumbnail.size
+    if img.size[0] < size[0] and img.size[1] < size[1]:
+        raise ThumbnailTooSmall('Image should be at least %s wide or %s high' % size)
+    img.thumbnail(size, Image.ANTIALIAS)
+    return img
+
+
+def crop(thumbnail):
+    """ Crop the image down to the same ratio as `size` """
+    img = thumbnail.original_image
+    size = thumbnail.size
+
+    if img.size[0] < size[0] or img.size[1] < size[1]:
+        raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size)
+
+    image_x, image_y = img.size
+
+    crop_ratio = size[0] / float(size[1])
+    image_ratio = image_x / float(image_y)
+
+    if crop_ratio < image_ratio:
+        # x needs to shrink
+        top = 0
+        bottom = image_y
+        crop_width = int(image_y * crop_ratio)
+        left = (image_x - crop_width) // 2
+        right = left + crop_width
+    else:
+        # y needs to shrink
+        left = 0
+        right = image_x
+        crop_height = int(image_x * crop_ratio)
+        top = (image_y - crop_height) // 2
+        bottom = top + crop_height
+
+    img = img.crop((left, top, right, bottom))
+    return img.resize(size, Image.ANTIALIAS)
+
+
+def squash(thumbnail):
+    """ Resize the image down to exactly `size` (changes ratio) """
+    img = thumbnail.original_image
+    size = thumbnail.size
+    if img.size[0] < size[0] or img.size[1] < size[1]:
+        raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size)
+    return img.resize(size, Image.ANTIALIAS)
Index: django/contrib/thumbnails/__init__.py
===================================================================
--- django/contrib/thumbnails/__init__.py	(revision 0)
+++ django/contrib/thumbnails/__init__.py	(revision 0)
@@ -0,0 +1,3 @@
+from base import *
+from exceptions import *
+from methods import *
\ No newline at end of file
Index: django/contrib/thumbnails/base.py
===================================================================
--- django/contrib/thumbnails/base.py	(revision 0)
+++ django/contrib/thumbnails/base.py	(revision 0)
@@ -0,0 +1,87 @@
+from django.conf import settings
+import os
+from StringIO import StringIO
+from PIL import Image
+from exceptions import ThumbnailNoData, ThumbnailInvalidImage
+from methods import scale
+
+__all__ = ('Thumbnail',)
+
+class Thumbnail(object):
+    method = scale
+    base_url = settings.MEDIA_URL
+    root = settings.MEDIA_ROOT
+
+    def __init__(self, filename='', data=None, overwrite=False, size=None):
+        self._data = data
+        if size:
+            self.size = size
+        self.filename = filename
+        self.overwrite = overwrite
+        if data:
+            # If data was given and the thumbnail does not already exist,
+            # generate thumbnail image now.
+            self.make_thumbnail(data)
+
+    def get_filename(self):
+        return self._filename
+    def set_filename(self, filename):
+        if filename:
+            filename = filename % {'x': self.size[0], 'y': self.size[1], 'method': self.method.__name__}
+            filename = os.path.normpath(filename).lstrip(os.sep)
+            if os.path.splitext(filename)[1] != '.jpg':
+                filename.append('.jpg')
+            filename = os.path.join(self.root, filename)
+        self._filename = filename
+    filename = property(get_filename, set_filename)
+
+    def get_thumbnail(self):
+        if hasattr(self, '_thumbnail'):
+            return self._thumbnail
+        try:
+            img = Image.open(self.filename)
+        except IOError, msg:
+            raise ThumbnailInvalidImage(msg)
+        self._thumbnail = img
+        return img
+    thumbnail = property(get_thumbnail)
+
+    def make_thumbnail(self, data):
+        if self.overwrite or not os.path.isfile(self.filename):
+            try:
+                original = Image.open(StringIO(data))
+            except IOError, msg:
+                raise ThumbnailInvalidImage(msg)
+            self._original_image = original
+            thumbnail = self.method()
+            self._thumbnail = thumbnail
+            if self.filename:
+                thumbnail.save(self.filename, "JPEG")
+
+    def get_url(self):
+        if hasattr(self, '_url'):
+            return self._url
+        filename = self.filename
+        if not os.path.isfile(filename):
+            raise ThumbnailNoData
+        url = os.path.normpath(filename[len(self.root):]).lstrip(os.sep)
+        url = os.path.join(self.base_url, url)
+        if os.sep != '/':
+            url = url.replace(os.sep, '/')
+        self._url = url
+        return url
+    url = property(get_url)
+
+    # Make the object will output it's url to Django templates.
+    __str__ = get_url
+
+    def get_original_image(self):
+        if hasattr(self, '_original_image'):
+            return self._original_image
+        raise ThumbnailNoData
+    original_image = property(get_original_image)
+
+    def delete(self):
+        if os.path.isfile(self.filename):
+            os.remove(self.filename)
+        self.filename = ''
Index: django/contrib/thumbnails/exceptions.py
===================================================================
--- django/contrib/thumbnails/exceptions.py	(revision 0)
+++ django/contrib/thumbnails/exceptions.py	(revision 0)
@@ -0,0 +1,11 @@
+class ThumbnailException(Exception):
+    pass
+
+class ThumbnailNoData(ThumbnailException):
+    pass
+
+class ThumbnailTooSmall(ThumbnailException):
+    pass
+
+class ThumbnailInvalidImage(ThumbnailException):
+    pass
Index: django/contrib/thumbnails/methods.py
===================================================================
--- django/contrib/thumbnails/methods.py	(revision 0)
+++ django/contrib/thumbnails/methods.py	(revision 0)
@@ -0,0 +1,53 @@
+from PIL import Image
+from exceptions import ThumbnailTooSmall
+
+
+def scale(thumbnail):
+    """ Normal PIL thumbnail """
+    img = thumbnail.original_image
+    size = thumbnail.size
+    if img.size[0] < size[0] and img.size[1] < size[1]:
+        raise ThumbnailTooSmall('Image should be at least %s wide or %s high' % size)
+    img.thumbnail(size, Image.ANTIALIAS)
+    return img
+
+
+def crop(thumbnail):
+    """ Crop the image down to the same ratio as `size` """
+    img = thumbnail.original_image
+    size = thumbnail.size
+
+    if img.size[0] < size[0] or img.size[1] < size[1]:
+        raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size)
+
+    image_x, image_y = img.size
+
+    crop_ratio = size[0] / float(size[1])
+    image_ratio = image_x / float(image_y)
+
+    if crop_ratio < image_ratio:
+        # x needs to shrink
+        top = 0
+        bottom = image_y
+        crop_width = int(image_y * crop_ratio)
+        left = (image_x - crop_width) // 2
+        right = left + crop_width
+    else:
+        # y needs to shrink
+        left = 0
+        right = image_x
+        crop_height = int(image_x * crop_ratio)
+        top = (image_y - crop_height) // 2
+        bottom = top + crop_height
+
+    img = img.crop((left, top, right, bottom))
+    return img.resize(size, Image.ANTIALIAS)
+
+
+def squash(thumbnail):
+    """ Resize the image down to exactly `size` (changes ratio) """
+    img = thumbnail.original_image
+    size = thumbnail.size
+    if img.size[0] < size[0] or img.size[1] < size[1]:
+        raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size)
+    return img.resize(size, Image.ANTIALIAS)
Index: django/contrib/thumbnails/templatetags/__init__.py
===================================================================
Index: django/contrib/thumbnails/templatetags/thumbnails.py
===================================================================
--- django/contrib/thumbnails/templatetags/thumbnails.py	(revision 0)
+++ django/contrib/thumbnails/templatetags/thumbnails.py	(revision 0)
@@ -0,0 +1,73 @@
+from django.template import Library
+from django.contrib.thumbnails import Thumbnail, crop, squash, ThumbnailException
+from django.conf import settings
+import os.path
+
+register = Library()
+
+class ThumbnailCrop(Thumbnail):
+    method = crop
+
+class ThumbnailSquash(Thumbnail):
+    method = squash
+
+
+#@register.filter
+def thumbnail(file, size):
+    return create_thumbnail(Thumbnail, file, size)
+register.filter(thumbnail)
+
+
+#@register.filter
+def thumbnail_crop(file, size):
+    return create_thumbnail(ThumbnailCrop, file, size)
+register.filter(thumbnail_crop)
+
+
+#@register.filter
+def thumbnail_squash(file, size):
+    return create_thumbnail(ThumbnailSquash, file, size)
+register.filter(thumbnail_squash)
+
+
+def create_thumbnail(thumbnail_cls, file, size_string):
+    """
+    Creates a thumbnail image for the file (which must exist on MEDIA_ROOT)
+    and returns a url to this image.
+    
+    If the thumbnail image is not found, an empty string will be returned.
+    """
+    # Define the size.
+    size = [int(x) for x in size_string.split('x')]
+    
+    # Define the filename, then create the thumbnail object.
+    basename, ext = os.path.splitext(file)
+    thumbnail_filename = '%s_%s_%%(method)s%s' % (basename, size_string, ext)
+    original_filename = os.path.join(settings.MEDIA_ROOT, file)
+    
+    # See if the thumbnail exists already (and is newer than the
+    # original filename).
+    try:
+        thumbnail = thumbnail_cls(thumbnail_filename, size=size)
+        if os.path.getmtime(original_filename) > os.path.getmtime(thumbnail.filename):
+            thumbnail.delete()
+        else:
+            return thumbnail.url
+    except (ThumbnailException, OSError):
+        # Couldn't get the thumbnail (or something else went wrong).
+        pass
+
+    # Read the original file from disk.
+    try:
+        data = open(original_filename, 'rb').read()
+    except OSError:
+        # Couldn't read the original file.
+        return ''
+    
+    # Generate the thumbnail.
+    try:
+        thumbnail = thumbnail_cls(thumbnail_filename, data, size=size)
+    except ThumbnailException:
+        return ''
+
+    return thumbnail
Index: django/contrib/thumbnails/templatetags/__init__.py
===================================================================
Index: django/contrib/thumbnails/templatetags/thumbnails.py
===================================================================
--- django/contrib/thumbnails/templatetags/thumbnails.py	(revision 0)
+++ django/contrib/thumbnails/templatetags/thumbnails.py	(revision 0)
@@ -0,0 +1,73 @@
+from django.template import Library
+from django.contrib.thumbnails import Thumbnail, crop, squash, ThumbnailException
+from django.conf import settings
+import os.path
+
+register = Library()
+
+class ThumbnailCrop(Thumbnail):
+    method = crop
+
+class ThumbnailSquash(Thumbnail):
+    method = squash
+
+
+#@register.filter
+def thumbnail(file, size):
+    return create_thumbnail(Thumbnail, file, size)
+register.filter(thumbnail)
+
+
+#@register.filter
+def thumbnail_crop(file, size):
+    return create_thumbnail(ThumbnailCrop, file, size)
+register.filter(thumbnail_crop)
+
+
+#@register.filter
+def thumbnail_squash(file, size):
+    return create_thumbnail(ThumbnailSquash, file, size)
+register.filter(thumbnail_squash)
+
+
+def create_thumbnail(thumbnail_cls, file, size_string):
+    """
+    Creates a thumbnail image for the file (which must exist on MEDIA_ROOT)
+    and returns a url to this image.
+    
+    If the thumbnail image is not found, an empty string will be returned.
+    """
+    # Define the size.
+    size = [int(x) for x in size_string.split('x')]
+    
+    # Define the filename, then create the thumbnail object.
+    basename, ext = os.path.splitext(file)
+    thumbnail_filename = '%s_%s_%%(method)s%s' % (basename, size_string, ext)
+    original_filename = os.path.join(settings.MEDIA_ROOT, file)
+    
+    # See if the thumbnail exists already (and is newer than the
+    # original filename).
+    try:
+        thumbnail = thumbnail_cls(thumbnail_filename, size=size)
+        if os.path.getmtime(original_filename) > os.path.getmtime(thumbnail.filename):
+            thumbnail.delete()
+        else:
+            return thumbnail.url
+    except (ThumbnailException, OSError):
+        # Couldn't get the thumbnail (or something else went wrong).
+        pass
+
+    # Read the original file from disk.
+    try:
+        data = open(original_filename, 'rb').read()
+    except OSError:
+        # Couldn't read the original file.
+        return ''
+    
+    # Generate the thumbnail.
+    try:
+        thumbnail = thumbnail_cls(thumbnail_filename, data, size=size)
+    except ThumbnailException:
+        return ''
+
+    return thumbnail
Index: docs/thumbnails.txt
===================================================================
--- docs/thumbnails.txt	(revision 0)
+++ docs/thumbnails.txt	(revision 0)
@@ -0,0 +1,274 @@
+=========================
+django.contrib.thumbnails
+=========================
+
+The ``django.contrib.thumbnails`` package, part of the `"django.contrib" add-ons`_,
+provides a way of thumbnailing images.
+
+It requires the Python Imaging Library (PIL_).
+
+.. _"django.contrib" add-ons: ../add_ons/
+.. _PIL: http://www.pythonware.com/products/pil/
+
+Template filters
+================
+
+To use these template filters, add ``'django.contrib.thumbnails'`` to your
+``INSTALLED_APPS`` setting. Once you've done that, use
+``{% load thumbnails %}`` in a template to give your template access to the
+filters.
+
+The filters, all very similar in behaviour, are:
+
+ * ``thumbnail``
+ * ``thumbnail_crop``
+ * ``thumbnail_squash``
+
+The only difference between them is the `Thumbnail methods`_ that they use.
+
+Using the thumbnail filters
+---------------------------
+
+Usage::
+
+    <img src="{{ object.imagefield|thumbnail:"150x100" }}" />
+
+The filter is applied to a image field (not the url get from 
+``get_[field]_url`` method of the model). Supposing the imagefield filename is
+``'image.jpg'``, it creates a thumbnailed image file proportionally resized
+down to a maximum of 150 pixels wide and 100 pixels high called
+``'image_150x100_scale.jpg'`` in the same location as the original image
+and returns the URL to this thumbnail image.
+
+The ``thumbnail_crop`` works exactly the same way but uses the crop method
+(and the filename would be called ``'image_150x100_crop.jpg'``). Similarly,
+``thumbnail_squash`` resizes the image to exactly the dimensions given
+(``'image_150x100_squash.jpg'``).
+
+If the thumbnail filename already exists, it is only overwritten if the date
+of the the original image file is newer than the thumbnail file.
+
+Rather than just outputting the url, you can reference any other properties
+(see the ```Thumbnail`` object properties`_ section below for a complete
+list)::
+
+    {% with object.imagefield|thumbnail:"150x100" as thumb %}
+    <img src="{{ thumb.url }}" width="{{ thumb.thumbnail.size.0 }}" height="{{ thumb.size.1 }}" />
+    {% endwith %}
+
+
+Creating a thumbnail
+====================
+
+The rest of this documentation deals with lower-level usage of thumbnails.
+
+To create a thumbnail object, simply call the ``Thumbnail`` class::
+
+    >>> from django.contrib.thumbnails import *
+    >>> thumbnail = Thumbnail(filename, data, size=(100, 100))
+
+The thumbnail object takes the following arguments:
+
+    =============== ===========================================================
+     Argument        Description
+    =============== ===========================================================
+
+    ``filename``    A string containing the path and filename to use when
+                    saving or retreiving this thumbnail image from disk
+                    (relative to the ``Thumbnail`` object's ``root`` property
+                    which defaults to ``settings.MEDIA_ROOT``).
+
+                    For advanced usage, see the ```Thumbnail```_ property 
+                    section.
+
+    ``data``        A string or stream of the original image object to be
+                    thumbnailed. If not provided and a file matching the
+                    thumbnail can not be found, ``TemplateNoData`` will be
+                    raised.
+
+                    Example::
+
+                        >>> Thumbnail('%(method)/s%(x)sx%(y)s/test.jpg', size=(60, 40)).filename
+                        '.../scale/60x40/test.jpg'
+
+    ``overwrite``   Set to ``True`` to overwrite the thumbnail with ``data``
+                    even if an existing cached thumbnail is found. Defaults to
+                    ``False``.
+
+    ``size``        The size for the thumbnail image. Required unless using a
+                    subclass which provides a default ``size`` (see the
+                    `Custom thumbnails`_ section below).
+
+``Thumbnail`` object properties
+===============================
+
+The thumbnail object which is created provides the following properties and
+functions:
+
+``filename``
+------------
+
+Reading this property returns the full path and filename to this thumbnail
+image.
+
+When you set this property, the filename string you provide is internally
+appended to the ``Thumbnail`` object's ``root`` property.
+
+You can use string formatting to generate the filename based on the
+thumbnailing method and size:
+
+  * ``%(x)s`` for the thumbnail target width,
+  * ``%(y)s`` for the thumbnail target height,
+  * ``%(method)s`` for the thumbnailing method.
+
+For example::
+
+    >>> Thumbnail('%(media)/s%(x)sx%(y)s/test.jpg', size=(60, 40)).filename
+    '.../scale/60x40/test.jpg'
+
+Note: thumbnailed images are always saved as JPEG images, so if the filename
+string does not end in `'.jpg'`, this will be automatically appended to the
+thumbnail's filename.
+
+``original_image``
+------------------
+
+This read-only property returns a PIL ``Image`` containing the original
+image (passed in with ``data``).
+
+``thumbnail``
+-------------
+
+This read-only property returns a PIL ``Image`` containing the thumbnail
+image.
+
+``url``
+-------
+
+This read-only property returns the full url this thumbnail image.
+
+It is generated by appending the parsed ``filename`` string to the 
+``Template`` object's ``base_url`` property.
+
+``delete()``
+------------
+
+Call this function to delete the thumbnail file if it exists on the disk.
+
+Custom thumbnails
+=================
+
+Similar to newforms, you can create a subclass to override the default
+properties of the ``Thumbnail`` base class::
+
+    from django.contrib.thumbnails import Thumbnail
+
+    class MyThumbnail(Thumbnail):
+        size = (100, 100)
+
+Here are the properties you can provide to your subclass:
+
+    =============== ===========================================================
+     Property        Description
+    =============== ===========================================================
+
+    ``size``        Default size for creating thumbnails (no default).
+    ``base_url``    Base url for thumbnails (default is ``settings.MEDIA_URL``).
+    ``root``        Base directory for thumbnails (default is
+                    ``settings.MEDIA_ROOT``).
+    ``method``      The thumbnailing funciton to use (default is ``scale``).
+                    See the `Thumbnail methods`_ section below.
+
+Thumbnail methods
+=================
+
+There are two thumbnailing methods available in
+``django.contrib.thumbnails.methods``
+
+``crop()``
+----------
+
+This method crops the image height or width to match the ratio of the thumbnail
+``size`` and then resizes it down to exactly the dimensions of ``size``.
+
+It requires the original image to be both as wide and as high as ``size``.
+
+``scale()``
+-----------
+
+This is the normal PIL scaling method of proportionally resizing the image down
+to no greater than the thumbnail ``size`` dimensions.
+
+It requires the original image to be either as wide or as high as ``size``.
+
+``squash()``
+------------
+
+This method resizes the image down to exactly the dimensions given. This will
+potentially squash or stretch the image.
+
+It requires the original image to be both as wide and as high as ``size``.
+
+Making your own methods
+-----------------------
+
+To make your own thumbnailing function, create a function which accepts one
+parameter (``thumbnail``) and returns a PIL ``Image``.
+
+The ``thumbnail`` parameter will be a ``Thumbnail`` object, so you can use it
+to get the original image (it will raise ``ThumbnailNoData`` if no data was
+provided) and the thumbnail size::
+
+    img = thumbnail.original_image
+    size = thumbnail.size
+
+Exceptions
+==========
+
+The following exceptions (all found in ``django.contrib.thumbnails.exceptions``
+and all subclasses of ``ThumbnailException``) could be raised when using the
+``Thumbnail`` object:
+
+    =========================  ================================================
+     Exception                  Reason
+    =========================  ================================================
+
+    ``ThumbnailNoData``        Tried to get the ``original_image`` when no
+                               ``data`` was provided or tried to get the
+                               ``url`` when the file did not exist and no
+                               ``data`` was provided.
+    ``ThumbnailTooSmall``      The ``original_image`` was too small to
+                               thumbnail using the given thumbnailing method.
+    ``ThumbnailInvalidImage``  The ``data`` provided could not be decoded to
+                               a valid image format (or more rarely, using
+                               ``thumbnail`` to retreive an existing thumbnail
+                               file from disk which could not be decoded to a
+                               valid image format).
+
+Putting it all together
+=======================
+
+Here is a snippet of an example view which receives an image file from the user
+and saves a thumbnail of this image to a file named ``[userid].jpg``::
+
+    from django.contrib.thumbnails import Thumbnail, crop, ThumbnailException
+
+    class ProfileThumbnail(Thumbnail):
+        size = (100, 100)
+        method = crop
+
+    def profile_image(request, id):
+        profile = get_object_or_404(Profile, pk=id)
+        if request.method == 'POST':
+            image = request.FILES.get('profile_image')
+            profile.has_image = False
+            if image:
+                filename = str(profile.id)
+                try:
+                    thumbnail = ProfileThumbnail(filename, image['content'])
+                    profile.has_image = True
+                except ThumbnailException:
+                    pass
+            profile.save()
+            return HttpResponseRedirect('../')
+        ...
