2009-01-04 18:30:03 +00:00
""" ImageKit image specifications
2009-01-04 17:38:06 +00:00
2009-01-04 18:30:03 +00:00
All imagekit specifications must inherit from the ImageSpec class . Models
2009-01-14 16:09:17 +00:00
inheriting from ImageModel will be modified with a descriptor / accessor for each
2009-01-04 18:30:03 +00:00
spec found .
2009-01-04 17:38:06 +00:00
"""
2009-01-04 22:14:13 +00:00
import os
2011-09-19 01:08:49 +00:00
import datetime
2009-02-04 19:38:30 +00:00
from StringIO import StringIO
2009-01-08 21:11:15 +00:00
from imagekit . lib import *
2009-01-08 20:04:20 +00:00
from imagekit . utils import img_to_fobj
2009-01-08 17:45:06 +00:00
from django . core . files . base import ContentFile
2011-09-19 01:08:49 +00:00
from django . utils . encoding import force_unicode , smart_str
2011-09-21 00:19:19 +00:00
from django . db . models . signals import post_save , post_delete
2009-01-04 22:14:13 +00:00
2009-06-04 15:06:11 +00:00
2009-01-04 18:30:03 +00:00
class ImageSpec ( object ) :
2009-12-19 16:01:54 +00:00
2011-09-10 04:25:27 +00:00
image_field = None
2011-09-08 19:52:09 +00:00
processors = [ ]
pre_cache = False
quality = 70
increment_count = False
2011-09-10 03:23:27 +00:00
storage = None
2011-09-21 01:04:35 +00:00
format = None
cache_to = None
""" Specifies the filename to use when saving the image cache file. This is
modeled after ImageField ' s `upload_to` and can accept 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.
If you have not explicitly set a format on your ImageSpec , the extension of
the path returned by this function will be used to infer one .
"""
2011-09-08 19:52:09 +00:00
def __init__ ( self , processors = None , * * kwargs ) :
if processors :
self . processors = processors
self . __dict__ . update ( kwargs )
2009-12-19 16:01:54 +00:00
2011-09-10 04:24:37 +00:00
def _get_imgfield ( self , obj ) :
2011-09-10 04:25:27 +00:00
field_name = getattr ( self , ' image_field ' , None ) or obj . _ik . default_image_field
return getattr ( obj , field_name )
2011-09-10 04:24:37 +00:00
2011-09-08 13:15:08 +00:00
def process ( self , image , obj ) :
2009-06-04 15:06:11 +00:00
fmt = image . format
img = image . copy ( )
2011-09-08 13:15:08 +00:00
for proc in self . processors :
2011-09-10 04:24:37 +00:00
img , fmt = proc . process ( img , fmt , obj , self )
2011-09-21 01:04:35 +00:00
format = self . format or fmt
img . format = format
return img , format
2009-12-19 16:01:54 +00:00
2011-09-20 19:42:28 +00:00
def contribute_to_class ( self , cls , name ) :
2011-09-21 13:39:06 +00:00
setattr ( cls , name , _ImageSpecDescriptor ( self , name ) )
2011-09-21 14:37:50 +00:00
# Connect to the signals only once for this class.
uid = ' %s . %s ' % ( cls . __module__ , cls . __name__ )
post_save . connect ( _post_save_handler ,
2011-09-21 00:19:19 +00:00
sender = cls ,
dispatch_uid = ' %s _save ' % uid )
2011-09-21 14:37:50 +00:00
post_delete . connect ( _post_delete_handler ,
2011-09-21 00:19:19 +00:00
sender = cls ,
dispatch_uid = ' %s .delete ' % uid )
2011-09-21 13:39:06 +00:00
class BoundImageSpec ( ImageSpec ) :
def __init__ ( self , obj , unbound_field , property_name ) :
super ( BoundImageSpec , self ) . __init__ ( unbound_field . processors ,
image_field = unbound_field . image_field ,
pre_cache = unbound_field . pre_cache ,
quality = unbound_field . quality ,
increment_count = unbound_field . increment_count ,
storage = unbound_field . storage , format = unbound_field . format ,
cache_to = unbound_field . cache_to )
2009-01-04 18:30:03 +00:00
self . _img = None
2009-06-04 15:47:16 +00:00
self . _fmt = None
2009-01-04 18:30:03 +00:00
self . _obj = obj
2011-09-08 12:30:54 +00:00
self . property_name = property_name
2009-12-19 16:01:54 +00:00
2011-09-21 01:04:35 +00:00
@property
def _format ( self ) :
""" The format used to save the cache file. If the format is set
explicitly on the ImageSpec , that format will be used . Otherwise , the
format will be inferred from the extension of the cache filename ( see
the ` name ` property ) .
"""
2011-09-21 13:39:06 +00:00
format = self . format
2011-09-21 01:04:35 +00:00
if not format :
# Get the real (not suggested) extension.
extension = os . path . splitext ( self . name ) [ 1 ] . lower ( )
# Try to guess the format from the extension.
format = Image . EXTENSION . get ( extension )
return format or self . _img . format or ' JPEG '
2009-01-08 20:04:20 +00:00
def _get_imgfile ( self ) :
2011-09-21 01:04:35 +00:00
format = self . _format
2009-01-08 20:04:20 +00:00
if format != ' JPEG ' :
imgfile = img_to_fobj ( self . _img , format )
else :
imgfile = img_to_fobj ( self . _img , format ,
2011-09-21 13:39:06 +00:00
quality = int ( self . quality ) ,
2009-01-08 20:04:20 +00:00
optimize = True )
return imgfile
2009-12-19 16:01:54 +00:00
2011-09-10 04:24:37 +00:00
@property
def _imgfield ( self ) :
2011-09-21 13:39:06 +00:00
return self . _get_imgfield ( self . _obj )
2011-09-10 04:24:37 +00:00
2009-01-08 17:45:06 +00:00
def _create ( self ) :
2011-09-10 04:24:37 +00:00
if self . _imgfield :
2011-02-11 19:58:36 +00:00
if self . _exists ( ) :
return
2011-02-11 20:11:20 +00:00
# process the original image file
try :
2011-09-10 04:24:37 +00:00
fp = self . _imgfield . storage . open ( self . _imgfield . name )
2011-02-11 20:11:20 +00:00
except IOError :
return
fp . seek ( 0 )
fp = StringIO ( fp . read ( ) )
2011-09-21 13:39:06 +00:00
self . _img , self . _fmt = self . process ( Image . open ( fp ) , self . _obj )
2011-02-11 20:11:20 +00:00
# save the new image to the cache
content = ContentFile ( self . _get_imgfile ( ) . read ( ) )
2011-09-08 13:15:58 +00:00
self . _storage . save ( self . name , content )
2009-12-19 16:01:54 +00:00
2009-01-08 17:45:06 +00:00
def _delete ( self ) :
2011-09-10 04:24:37 +00:00
if self . _imgfield :
2011-08-26 14:42:14 +00:00
try :
2011-09-08 13:15:58 +00:00
self . _storage . delete ( self . name )
2011-08-26 14:42:14 +00:00
except ( NotImplementedError , IOError ) :
return
2009-01-08 17:45:06 +00:00
def _exists ( self ) :
2011-09-10 04:24:37 +00:00
if self . _imgfield :
2011-09-08 13:15:58 +00:00
return self . _storage . exists ( self . name )
2009-01-08 17:45:06 +00:00
2011-09-21 01:04:35 +00:00
@property
def _suggested_extension ( self ) :
2011-09-21 13:39:06 +00:00
if self . format :
2011-09-21 01:04:35 +00:00
# Try to look up an extension by the format
extensions = [ k . lstrip ( ' . ' ) for k , v in Image . EXTENSION . iteritems ( ) \
2011-09-21 13:39:06 +00:00
if v == self . format . upper ( ) ]
2011-09-21 01:04:35 +00:00
else :
extensions = [ ]
original_extension = os . path . splitext ( self . _imgfield . name ) [ 1 ] . lstrip ( ' . ' )
if not extensions or original_extension . lower ( ) in extensions :
# If the original extension matches the format, use it.
extension = original_extension
else :
extension = extensions [ 0 ]
return extension
2009-07-19 19:26:53 +00:00
@property
def name ( self ) :
2011-09-21 01:04:35 +00:00
"""
Specifies the filename that the cached image will use . The user can
control this by providing a ` cache_to ` method to the ImageSpec .
"""
2011-09-19 01:08:49 +00:00
filename = self . _imgfield . name
if filename :
2011-09-21 13:39:06 +00:00
cache_to = self . cache_to or \
2011-09-19 01:08:49 +00:00
getattr ( self . _obj . _ik , ' default_cache_to ' , None )
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 . _obj , self . _imgfield . name , \
2011-09-21 01:04:35 +00:00
self . property_name , self . _suggested_extension ) ) ) )
2011-02-11 19:58:36 +00:00
else :
2011-09-19 01:08:49 +00:00
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 )
return new_filename
2009-01-04 18:30:03 +00:00
2011-09-08 13:15:58 +00:00
@property
def _storage ( self ) :
2011-09-21 13:39:06 +00:00
return self . storage or \
2011-09-10 03:23:27 +00:00
getattr ( self . _obj . _ik , ' default_storage ' , None ) or \
self . _imgfield . storage
2011-09-08 13:15:58 +00:00
2009-01-04 18:30:03 +00:00
@property
def url ( self ) :
2011-09-21 13:39:06 +00:00
if not self . pre_cache :
2011-02-11 00:23:36 +00:00
self . _create ( )
2011-09-21 13:39:06 +00:00
if self . increment_count :
2009-01-04 18:30:03 +00:00
fieldname = self . _obj . _ik . save_count_as
if fieldname is not None :
current_count = getattr ( self . _obj , fieldname )
setattr ( self . _obj , fieldname , current_count + 1 )
2009-01-08 21:42:26 +00:00
self . _obj . save ( clear_cache = False )
2011-09-08 13:15:58 +00:00
return self . _storage . url ( self . name )
2009-12-19 16:01:54 +00:00
2009-01-08 17:45:06 +00:00
@property
def file ( self ) :
self . _create ( )
2011-09-08 13:15:58 +00:00
return self . _storage . open ( self . name )
2009-12-19 16:01:54 +00:00
2009-01-04 18:30:03 +00:00
@property
def image ( self ) :
2011-02-11 00:23:36 +00:00
if not self . _img :
2009-01-08 17:45:06 +00:00
self . _create ( )
2011-02-11 00:23:36 +00:00
if not self . _img :
2009-01-14 16:13:31 +00:00
self . _img = Image . open ( self . file )
2009-01-04 18:30:03 +00:00
return self . _img
2009-12-19 16:01:54 +00:00
2009-01-04 18:30:03 +00:00
@property
def width ( self ) :
return self . image . size [ 0 ]
2009-12-19 16:01:54 +00:00
2009-01-04 18:30:03 +00:00
@property
def height ( self ) :
return self . image . size [ 1 ]
2011-09-21 13:39:06 +00:00
class _ImageSpecDescriptor ( object ) :
2011-09-08 12:30:54 +00:00
def __init__ ( self , spec , property_name ) :
self . _property_name = property_name
2009-01-04 18:30:03 +00:00
self . _spec = spec
2011-09-21 13:52:38 +00:00
def __get__ ( self , instance , owner ) :
if instance is None :
return self . _spec
else :
return BoundImageSpec ( instance , self . _spec , self . _property_name )
2011-09-21 00:19:19 +00:00
2011-09-21 14:37:50 +00:00
def _post_save_handler ( sender , instance = None , created = False , raw = False , * * kwargs ) :
if raw :
return
bound_specs = _get_bound_specs ( instance )
for bound_spec in bound_specs :
name = bound_spec . property_name
imgfield = bound_spec . _get_imgfield ( instance )
2011-09-21 00:19:19 +00:00
newfile = imgfield . storage . open ( str ( imgfield ) )
img = Image . open ( newfile )
2011-09-21 14:37:50 +00:00
img , format = bound_spec . process ( img , instance )
2011-09-21 00:19:19 +00:00
if format != ' JPEG ' :
imgfile = img_to_fobj ( img , format )
else :
imgfile = img_to_fobj ( img , format ,
2011-09-21 14:37:50 +00:00
quality = int ( bound_spec . quality ) ,
2011-09-21 00:19:19 +00:00
optimize = True )
content = ContentFile ( imgfile . read ( ) )
newfile . close ( )
name = str ( imgfield )
imgfield . storage . delete ( name )
imgfield . storage . save ( name , content )
if not created :
2011-09-21 14:37:50 +00:00
bound_spec . _delete ( )
bound_spec . _create ( )
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 )
bound_specs = _get_bound_specs ( instance )
for bound_spec in bound_specs :
bound_spec . _delete ( )
def _get_bound_specs ( instance ) :
bound_specs = [ ]
for key in dir ( instance ) :
try :
value = getattr ( instance , key )
except AttributeError :
continue
if isinstance ( value , BoundImageSpec ) :
bound_specs . append ( value )
return bound_specs