2011-09-21 15:37:29 +00:00
import os
import datetime
from StringIO import StringIO
2011-10-20 03:12:47 +00:00
2009-01-08 20:04:20 +00:00
from django . core . files . base import ContentFile
2008-12-28 21:48:21 +00:00
from django . db import models
2011-09-22 04:24:13 +00:00
from django . db . models . fields . files import ImageFieldFile
2011-10-20 03:12:47 +00:00
from django . db . models . signals import post_save , post_delete
from django . utils . encoding import force_unicode , smart_str
2009-01-04 18:40:22 +00:00
2011-11-02 15:26:24 +00:00
from imagekit . utils import img_to_fobj , get_spec_files , open_image , \
format_to_extension , extension_to_format , UnknownFormatError , \
UnknownExtensionError
2011-11-07 02:40:31 +00:00
from imagekit . processors import ProcessorPipeline , AutoConvert
2012-02-11 18:06:48 +00:00
import warnings
2008-12-28 21:48:21 +00:00
2009-03-03 16:44:51 +00:00
2012-02-11 18:06:48 +00:00
class _ImageSpecFieldMixin ( object ) :
2011-11-16 15:00:35 +00:00
def __init__ ( self , processors = None , format = None , options = { } ,
2011-11-07 02:40:31 +00:00
autoconvert = True ) :
2011-09-22 04:24:13 +00:00
self . processors = processors
self . format = format
2011-11-16 15:00:35 +00:00
self . options = options
2011-11-07 02:40:31 +00:00
self . autoconvert = autoconvert
2009-03-03 16:44:51 +00:00
2012-01-26 11:13:57 +00:00
def process ( self , image , file , instance ) :
processors = self . processors
if callable ( processors ) :
processors = processors ( instance = instance , file = file )
processors = ProcessorPipeline ( processors or [ ] )
2011-09-24 01:25:47 +00:00
return processors . process ( image . copy ( ) )
2008-12-28 21:48:21 +00:00
2009-12-19 16:01:54 +00:00
2012-02-11 18:06:48 +00:00
class ImageSpecField ( _ImageSpecFieldMixin ) :
2011-10-31 14:12:03 +00:00
"""
2012-02-11 18:06:48 +00:00
The heart and soul of the ImageKit library , ImageSpecField allows you to add
2011-09-26 00:38:43 +00:00
variants of uploaded images to your models .
2009-12-19 16:01:54 +00:00
2009-03-03 16:44:51 +00:00
"""
2011-09-26 00:38:43 +00:00
_upload_to_attr = ' cache_to '
2011-11-16 15:00:35 +00:00
def __init__ ( self , processors = None , format = None , options = { } ,
2011-11-07 02:40:31 +00:00
image_field = None , pre_cache = False , storage = None , cache_to = None ,
autoconvert = True ) :
2011-09-26 00:38:43 +00:00
"""
: param processors : A list of processors to run on the original image .
2011-10-31 14:12:03 +00:00
: param format : The format of the output file . If not provided ,
2012-02-11 18:06:48 +00:00
ImageSpecField will try to guess the appropriate format based on the
2011-10-31 14:12:03 +00:00
extension of the filename and the format of the input image .
2011-11-16 15:00:35 +00:00
: param options : A dictionary that will be passed to PIL ' s
` ` Image . save ( ) ` ` method as keyword arguments . Valid options vary
between formats , but some examples include ` ` quality ` ` ,
` ` optimize ` ` , and ` ` progressive ` ` for JPEGs . See the PIL
documentation for others .
2011-09-26 00:38:43 +00:00
: param image_field : The name of the model property that contains the
2011-10-31 14:12:03 +00:00
original image .
: param pre_cache : A boolean that specifies whether the image should
be generated immediately ( True ) or on demand ( False ) .
2011-09-26 00:38:43 +00:00
: param storage : A Django storage system to use to save the generated
2011-10-31 14:12:03 +00:00
image .
2011-09-26 00:38:43 +00:00
: param cache_to : Specifies the filename to use when saving the image
2011-10-31 14:12:03 +00:00
cache file . This is modeled after ImageField ' s ``upload_to`` and
can be either a string ( that specifies a directory ) or a
callable ( that returns a filepath ) . Callable values should
accept the following arguments :
- instance - - The model instance this spec belongs to
- path - - The path of the original image
- specname - - the property name that the spec is bound to on
the model instance
- extension - - A recommended extension . If the format of the
spec is set explicitly , this suggestion will be
based on that format . if not , the extension of the
original file will be passed . You do not have to use
this extension , it ' s only a recommendation.
2011-11-07 02:40:31 +00:00
: param autoconvert : Specifies whether the AutoConvert processor
should be run before saving .
2011-09-26 00:38:43 +00:00
"""
2011-09-21 15:37:29 +00:00
2012-02-11 18:06:48 +00:00
_ImageSpecFieldMixin . __init__ ( self , processors , format = format ,
2011-11-16 15:00:35 +00:00
options = options , autoconvert = autoconvert )
2011-09-22 04:24:13 +00:00
self . image_field = image_field
self . pre_cache = pre_cache
self . storage = storage
self . cache_to = cache_to
2011-09-21 15:37:29 +00:00
def contribute_to_class ( self , cls , name ) :
2012-02-11 18:06:48 +00:00
setattr ( cls , name , _ImageSpecFieldDescriptor ( self , name ) )
2011-10-04 02:51:03 +00:00
try :
ik = getattr ( cls , ' _ik ' )
except AttributeError :
ik = type ( ' ImageKitMeta ' , ( object , ) , { ' spec_file_names ' : [ ] } )
setattr ( cls , ' _ik ' , ik )
ik . spec_file_names . append ( name )
2011-09-21 15:37:29 +00:00
# Connect to the signals only once for this class.
uid = ' %s . %s ' % ( cls . __module__ , cls . __name__ )
2011-12-02 07:46:27 +00:00
post_save . connect ( _post_save_handler , sender = cls ,
dispatch_uid = ' %s _save ' % uid )
post_delete . connect ( _post_delete_handler , sender = cls ,
dispatch_uid = ' %s .delete ' % uid )
2011-09-21 15:37:29 +00:00
2012-02-11 18:06:48 +00:00
class ImageSpec ( ImageSpecField ) :
def __init__ ( self , * args , * * kwargs ) :
warnings . warn ( ' ImageSpec has been renamed to ImageSpecField. Please '
' use that instead. ' , DeprecationWarning )
super ( ImageSpec , self ) . __init__ ( * args , * * kwargs )
2011-09-22 04:24:13 +00:00
def _get_suggested_extension ( name , format ) :
2011-09-23 16:44:53 +00:00
original_extension = os . path . splitext ( name ) [ 1 ]
2011-11-02 15:26:24 +00:00
try :
suggested_extension = format_to_extension ( format )
except UnknownFormatError :
2011-09-22 04:24:13 +00:00
extension = original_extension
else :
2011-11-02 15:26:24 +00:00
if suggested_extension . lower ( ) == original_extension . lower ( ) :
extension = original_extension
else :
try :
original_format = extension_to_format ( original_extension )
except UnknownExtensionError :
extension = suggested_extension
else :
# If the formats match, give precedence to the original extension.
if format . lower ( ) == original_format . lower ( ) :
extension = original_extension
else :
extension = suggested_extension
2011-09-22 04:24:13 +00:00
return extension
2012-02-11 18:06:48 +00:00
class _ImageSpecFieldFileMixin ( object ) :
2011-09-24 05:57:50 +00:00
def _process_content ( self , filename , content ) :
2011-09-25 03:09:49 +00:00
img = open_image ( content )
2011-09-24 05:57:50 +00:00
original_format = img . format
2012-01-26 11:13:57 +00:00
img = self . field . process ( img , self , self . instance )
2011-11-16 15:00:35 +00:00
options = dict ( self . field . options or { } )
2011-09-24 05:57:50 +00:00
# Determine the format.
format = self . field . format
if not format :
if callable ( getattr ( self . field , self . field . _upload_to_attr ) ) :
# The extension is explicit, so assume they want the matching format.
extension = os . path . splitext ( filename ) [ 1 ] . lower ( )
# Try to guess the format from the extension.
2011-11-02 15:26:24 +00:00
try :
format = extension_to_format ( extension )
except UnknownExtensionError :
pass
2011-09-24 05:57:50 +00:00
format = format or img . format or original_format or ' JPEG '
2011-11-07 02:40:31 +00:00
# Run the AutoConvert processor
if getattr ( self . field , ' autoconvert ' , True ) :
autoconvert_processor = AutoConvert ( format )
img = autoconvert_processor . process ( img )
2011-11-16 15:27:18 +00:00
options = dict ( autoconvert_processor . save_kwargs . items ( ) + \
options . items ( ) )
2011-11-07 02:40:31 +00:00
2011-11-16 15:00:35 +00:00
imgfile = img_to_fobj ( img , format , * * options )
2011-09-24 05:57:50 +00:00
content = ContentFile ( imgfile . read ( ) )
return img , content
2012-02-11 18:06:48 +00:00
class ImageSpecFieldFile ( _ImageSpecFieldFileMixin , ImageFieldFile ) :
2011-12-02 07:28:08 +00:00
def __init__ ( self , instance , field , attname ) :
2011-10-10 21:00:41 +00:00
ImageFieldFile . __init__ ( self , instance , field , None )
2011-09-22 01:04:40 +00:00
self . attname = attname
2011-12-02 07:28:08 +00:00
self . storage = self . field . storage or self . source_file . storage
@property
def source_file ( self ) :
field_name = getattr ( self . field , ' image_field ' , None )
if field_name :
field_file = getattr ( self . instance , field_name )
else :
image_fields = [ getattr ( self . instance , f . attname ) for f in \
self . instance . __class__ . _meta . fields if \
isinstance ( f , models . ImageField ) ]
if len ( image_fields ) == 0 :
2012-01-26 23:19:39 +00:00
raise Exception ( ' %s does not define any ImageFields, so your ' \
2012-02-11 18:06:48 +00:00
' %s ImageSpecField has no image to act on. ' % \
2012-01-26 23:19:39 +00:00
( self . instance . __class__ . __name__ , self . attname ) )
2011-12-02 07:28:08 +00:00
elif len ( image_fields ) > 1 :
2012-01-26 23:19:39 +00:00
raise Exception ( ' %s defines multiple ImageFields, but you ' \
' have not specified an image_field for your %s ' \
2012-02-11 18:06:48 +00:00
' ImageSpecField. ' % ( self . instance . __class__ . __name__ ,
2011-12-02 07:28:08 +00:00
self . attname ) )
else :
field_file = image_fields [ 0 ]
return field_file
2011-09-21 15:37:29 +00:00
2011-10-10 21:00:41 +00:00
def _require_file ( self ) :
if not self . source_file :
raise ValueError ( " The ' %s ' attribute ' s image_field has no file associated with it. " % self . attname )
def _get_file ( self ) :
2011-11-08 04:01:52 +00:00
self . generate ( )
2011-10-10 21:00:41 +00:00
return super ( ImageFieldFile , self ) . file
file = property ( _get_file , ImageFieldFile . _set_file , ImageFieldFile . _del_file )
@property
def url ( self ) :
2011-11-08 04:01:52 +00:00
self . generate ( )
2011-10-10 21:00:41 +00:00
return super ( ImageFieldFile , self ) . url
2011-11-08 04:01:52 +00:00
def generate ( self , lazy = True ) :
2011-10-31 14:12:03 +00:00
"""
2011-11-08 04:01:52 +00:00
Generates a new image by running the processors on the source file .
2011-10-20 03:06:10 +00:00
2011-09-24 03:02:17 +00:00
Keyword Arguments :
2011-10-31 14:12:03 +00:00
lazy - - True if an already - existing image should be returned ;
False if a new image should be created and the existing
one overwritten .
2011-09-21 15:37:29 +00:00
"""
2011-10-10 21:00:41 +00:00
if lazy and ( getattr ( self , ' _file ' , None ) or self . storage . exists ( self . name ) ) :
2009-01-04 16:29:53 +00:00
return
2011-10-10 21:00:41 +00:00
2011-10-20 03:12:47 +00:00
if self . source_file : # TODO: Should we error here or something if the source_file doesn't exist?
2011-10-31 14:12:03 +00:00
# Process the original image file.
2011-09-03 03:24:35 +00:00
try :
2011-09-24 03:02:17 +00:00
fp = self . source_file . storage . open ( self . source_file . name )
2011-09-21 15:37:29 +00:00
except IOError :
return
fp . seek ( 0 )
fp = StringIO ( fp . read ( ) )
2009-12-19 16:01:54 +00:00
2011-09-24 05:57:50 +00:00
img , content = self . _process_content ( self . name , fp )
2011-09-25 20:18:52 +00:00
self . storage . save ( self . name , content )
2008-12-28 21:48:21 +00:00
2011-10-10 21:00:41 +00:00
def delete ( self , save = False ) :
2011-10-31 14:12:03 +00:00
"""
Pulled almost verbatim from ` ` ImageFieldFile . delete ( ) ` ` and
2011-10-10 21:00:41 +00:00
` ` FieldFile . delete ( ) ` ` but with the attempts to reset the instance
property removed .
2009-12-19 16:01:54 +00:00
2011-10-10 21:00:41 +00:00
"""
# Clear the image dimensions cache
if hasattr ( self , ' _dimensions_cache ' ) :
del self . _dimensions_cache
2009-12-19 16:01:54 +00:00
2011-10-10 21:00:41 +00:00
# Only close the file if it's already open, which we know by the
2011-10-31 14:12:03 +00:00
# presence of self._file.
2011-10-10 21:00:41 +00:00
if hasattr ( self , ' _file ' ) :
self . close ( )
del self . file
2011-12-07 05:52:06 +00:00
if self . name and self . storage . exists ( self . name ) :
try :
self . storage . delete ( self . name )
except NotImplementedError :
pass
2011-10-10 21:00:41 +00:00
2011-10-31 14:12:03 +00:00
# Delete the filesize cache.
2011-10-10 21:00:41 +00:00
if hasattr ( self , ' _size ' ) :
del self . _size
self . _committed = False
if save :
self . instance . save ( )
2009-12-19 16:01:54 +00:00
2009-01-06 19:00:53 +00:00
@property
2011-09-21 15:37:29 +00:00
def _suggested_extension ( self ) :
2011-09-24 03:02:17 +00:00
return _get_suggested_extension ( self . source_file . name , self . field . format )
2011-09-21 15:37:29 +00:00
2011-09-22 01:02:12 +00:00
def _default_cache_to ( self , instance , path , specname , extension ) :
2011-10-31 14:12:03 +00:00
"""
Determines the filename to use for the transformed image . Can be
overridden on a per - spec basis by setting the cache_to property on
the spec .
2011-09-22 01:02:12 +00:00
"""
filepath , basename = os . path . split ( path )
filename = os . path . splitext ( basename ) [ 0 ]
2012-01-26 23:19:39 +00:00
new_name = ' %s _ %s %s ' % ( filename , specname , extension )
2011-09-22 01:02:12 +00:00
return os . path . join ( os . path . join ( ' cache ' , filepath ) , new_name )
2009-12-19 16:01:54 +00:00
2009-12-18 06:26:19 +00:00
@property
2011-09-21 15:37:29 +00:00
def name ( self ) :
"""
Specifies the filename that the cached image will use . The user can
2012-02-11 18:06:48 +00:00
control this by providing a ` cache_to ` method to the ImageSpecField .
2011-09-21 15:37:29 +00:00
"""
2011-11-01 23:39:54 +00:00
name = getattr ( self , ' _name ' , None )
if not name :
filename = self . source_file . name
new_filename = None
if filename :
cache_to = self . field . cache_to or self . _default_cache_to
if not cache_to :
raise Exception ( ' No cache_to or default_cache_to value specified ' )
if callable ( cache_to ) :
new_filename = force_unicode ( datetime . datetime . now ( ) . strftime ( \
smart_str ( cache_to ( self . instance , self . source_file . name ,
self . attname , self . _suggested_extension ) ) ) )
else :
dir_name = os . path . normpath ( force_unicode ( datetime . datetime . now ( ) . strftime ( smart_str ( cache_to ) ) ) )
filename = os . path . normpath ( os . path . basename ( filename ) )
new_filename = os . path . join ( dir_name , filename )
self . _name = new_filename
return self . _name
2011-09-21 15:37:29 +00:00
2011-10-10 21:00:41 +00:00
@name.setter
def name ( self , value ) :
2011-10-31 14:12:03 +00:00
# TODO: Figure out a better way to handle this. We really don't want
# to allow anybody to set the name, but ``File.__init__`` (which is
2012-02-11 18:06:48 +00:00
# called by ``ImageSpecFieldFile.__init__``) does, so we have to allow
# it at least that one time.
2011-10-10 21:00:41 +00:00
pass
2011-09-21 15:37:29 +00:00
2012-02-11 18:06:48 +00:00
class _ImageSpecFieldDescriptor ( object ) :
2011-09-22 22:13:32 +00:00
def __init__ ( self , field , attname ) :
2011-09-24 03:02:17 +00:00
self . attname = attname
2011-09-22 22:13:32 +00:00
self . field = field
2011-09-21 15:37:29 +00:00
def __get__ ( self , instance , owner ) :
if instance is None :
2011-09-22 22:13:32 +00:00
return self . field
2009-09-02 18:20:30 +00:00
else :
2012-02-11 18:06:48 +00:00
img_spec_file = ImageSpecFieldFile ( instance , self . field ,
self . attname )
2011-09-25 16:42:27 +00:00
setattr ( instance , self . attname , img_spec_file )
return img_spec_file
2011-09-21 15:37:29 +00:00
def _post_save_handler ( sender , instance = None , created = False , raw = False , * * kwargs ) :
if raw :
return
2011-09-22 13:20:37 +00:00
spec_files = get_spec_files ( instance )
for spec_file in spec_files :
2011-09-22 23:44:47 +00:00
if not created :
2011-10-10 21:00:41 +00:00
spec_file . delete ( save = False )
2011-09-22 23:44:47 +00:00
if spec_file . field . pre_cache :
2011-11-08 04:01:52 +00:00
spec_file . generate ( False )
2011-09-21 15:37:29 +00:00
def _post_delete_handler ( sender , instance = None , * * kwargs ) :
assert instance . _get_pk_val ( ) is not None , " %s object can ' t be deleted because its %s attribute is set to None. " % ( instance . _meta . object_name , instance . _meta . pk . attname )
2011-09-22 13:20:37 +00:00
spec_files = get_spec_files ( instance )
for spec_file in spec_files :
2011-10-10 21:00:41 +00:00
spec_file . delete ( save = False )
2011-09-21 15:37:29 +00:00
2012-02-11 18:06:48 +00:00
class ProcessedImageFieldFile ( ImageFieldFile , _ImageSpecFieldFileMixin ) :
2011-09-22 04:24:13 +00:00
def save ( self , name , content , save = True ) :
new_filename = self . field . generate_filename ( self . instance , name )
2011-09-24 05:57:50 +00:00
img , content = self . _process_content ( new_filename , content )
2011-09-22 04:24:13 +00:00
return super ( ProcessedImageFieldFile , self ) . save ( name , content , save )
2012-02-11 18:06:48 +00:00
class ProcessedImageField ( models . ImageField , _ImageSpecFieldMixin ) :
2011-10-31 14:12:03 +00:00
"""
ProcessedImageField is an ImageField that runs processors on the uploaded
2011-09-26 00:38:43 +00:00
image * before * saving it to storage . This is in contrast to specs , which
maintain the original . Useful for coercing fileformats or keeping images
within a reasonable size .
"""
2011-09-24 05:57:50 +00:00
_upload_to_attr = ' upload_to '
2011-09-22 04:24:13 +00:00
attr_class = ProcessedImageFieldFile
2011-11-16 15:00:35 +00:00
def __init__ ( self , processors = None , format = None , options = { } ,
2011-09-22 04:24:13 +00:00
verbose_name = None , name = None , width_field = None , height_field = None ,
2011-11-07 02:40:31 +00:00
autoconvert = True , * * kwargs ) :
2011-09-26 00:38:43 +00:00
"""
The ProcessedImageField constructor accepts all of the arguments that
2011-10-31 14:12:03 +00:00
the : class : ` django . db . models . ImageField ` constructor accepts , as well
2011-11-16 15:00:35 +00:00
as the ` ` processors ` ` , ` ` format ` ` , and ` ` options ` ` arguments of
2012-02-11 18:06:48 +00:00
: class : ` imagekit . models . ImageSpecField ` .
2011-09-26 00:38:43 +00:00
"""
2011-11-16 15:00:35 +00:00
if ' quality ' in kwargs :
raise Exception ( ' The " quality " keyword argument has been '
""" deprecated. Use `options= { ' quality ' : %s }` instead. """ \
% kwargs [ ' quality ' ] )
2012-02-11 18:06:48 +00:00
_ImageSpecFieldMixin . __init__ ( self , processors , format = format ,
2011-11-16 15:00:35 +00:00
options = options , autoconvert = autoconvert )
2011-09-22 04:24:13 +00:00
models . ImageField . __init__ ( self , verbose_name , name , width_field ,
height_field , * * kwargs )
def get_filename ( self , filename ) :
filename = os . path . normpath ( self . storage . get_valid_name ( os . path . basename ( filename ) ) )
name , ext = os . path . splitext ( filename )
ext = _get_suggested_extension ( filename , self . format )
2012-01-26 23:19:39 +00:00
return ' %s %s ' % ( name , ext )
2011-10-24 21:58:38 +00:00
try :
from south . modelsinspector import add_introspection_rules
except ImportError :
pass
else :
add_introspection_rules ( [ ] , [ r ' ^imagekit \ .models \ .ProcessedImageField$ ' ] )