Source

django-extended-image-field / fields.py

Full commit
#-*- coding: utf-8 -*-
''' Extended class of ImageField.
    It can add previews by set sizes and sort them to directories.
    It use self.storage for all operations with files
    It used widget with thumbnail in admin interface 
    It also can retrun a dictonary (height, width, url) 
        of preview's name in the template '''

import os
import random
import cStringIO
from PIL import Image
from django.core.files.base import ContentFile
from django.db.models import ImageField
from django.db.models.fields.files import ImageFieldFile
from widgets import ExtendedImageWidget


class ExtendedImageFieldFile(ImageFieldFile):      
    def __init__(self, *args, **kwargs):
        super(ExtendedImageFieldFile, self).__init__(*args, **kwargs)
        for preview in self.field.previews:
            try:
                setattr(self, preview['name'], self.get_thumb_params(preview['name']))
            except:
                setattr(self, preview['name'], {'width': 0, 'height': 0, 'url': ''})

    def save(self, name, content, save=True):
        name = self.gen_filename(name)
        if self.instance.pk:
            try:
                model = self.field.model
                old_image = model.objects.get(pk=self.instance.pk)
                old_path = getattr(old_image, self.field.attname)
                if self.storage.exists(old_path):
                    self.storage.delete(old_path)
                self.delete_previews(old_path.name)
            except:
                pass
        super(ExtendedImageFieldFile, self).save(name, content, save)
        self.add_previews(name, content)
    
    def delete(self, save=True):
        self.delete_previews(self.name)
        super(ExtendedImageFieldFile, self).delete(save)

    def add_previews(self, name, content):
        path =  self.storage.path(name)
        image_path = ('/').join([path.split('/')[:-1]][0])
        image_format = name.split('.')[-1:][0]
        image_name = name.replace('.%s' % image_format, '')
        content.seek(0)
        image = Image.open(content).convert('RGBA')
        for preview in self.field.previews:
            o = cStringIO.StringIO()
            newdir = ('/').join([image_path, '%s/%ss' % (self.field.upload_to, preview['name'])])
            if self.field.dir_sort and not os.path.isdir(newdir):
                try:
                    os.makedirs(newdir)
                except:
                    self.field.dir_sort = False
            if self.field.dir_sort:
                preview_name = ('/').join([self.field.upload_to, '%ss' % preview['name'], name]) 
            else:
                preview_name = ('/').join([self.field.upload_to, '%s_%s.%s' % (image_name, preview['name'], image_format)]) 
            try:
                width = preview['width']
            except:
                width = float(image.size[0]) * (preview['height'] / float(image.size[1]))
            try:
                height = preview['height']
            except:
                height = float(image.size[1]) * (preview['width'] / float(image.size[0]))
            preview_file = image.resize((int(round(width)), int(round(height))), Image.ANTIALIAS)
            preview_file.save(o, image_format.lower())
            self.storage.save(preview_name, ContentFile(o.getvalue()))
    
    def delete_previews(self, name):
        image_format = '.' + name.split('.')[-1:][0] 
        image_name = name.split('/')[-1:][0].replace(image_format, '')
        for preview in self.field.previews:
            if self.field.dir_sort:
                preview_name = ('/').join([self.field.upload_to, '%ss' % preview['name'], '%s%s' % (image_name, image_format)]) 
            else:
                preview_name = ('/').join([self.field.upload_to, '%s_%s%s' % (image_name, preview['name'], image_format)]) 
            if self.storage.exists(preview_name):
                self.storage.delete(preview_name)

    def gen_filename(self, name):
        filename = u''
        alphabet = u'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm_0123456789' * 10
        image_format = name.split('.')[-1:][0].upper()
        if image_format == 'JPG':
            image_format = 'JPEG'
        for a in xrange(10):
            filename += random.choice(alphabet)
        filename += '.' + image_format
        return filename          
        
    def get_thumb_params(self, name):
        preview_format = '.' + self.name.split('.')[-1:][0]
        preview_name = self.name.split('/')[-1:][0].replace(preview_format, '')
        if self.field.dir_sort:
            preview_path = ('/').join([self.field.upload_to, '%ss'  % name, '%s%s' % (preview_name, preview_format)])
        else:
            preview_path = ('/').join([self.field.upload_to, '%s_%s%s' % (preview_name, name, preview_format)])
        if self.storage.exists(preview_path):
            preview = Image.open(self.storage.path(preview_path))
            preview_dict = {'width': preview.size[0], 'height': preview.size[1], 'url': self.storage.url(preview_path)}
            return preview_dict
        else:
            return {'width': 0, 'height':0, 'url':''}


class ExtendedImageField(ImageField):
    ''' Set attribute preview to tuple of dictonaries contains:
        name - required. Name of preview.
        width - preview's width. If not set it will calculate from
                height on the basis of proportions
        height - preview's height. If not set it will calculate from
                width on the basis of proportions
        Set attribute dir_sort to True is you want to sort your
        previews to folders 
        Set attribute widget to ExtendedImageField and set attr to 
        dictonary contains name - name of preview you want to show
        If you lose dir_sort it will set up to True by default
        If you lose widget it will set up to ExtendedImageField with
        base image as shown by default '''
    attr_class = ExtendedImageFieldFile
    
    def __init__(self, previews=None, dir_sort=None, widget=None, **kwargs):
        self.previews = previews
        self.dir_sort = dir_sort
        self.widget = widget
        if self.dir_sort is None:
            self.dir_sort = True
        if self.widget is None:
            self.widget = ExtendedImageWidget(attrs={'preview': 'self'})
        super(ExtendedImageField, self).__init__(**kwargs)
    
    def formfield(self, **kwargs):
        kwargs['widget'] = self.widget
        return super(ExtendedImageField, self).formfield(**kwargs)