fromdjango.db.models.fields.filesimportImageField,ImageFieldFilefromdjango.core.files.baseimportFile,ContentFileclassImageVariation(File):variation_suffix='variation'def__init__(self,instance):self.field=instance.fieldself.instance=instance.instanceself.storage=getattr(self,'storage',instance.storage)name=getattr(self.instance,self.field.name)oru''self._name=self.get_variation_name(unicode(name))self._closed=Falsedefget_variation_name(self,name):try:dot_index=name.rindex('.')except:name='%s%s'%(name,self.variation_suffix)else:name='%s%s%s'%(name[:dot_index],self.variation_suffix,name[dot_index:])returnnamedefsave(self,name,content):self.storage.save(self.name,content)defdelete(self):self.close()self.storage.delete(self.name)def_require_file(self):ifnotself:raiseValueError("The '%s' attribute has no file associated with it."%self.field.name)def_get_file(self):self._require_file()ifnothasattr(self,'_file'):self._file=self.storage.open(self.name,'rb')returnself._filefile=property(_get_file)def_get_path(self):self._require_file()returnself.storage.path(self.name)path=property(_get_path)def_get_url(self):self._require_file()returnself.storage.url(self.name)url=property(_get_url)defopen(self,mode='rb'):self._require_file()returnsuper(ImageVariation,self).open(mode)# open() doesn't alter the file's contents, but it does reset the pointeropen.alters_data=TruefromStringIOimportStringIOdefget_file(data):ifhasattr(data,'temporary_file_path'):file=data.temporary_file_path()else:ifhasattr(data,'read'):file=StringIO(data.read())else:file=StringIO(data['content'])returnfileWIDTH,HEIGHT=0,1defresize_image(content,size):fromPILimportImageimg=Image.open(get_file(content))ifimg.size[0]>size['width']orimg.size[1]>size['height']:ifsize['force']:target_height=float(size['height']*img.size[WIDTH])/size['width']iftarget_height<img.size[HEIGHT]:# Crop heightcrop_side_size=int((img.size[HEIGHT]-target_height)/2)img=img.crop((0,crop_side_size,img.size[WIDTH],img.size[HEIGHT]-crop_side_size))eliftarget_height>img.size[HEIGHT]:# Crop widthtarget_width=float(size['width']*img.size[HEIGHT])/size['height']crop_side_size=int((img.size[WIDTH]-target_width)/2)img=img.crop((crop_side_size,0,img.size[WIDTH]-crop_side_size,img.size[HEIGHT]))img.thumbnail((size['width'],size['height']),Image.ANTIALIAS)out=StringIO()try:img.save(out,optimize=1)exceptIOError:img.save(out)returnoutelse:returncontentclassThumbnail(ImageVariation):variation_suffix='thumbnail'defsave(self,name,content):self.storage.save(self.name,resize_image(content,{'height':200,'width':200,'force':True}))classImageVariationsDescriptor(object):def__get__(self,instance=None,owner=None):self.variations=[(v(instance))forvininstance.field.variations]forvinself.variations:setattr(self,v.__class__.__name__.lower(),v)returnselfdefsave(self,name,content):forvinself.variations:content.seek(0)v.save(name,content)defdelete(self):forvinself.variations:v.delete()classImageVariationsFieldFile(ImageFieldFile):variations=ImageVariationsDescriptor()defsave(self,name,content,save=True):super(ImageVariationsFieldFile,self).save(name,content,save=save)self.variations.save(name,content)defdelete(self,save=True):self.variations.delete()super(ImageVariationsFieldFile,self).delete(save)classImageVariationsField(ImageField):attr_class=ImageVariationsFieldFiledef__init__(self,variations=[],*args,**kwargs):self.variations=variationssuper(ImageVariationsField,self).__init__(*args,**kwargs)