Login

ResizeImageField

Author:
wim
Posted:
September 29, 2010
Language:
Python
Version:
1.2
Score:
5 (after 5 ratings)

ResizeImageField

(extension of RemovableImageField)

by Wim Feijen, Go2People.

What does it do?

ResizeImageField is a replacement for django's ImageField. It has two major benefits: 1. Creation of thumbnails and scaled images. 1. Extends the image upload form and adds a preview and a checkbox to remove the existing image.

It's easy to use: - Replace ImageField by ResizeImageField - No further changes are necessary

Requirements:

Working installation of PIL, the Python Imaging Library

Usage

  • add resize_image to your app
  • add resize_filters.py to your templatetags
  • in settings.py, set a PHOTO_DIR, f.e. photos/original
  • in models.py, add:
  • from settings import PHOTO_DIR
  • from resize_image import ResizeImageField
  • photo = ResizeImageField(upload_to=PHOTO_DIR, blank=True)

Scaled images will be stored in 'photos/scaled', thumbnails will be stored in 'photos/thumb'.

Access your images from your template. Add::

{% load resize_filters %} {{ address.photo.url|thumb }}

or::

{{ address.photo.url|scaled }}

Defaults

  • Scaled images are max. 200x200 pixels by default
  • Thumbnails are 50x50 pixels.

Override the default behaviour in settings.py

Scaling is done by PIL's thumbnail function, transparency is conserved.

Credits

This code is an adaptation from python snippet 636 by tomZ: "Updated Filefield / ImageField with a delete checkbox"

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#-----
# Create a file resize_image.py:
#-----

# Adapted from: http://www.djangosnippets.org/snippets/636/
# ResizeImageField stands for ScalableAndRemovableImageField
from django import forms
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext as _

import os

import Image # from PIL

ORIGINAL_NAME = os.path.basename(settings.PHOTO_DIR)
SCALED_NAME = getattr(settings, 'SCALED_NAME', 'scaled')
THUMB_NAME = getattr(settings, 'THUMB_NAME', 'thumb')

SCALED_SIZE = getattr(settings, 'SCALED_SIZE', (200, 200))
THUMB_SIZE = getattr(settings, 'SCALED_SIZE', (50, 50)) # height, width

class DeleteCheckboxWidget(forms.CheckboxInput):
    def __init__(self, *args, **kwargs):
        self.is_image = kwargs.pop('is_image')
        self.value = kwargs.pop('initial')
        super(DeleteCheckboxWidget, self).__init__(*args, **kwargs)

    def render(self, name, value, attrs=None):
        value = value or self.value
        if value:
            attrs['style'] = 'width:auto;'
            s = u'<label for="%s">%s %s</label>' % (
                    attrs['id'],
                    super(DeleteCheckboxWidget, self).render(name, False, attrs),
                    _('Delete')
                )
            if self.is_image:
                s += u'<br><img src="%s%s" height="100">' % (settings.MEDIA_URL, unicode(value).replace(ORIGINAL_NAME, SCALED_NAME, 1))
            else:
                s += u'<br><a href="%s%s">%s</a>' % (settings.MEDIA_URL, value, os.path.basename(value))
            return s
        else:
            return u''


class RemovableFileFormWidget(forms.MultiWidget):
    def __init__(self, is_image=False, initial=None, **kwargs):
        widgets = (forms.FileInput(), DeleteCheckboxWidget(is_image=is_image, initial=initial))
        super(RemovableFileFormWidget, self).__init__(widgets)

    def decompress(self, value):
        return [None, value]

class RemovableFileFormField(forms.MultiValueField):
    widget = RemovableFileFormWidget
    field = forms.FileField
    is_image = False

    def __init__(self, *args, **kwargs):
        fields = [self.field(*args, **kwargs), forms.BooleanField(required=False)]
        # Compatibility with form_for_instance
        if kwargs.get('initial'):
            initial = kwargs['initial']
        else:
            initial = None
        self.widget = self.widget(is_image=self.is_image, initial=initial)
        super(RemovableFileFormField, self).__init__(fields, label=kwargs.pop('label'), required=False)

    def compress(self, data_list):
        return data_list


class ResizeImageFormField(RemovableFileFormField):
    field = forms.ImageField
    is_image = True

class ResizeImageField(models.ImageField):

    def delete_file(self, instance, *args, **kwargs):
        '''Overwrite delete method. Delete scaled instances as well.'''
        if getattr(instance, self.attname):
            image = getattr(instance, '%s' % self.name)
            file_name = image.path
            # If the file exists and no other object of this type references it,
            # delete it from the filesystem.
            if os.path.exists(file_name) and \
                not instance.__class__._default_manager.filter(**{'%s__exact' % self.name: getattr(instance, self.attname)}).exclude(pk=instance._get_pk_val()):
                if os.path.exists(file_name):
                    os.remove(file_name)
                scaled_name = file_name.replace(ORIGINAL_NAME, SCALED_NAME, 1)
                if os.path.exists(scaled_name):
                    os.remove(scaled_name)
                thumb_name = file_name.replace(ORIGINAL_NAME, THUMB_NAME, 1)
                if os.path.exists(thumb_name):
                    os.remove(thumb_name)

    def get_internal_type(self):
        '''Copied from Django snippet example and probably incorrect.'''
        return 'FileField'

    def check_or_create_dir(self, full_path):
        '''Create dir if it does not yet exist.'''
        directory = os.path.dirname(full_path)
        if not os.path.exists(directory):
            os.makedirs(directory)
        elif not os.path.isdir(directory):
            raise IOError("%s exists and is not a directory." % directory)

    def save_form_data(self, instance, data):
        '''Save/replace or delete file. If saving, store scaled images as well.'''
        if data and data[0]: # Replace file
            self.delete_file(instance)
            super(ResizeImageField, self).save_form_data(instance, data[0])
            image = getattr(instance, '%s' % self.name)
            file_path = image.path
            img = Image.open(file_path)
            self.resize(img, file_path, SCALED_NAME, SCALED_SIZE)
            self.resize(img, file_path, THUMB_NAME, THUMB_SIZE)
                
        if data and data[1]: # Delete file
            self.delete_file(instance)
            setattr(instance, self.name, None)

    def resize(self, img, file_path, new_name, new_size):
        '''Resize image, using PIL, and save.'''
        new_path = file_path.replace(ORIGINAL_NAME, new_name, 1)
        self.check_or_create_dir(new_path)
        img.thumbnail(new_size, Image.ANTIALIAS)
        try:
            transparency = img.info['transparency']
            img.save(new_path, transparency=transparency)
        except:
            img.save(new_path)

    def formfield(self, **kwargs):
        '''Django default.'''
        defaults = {'form_class': ResizeImageFormField}
        defaults.update(kwargs)
        return super(ResizeImageField, self).formfield(**defaults)





#-----
# In settings.py:
#-----    

PHOTO_DIR = 'photos/original' # No trailing slash!


#-----
# Example models.py:
#-----

from django.db import models
  
from settings import PHOTO_DIR
    
from resize_image import ResizeImageField
    
class Address(models.Model):
    photo = ResizeImageField(upload_to=PHOTO_DIR, blank=True)


#------
# templatetags/resize_filters.py:
#------

from django import template

register = template.Library()

from address.resize_image import ORIGINAL_NAME, SCALED_NAME, THUMB_NAME

@register.filter
def scaled(value):
    return value.replace(ORIGINAL_NAME, SCALED_NAME, 1)

@register.filter
def thumb(value):
    return value.replace(ORIGINAL_NAME, THUMB_NAME, 1)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

Meza (on November 19, 2010):

Looks like a nice idea, but can't seem to get this saving images in the right paths.

IOError at /admin/news/article/1469/

[Errno 2] No such file or directory: u'/var/sites/dev/dev/media/testimage.jpg'

It should be uploading to /var/sites/dev/dev/media/uploads/images/ Changing settings of PHOTO_DIR made no difference at all. Python 2.6 / Django 1.2.2 Any ideas?

#

Please login first before commenting.