Ticket #4115: contrib.thumbnails.patch

File contrib.thumbnails.patch, 27.3 KB (added by Chris Beaven, 17 years ago)

First cut... no tests but read the documentation for the low-down

  • django/contrib/thumbnails/base.py

     
     1from django.conf import settings
     2import os
     3from StringIO import StringIO
     4from PIL import Image
     5from exceptions import ThumbnailNoData, ThumbnailInvalidImage
     6from methods import scale
     7
     8__all__ = ('Thumbnail',)
     9
     10class Thumbnail(object):
     11    method = scale
     12    base_url = settings.MEDIA_URL
     13    root = settings.MEDIA_ROOT
     14
     15    def __init__(self, filename='', data=None, overwrite=False, size=None):
     16        self._data = data
     17        if size:
     18            self.size = size
     19        self.filename = filename
     20        self.overwrite = overwrite
     21        if data:
     22            # If data was given and the thumbnail does not already exist,
     23            # generate thumbnail image now.
     24            self.make_thumbnail(data)
     25
     26    def get_filename(self):
     27        return self._filename
     28    def set_filename(self, filename):
     29        if filename:
     30            filename = filename % {'x': self.size[0], 'y': self.size[1], 'method': self.method.__name__}
     31            filename = os.path.normpath(filename).lstrip(os.sep)
     32            if os.path.splitext(filename)[1] != '.jpg':
     33                filename.append('.jpg')
     34            filename = os.path.join(self.root, filename)
     35        self._filename = filename
     36    filename = property(get_filename, set_filename)
     37
     38    def get_thumbnail(self):
     39        if hasattr(self, '_thumbnail'):
     40            return self._thumbnail
     41        try:
     42            img = Image.open(self.filename)
     43        except IOError, msg:
     44            raise ThumbnailInvalidImage(msg)
     45        self._thumbnail = img
     46        return img
     47    thumbnail = property(get_thumbnail)
     48
     49    def make_thumbnail(self, data):
     50        if self.overwrite or not os.path.isfile(self.filename):
     51            try:
     52                original = Image.open(StringIO(data))
     53            except IOError, msg:
     54                raise ThumbnailInvalidImage(msg)
     55            self._original_image = original
     56            thumbnail = self.method()
     57            self._thumbnail = thumbnail
     58            if self.filename:
     59                thumbnail.save(self.filename, "JPEG")
     60
     61    def get_url(self):
     62        if hasattr(self, '_url'):
     63            return self._url
     64        filename = self.filename
     65        if not os.path.isfile(filename):
     66            raise ThumbnailNoData
     67        url = os.path.normpath(filename[len(self.root):]).lstrip(os.sep)
     68        url = os.path.join(self.base_url, url)
     69        if os.sep != '/':
     70            url = url.replace(os.sep, '/')
     71        self._url = url
     72        return url
     73    url = property(get_url)
     74
     75    # Make the object will output it's url to Django templates.
     76    __str__ = get_url
     77
     78    def get_original_image(self):
     79        if hasattr(self, '_original_image'):
     80            return self._original_image
     81        raise ThumbnailNoData
     82    original_image = property(get_original_image)
     83
     84    def delete(self):
     85        if os.path.isfile(self.filename):
     86            os.remove(self.filename)
     87        self.filename = ''
  • django/contrib/thumbnails/__init__.py

     
     1from base import *
     2from exceptions import *
     3from methods import *
     4 No newline at end of file
  • django/contrib/thumbnails/exceptions.py

     
     1class ThumbnailException(Exception):
     2    pass
     3
     4class ThumbnailNoData(ThumbnailException):
     5    pass
     6
     7class ThumbnailTooSmall(ThumbnailException):
     8    pass
     9
     10class ThumbnailInvalidImage(ThumbnailException):
     11    pass
  • django/contrib/thumbnails/methods.py

     
     1from PIL import Image
     2from exceptions import ThumbnailTooSmall
     3
     4
     5def scale(thumbnail):
     6    """ Normal PIL thumbnail """
     7    img = thumbnail.original_image
     8    size = thumbnail.size
     9    if img.size[0] < size[0] and img.size[1] < size[1]:
     10        raise ThumbnailTooSmall('Image should be at least %s wide or %s high' % size)
     11    img.thumbnail(size, Image.ANTIALIAS)
     12    return img
     13
     14
     15def crop(thumbnail):
     16    """ Crop the image down to the same ratio as `size` """
     17    img = thumbnail.original_image
     18    size = thumbnail.size
     19
     20    if img.size[0] < size[0] or img.size[1] < size[1]:
     21        raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size)
     22
     23    image_x, image_y = img.size
     24
     25    crop_ratio = size[0] / float(size[1])
     26    image_ratio = image_x / float(image_y)
     27
     28    if crop_ratio < image_ratio:
     29        # x needs to shrink
     30        top = 0
     31        bottom = image_y
     32        crop_width = int(image_y * crop_ratio)
     33        left = (image_x - crop_width) // 2
     34        right = left + crop_width
     35    else:
     36        # y needs to shrink
     37        left = 0
     38        right = image_x
     39        crop_height = int(image_x * crop_ratio)
     40        top = (image_y - crop_height) // 2
     41        bottom = top + crop_height
     42
     43    img = img.crop((left, top, right, bottom))
     44    return img.resize(size, Image.ANTIALIAS)
     45
     46
     47def squash(thumbnail):
     48    """ Resize the image down to exactly `size` (changes ratio) """
     49    img = thumbnail.original_image
     50    size = thumbnail.size
     51    if img.size[0] < size[0] or img.size[1] < size[1]:
     52        raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size)
     53    return img.resize(size, Image.ANTIALIAS)
  • django/contrib/thumbnails/__init__.py

     
     1from base import *
     2from exceptions import *
     3from methods import *
     4 No newline at end of file
  • django/contrib/thumbnails/base.py

     
     1from django.conf import settings
     2import os
     3from StringIO import StringIO
     4from PIL import Image
     5from exceptions import ThumbnailNoData, ThumbnailInvalidImage
     6from methods import scale
     7
     8__all__ = ('Thumbnail',)
     9
     10class Thumbnail(object):
     11    method = scale
     12    base_url = settings.MEDIA_URL
     13    root = settings.MEDIA_ROOT
     14
     15    def __init__(self, filename='', data=None, overwrite=False, size=None):
     16        self._data = data
     17        if size:
     18            self.size = size
     19        self.filename = filename
     20        self.overwrite = overwrite
     21        if data:
     22            # If data was given and the thumbnail does not already exist,
     23            # generate thumbnail image now.
     24            self.make_thumbnail(data)
     25
     26    def get_filename(self):
     27        return self._filename
     28    def set_filename(self, filename):
     29        if filename:
     30            filename = filename % {'x': self.size[0], 'y': self.size[1], 'method': self.method.__name__}
     31            filename = os.path.normpath(filename).lstrip(os.sep)
     32            if os.path.splitext(filename)[1] != '.jpg':
     33                filename.append('.jpg')
     34            filename = os.path.join(self.root, filename)
     35        self._filename = filename
     36    filename = property(get_filename, set_filename)
     37
     38    def get_thumbnail(self):
     39        if hasattr(self, '_thumbnail'):
     40            return self._thumbnail
     41        try:
     42            img = Image.open(self.filename)
     43        except IOError, msg:
     44            raise ThumbnailInvalidImage(msg)
     45        self._thumbnail = img
     46        return img
     47    thumbnail = property(get_thumbnail)
     48
     49    def make_thumbnail(self, data):
     50        if self.overwrite or not os.path.isfile(self.filename):
     51            try:
     52                original = Image.open(StringIO(data))
     53            except IOError, msg:
     54                raise ThumbnailInvalidImage(msg)
     55            self._original_image = original
     56            thumbnail = self.method()
     57            self._thumbnail = thumbnail
     58            if self.filename:
     59                thumbnail.save(self.filename, "JPEG")
     60
     61    def get_url(self):
     62        if hasattr(self, '_url'):
     63            return self._url
     64        filename = self.filename
     65        if not os.path.isfile(filename):
     66            raise ThumbnailNoData
     67        url = os.path.normpath(filename[len(self.root):]).lstrip(os.sep)
     68        url = os.path.join(self.base_url, url)
     69        if os.sep != '/':
     70            url = url.replace(os.sep, '/')
     71        self._url = url
     72        return url
     73    url = property(get_url)
     74
     75    # Make the object will output it's url to Django templates.
     76    __str__ = get_url
     77
     78    def get_original_image(self):
     79        if hasattr(self, '_original_image'):
     80            return self._original_image
     81        raise ThumbnailNoData
     82    original_image = property(get_original_image)
     83
     84    def delete(self):
     85        if os.path.isfile(self.filename):
     86            os.remove(self.filename)
     87        self.filename = ''
  • django/contrib/thumbnails/exceptions.py

     
     1class ThumbnailException(Exception):
     2    pass
     3
     4class ThumbnailNoData(ThumbnailException):
     5    pass
     6
     7class ThumbnailTooSmall(ThumbnailException):
     8    pass
     9
     10class ThumbnailInvalidImage(ThumbnailException):
     11    pass
  • django/contrib/thumbnails/methods.py

     
     1from PIL import Image
     2from exceptions import ThumbnailTooSmall
     3
     4
     5def scale(thumbnail):
     6    """ Normal PIL thumbnail """
     7    img = thumbnail.original_image
     8    size = thumbnail.size
     9    if img.size[0] < size[0] and img.size[1] < size[1]:
     10        raise ThumbnailTooSmall('Image should be at least %s wide or %s high' % size)
     11    img.thumbnail(size, Image.ANTIALIAS)
     12    return img
     13
     14
     15def crop(thumbnail):
     16    """ Crop the image down to the same ratio as `size` """
     17    img = thumbnail.original_image
     18    size = thumbnail.size
     19
     20    if img.size[0] < size[0] or img.size[1] < size[1]:
     21        raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size)
     22
     23    image_x, image_y = img.size
     24
     25    crop_ratio = size[0] / float(size[1])
     26    image_ratio = image_x / float(image_y)
     27
     28    if crop_ratio < image_ratio:
     29        # x needs to shrink
     30        top = 0
     31        bottom = image_y
     32        crop_width = int(image_y * crop_ratio)
     33        left = (image_x - crop_width) // 2
     34        right = left + crop_width
     35    else:
     36        # y needs to shrink
     37        left = 0
     38        right = image_x
     39        crop_height = int(image_x * crop_ratio)
     40        top = (image_y - crop_height) // 2
     41        bottom = top + crop_height
     42
     43    img = img.crop((left, top, right, bottom))
     44    return img.resize(size, Image.ANTIALIAS)
     45
     46
     47def squash(thumbnail):
     48    """ Resize the image down to exactly `size` (changes ratio) """
     49    img = thumbnail.original_image
     50    size = thumbnail.size
     51    if img.size[0] < size[0] or img.size[1] < size[1]:
     52        raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size)
     53    return img.resize(size, Image.ANTIALIAS)
  • django/contrib/thumbnails/templatetags/thumbnails.py

     
     1from django.template import Library
     2from django.contrib.thumbnails import Thumbnail, crop, squash, ThumbnailException
     3from django.conf import settings
     4import os.path
     5
     6register = Library()
     7
     8class ThumbnailCrop(Thumbnail):
     9    method = crop
     10
     11class ThumbnailSquash(Thumbnail):
     12    method = squash
     13
     14
     15#@register.filter
     16def thumbnail(file, size):
     17    return create_thumbnail(Thumbnail, file, size)
     18register.filter(thumbnail)
     19
     20
     21#@register.filter
     22def thumbnail_crop(file, size):
     23    return create_thumbnail(ThumbnailCrop, file, size)
     24register.filter(thumbnail_crop)
     25
     26
     27#@register.filter
     28def thumbnail_squash(file, size):
     29    return create_thumbnail(ThumbnailSquash, file, size)
     30register.filter(thumbnail_squash)
     31
     32
     33def create_thumbnail(thumbnail_cls, file, size_string):
     34    """
     35    Creates a thumbnail image for the file (which must exist on MEDIA_ROOT)
     36    and returns a url to this image.
     37   
     38    If the thumbnail image is not found, an empty string will be returned.
     39    """
     40    # Define the size.
     41    size = [int(x) for x in size_string.split('x')]
     42   
     43    # Define the filename, then create the thumbnail object.
     44    basename, ext = os.path.splitext(file)
     45    thumbnail_filename = '%s_%s_%%(method)s%s' % (basename, size_string, ext)
     46    original_filename = os.path.join(settings.MEDIA_ROOT, file)
     47   
     48    # See if the thumbnail exists already (and is newer than the
     49    # original filename).
     50    try:
     51        thumbnail = thumbnail_cls(thumbnail_filename, size=size)
     52        if os.path.getmtime(original_filename) > os.path.getmtime(thumbnail.filename):
     53            thumbnail.delete()
     54        else:
     55            return thumbnail.url
     56    except (ThumbnailException, OSError):
     57        # Couldn't get the thumbnail (or something else went wrong).
     58        pass
     59
     60    # Read the original file from disk.
     61    try:
     62        data = open(original_filename, 'rb').read()
     63    except OSError:
     64        # Couldn't read the original file.
     65        return ''
     66   
     67    # Generate the thumbnail.
     68    try:
     69        thumbnail = thumbnail_cls(thumbnail_filename, data, size=size)
     70    except ThumbnailException:
     71        return ''
     72
     73    return thumbnail
  • django/contrib/thumbnails/templatetags/thumbnails.py

     
     1from django.template import Library
     2from django.contrib.thumbnails import Thumbnail, crop, squash, ThumbnailException
     3from django.conf import settings
     4import os.path
     5
     6register = Library()
     7
     8class ThumbnailCrop(Thumbnail):
     9    method = crop
     10
     11class ThumbnailSquash(Thumbnail):
     12    method = squash
     13
     14
     15#@register.filter
     16def thumbnail(file, size):
     17    return create_thumbnail(Thumbnail, file, size)
     18register.filter(thumbnail)
     19
     20
     21#@register.filter
     22def thumbnail_crop(file, size):
     23    return create_thumbnail(ThumbnailCrop, file, size)
     24register.filter(thumbnail_crop)
     25
     26
     27#@register.filter
     28def thumbnail_squash(file, size):
     29    return create_thumbnail(ThumbnailSquash, file, size)
     30register.filter(thumbnail_squash)
     31
     32
     33def create_thumbnail(thumbnail_cls, file, size_string):
     34    """
     35    Creates a thumbnail image for the file (which must exist on MEDIA_ROOT)
     36    and returns a url to this image.
     37   
     38    If the thumbnail image is not found, an empty string will be returned.
     39    """
     40    # Define the size.
     41    size = [int(x) for x in size_string.split('x')]
     42   
     43    # Define the filename, then create the thumbnail object.
     44    basename, ext = os.path.splitext(file)
     45    thumbnail_filename = '%s_%s_%%(method)s%s' % (basename, size_string, ext)
     46    original_filename = os.path.join(settings.MEDIA_ROOT, file)
     47   
     48    # See if the thumbnail exists already (and is newer than the
     49    # original filename).
     50    try:
     51        thumbnail = thumbnail_cls(thumbnail_filename, size=size)
     52        if os.path.getmtime(original_filename) > os.path.getmtime(thumbnail.filename):
     53            thumbnail.delete()
     54        else:
     55            return thumbnail.url
     56    except (ThumbnailException, OSError):
     57        # Couldn't get the thumbnail (or something else went wrong).
     58        pass
     59
     60    # Read the original file from disk.
     61    try:
     62        data = open(original_filename, 'rb').read()
     63    except OSError:
     64        # Couldn't read the original file.
     65        return ''
     66   
     67    # Generate the thumbnail.
     68    try:
     69        thumbnail = thumbnail_cls(thumbnail_filename, data, size=size)
     70    except ThumbnailException:
     71        return ''
     72
     73    return thumbnail
  • docs/thumbnails.txt

     
     1=========================
     2django.contrib.thumbnails
     3=========================
     4
     5The ``django.contrib.thumbnails`` package, part of the `"django.contrib" add-ons`_,
     6provides a way of thumbnailing images.
     7
     8It requires the Python Imaging Library (PIL_).
     9
     10.. _"django.contrib" add-ons: ../add_ons/
     11.. _PIL: http://www.pythonware.com/products/pil/
     12
     13Template filters
     14================
     15
     16To use these template filters, add ``'django.contrib.thumbnails'`` to your
     17``INSTALLED_APPS`` setting. Once you've done that, use
     18``{% load thumbnails %}`` in a template to give your template access to the
     19filters.
     20
     21The filters, all very similar in behaviour, are:
     22
     23 * ``thumbnail``
     24 * ``thumbnail_crop``
     25 * ``thumbnail_squash``
     26
     27The only difference between them is the `Thumbnail methods`_ that they use.
     28
     29Using the thumbnail filters
     30---------------------------
     31
     32Usage::
     33
     34    <img src="{{ object.imagefield|thumbnail:"150x100" }}" />
     35
     36The filter is applied to a image field (not the url get from
     37``get_[field]_url`` method of the model). Supposing the imagefield filename is
     38``'image.jpg'``, it creates a thumbnailed image file proportionally resized
     39down to a maximum of 150 pixels wide and 100 pixels high called
     40``'image_150x100_scale.jpg'`` in the same location as the original image
     41and returns the URL to this thumbnail image.
     42
     43The ``thumbnail_crop`` works exactly the same way but uses the crop method
     44(and the filename would be called ``'image_150x100_crop.jpg'``). Similarly,
     45``thumbnail_squash`` resizes the image to exactly the dimensions given
     46(``'image_150x100_squash.jpg'``).
     47
     48If the thumbnail filename already exists, it is only overwritten if the date
     49of the the original image file is newer than the thumbnail file.
     50
     51Rather than just outputting the url, you can reference any other properties
     52(see the ```Thumbnail`` object properties`_ section below for a complete
     53list)::
     54
     55    {% with object.imagefield|thumbnail:"150x100" as thumb %}
     56    <img src="{{ thumb.url }}" width="{{ thumb.thumbnail.size.0 }}" height="{{ thumb.size.1 }}" />
     57    {% endwith %}
     58
     59
     60Creating a thumbnail
     61====================
     62
     63The rest of this documentation deals with lower-level usage of thumbnails.
     64
     65To create a thumbnail object, simply call the ``Thumbnail`` class::
     66
     67    >>> from django.contrib.thumbnails import *
     68    >>> thumbnail = Thumbnail(filename, data, size=(100, 100))
     69
     70The thumbnail object takes the following arguments:
     71
     72    =============== ===========================================================
     73     Argument        Description
     74    =============== ===========================================================
     75
     76    ``filename``    A string containing the path and filename to use when
     77                    saving or retreiving this thumbnail image from disk
     78                    (relative to the ``Thumbnail`` object's ``root`` property
     79                    which defaults to ``settings.MEDIA_ROOT``).
     80
     81                    For advanced usage, see the ```Thumbnail```_ property
     82                    section.
     83
     84    ``data``        A string or stream of the original image object to be
     85                    thumbnailed. If not provided and a file matching the
     86                    thumbnail can not be found, ``TemplateNoData`` will be
     87                    raised.
     88
     89                    Example::
     90
     91                        >>> Thumbnail('%(method)/s%(x)sx%(y)s/test.jpg', size=(60, 40)).filename
     92                        '.../scale/60x40/test.jpg'
     93
     94    ``overwrite``   Set to ``True`` to overwrite the thumbnail with ``data``
     95                    even if an existing cached thumbnail is found. Defaults to
     96                    ``False``.
     97
     98    ``size``        The size for the thumbnail image. Required unless using a
     99                    subclass which provides a default ``size`` (see the
     100                    `Custom thumbnails`_ section below).
     101
     102``Thumbnail`` object properties
     103===============================
     104
     105The thumbnail object which is created provides the following properties and
     106functions:
     107
     108``filename``
     109------------
     110
     111Reading this property returns the full path and filename to this thumbnail
     112image.
     113
     114When you set this property, the filename string you provide is internally
     115appended to the ``Thumbnail`` object's ``root`` property.
     116
     117You can use string formatting to generate the filename based on the
     118thumbnailing method and size:
     119
     120  * ``%(x)s`` for the thumbnail target width,
     121  * ``%(y)s`` for the thumbnail target height,
     122  * ``%(method)s`` for the thumbnailing method.
     123
     124For example::
     125
     126    >>> Thumbnail('%(media)/s%(x)sx%(y)s/test.jpg', size=(60, 40)).filename
     127    '.../scale/60x40/test.jpg'
     128
     129Note: thumbnailed images are always saved as JPEG images, so if the filename
     130string does not end in `'.jpg'`, this will be automatically appended to the
     131thumbnail's filename.
     132
     133``original_image``
     134------------------
     135
     136This read-only property returns a PIL ``Image`` containing the original
     137image (passed in with ``data``).
     138
     139``thumbnail``
     140-------------
     141
     142This read-only property returns a PIL ``Image`` containing the thumbnail
     143image.
     144
     145``url``
     146-------
     147
     148This read-only property returns the full url this thumbnail image.
     149
     150It is generated by appending the parsed ``filename`` string to the
     151``Template`` object's ``base_url`` property.
     152
     153``delete()``
     154------------
     155
     156Call this function to delete the thumbnail file if it exists on the disk.
     157
     158Custom thumbnails
     159=================
     160
     161Similar to newforms, you can create a subclass to override the default
     162properties of the ``Thumbnail`` base class::
     163
     164    from django.contrib.thumbnails import Thumbnail
     165
     166    class MyThumbnail(Thumbnail):
     167        size = (100, 100)
     168
     169Here are the properties you can provide to your subclass:
     170
     171    =============== ===========================================================
     172     Property        Description
     173    =============== ===========================================================
     174
     175    ``size``        Default size for creating thumbnails (no default).
     176    ``base_url``    Base url for thumbnails (default is ``settings.MEDIA_URL``).
     177    ``root``        Base directory for thumbnails (default is
     178                    ``settings.MEDIA_ROOT``).
     179    ``method``      The thumbnailing funciton to use (default is ``scale``).
     180                    See the `Thumbnail methods`_ section below.
     181
     182Thumbnail methods
     183=================
     184
     185There are two thumbnailing methods available in
     186``django.contrib.thumbnails.methods``
     187
     188``crop()``
     189----------
     190
     191This method crops the image height or width to match the ratio of the thumbnail
     192``size`` and then resizes it down to exactly the dimensions of ``size``.
     193
     194It requires the original image to be both as wide and as high as ``size``.
     195
     196``scale()``
     197-----------
     198
     199This is the normal PIL scaling method of proportionally resizing the image down
     200to no greater than the thumbnail ``size`` dimensions.
     201
     202It requires the original image to be either as wide or as high as ``size``.
     203
     204``squash()``
     205------------
     206
     207This method resizes the image down to exactly the dimensions given. This will
     208potentially squash or stretch the image.
     209
     210It requires the original image to be both as wide and as high as ``size``.
     211
     212Making your own methods
     213-----------------------
     214
     215To make your own thumbnailing function, create a function which accepts one
     216parameter (``thumbnail``) and returns a PIL ``Image``.
     217
     218The ``thumbnail`` parameter will be a ``Thumbnail`` object, so you can use it
     219to get the original image (it will raise ``ThumbnailNoData`` if no data was
     220provided) and the thumbnail size::
     221
     222    img = thumbnail.original_image
     223    size = thumbnail.size
     224
     225Exceptions
     226==========
     227
     228The following exceptions (all found in ``django.contrib.thumbnails.exceptions``
     229and all subclasses of ``ThumbnailException``) could be raised when using the
     230``Thumbnail`` object:
     231
     232    =========================  ================================================
     233     Exception                  Reason
     234    =========================  ================================================
     235
     236    ``ThumbnailNoData``        Tried to get the ``original_image`` when no
     237                               ``data`` was provided or tried to get the
     238                               ``url`` when the file did not exist and no
     239                               ``data`` was provided.
     240    ``ThumbnailTooSmall``      The ``original_image`` was too small to
     241                               thumbnail using the given thumbnailing method.
     242    ``ThumbnailInvalidImage``  The ``data`` provided could not be decoded to
     243                               a valid image format (or more rarely, using
     244                               ``thumbnail`` to retreive an existing thumbnail
     245                               file from disk which could not be decoded to a
     246                               valid image format).
     247
     248Putting it all together
     249=======================
     250
     251Here is a snippet of an example view which receives an image file from the user
     252and saves a thumbnail of this image to a file named ``[userid].jpg``::
     253
     254    from django.contrib.thumbnails import Thumbnail, crop, ThumbnailException
     255
     256    class ProfileThumbnail(Thumbnail):
     257        size = (100, 100)
     258        method = crop
     259
     260    def profile_image(request, id):
     261        profile = get_object_or_404(Profile, pk=id)
     262        if request.method == 'POST':
     263            image = request.FILES.get('profile_image')
     264            profile.has_image = False
     265            if image:
     266                filename = str(profile.id)
     267                try:
     268                    thumbnail = ProfileThumbnail(filename, image['content'])
     269                    profile.has_image = True
     270                except ThumbnailException:
     271                    pass
     272            profile.save()
     273            return HttpResponseRedirect('../')
     274        ...
Back to Top