2012-02-14 16:34:51 +00:00
import os
2008-12-28 21:48:21 +00:00
from django . db import models
2012-02-11 05:04:56 +00:00
from django . db . models . signals import post_init , post_save , post_delete
2009-01-04 18:40:22 +00:00
2012-02-14 02:44:29 +00:00
from . . . imagecache import get_default_image_cache_backend
from . . . generators import SpecFileGenerator
from . files import ImageSpecFieldFile , ProcessedImageFieldFile
2012-02-17 23:39:51 +00:00
from . utils import ImageSpecFileDescriptor , ImageKitMeta , BoundImageKitMeta
2012-04-20 01:18:42 +00:00
from . . . utils import suggest_extension
2012-02-02 04:26:39 +00:00
2012-02-12 01:11:56 +00:00
class ImageSpecField ( object ) :
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
"""
2012-04-10 01:25:46 +00:00
def __init__ ( self , processors = None , format = None , options = None ,
2012-02-02 03:37:58 +00:00
image_field = None , pre_cache = None , storage = None , cache_to = None ,
2012-02-12 21:18:34 +00:00
autoconvert = True , image_cache_backend = None ) :
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 .
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.
2012-04-21 01:37:43 +00:00
: param autoconvert : Specifies whether automatic conversion using
` ` prepare_image ( ) ` ` should be performed prior to saving .
2012-02-12 21:18:34 +00:00
: param image_cache_backend : An object responsible for managing the state
2012-02-02 03:37:58 +00:00
of cached files . Defaults to an instance of
2012-02-12 21:18:34 +00:00
IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND
2011-09-26 00:38:43 +00:00
"""
2011-09-21 15:37:29 +00:00
2012-02-02 03:37:58 +00:00
if pre_cache is not None :
raise Exception ( ' The pre_cache argument has been removed in favor '
' of cache state backends. ' )
2012-02-12 04:22:06 +00:00
# The generator accepts a callable value for processors, but it
# takes different arguments than the callable that ImageSpecField
# expects, so we create a partial application and pass that instead.
# TODO: Should we change the signatures to match? Even if `instance` is not part of the signature, it's accessible through the source file object's instance property.
p = lambda file : processors ( instance = file . instance , file = file ) if \
callable ( processors ) else processors
self . generator = SpecFileGenerator ( p , format = format , options = options ,
2012-02-12 04:41:07 +00:00
autoconvert = autoconvert , storage = storage )
2011-09-22 04:24:13 +00:00
self . image_field = image_field
self . storage = storage
self . cache_to = cache_to
2012-02-12 04:39:16 +00:00
self . image_cache_backend = image_cache_backend or \
get_default_image_cache_backend ( )
2011-09-21 15:37:29 +00:00
def contribute_to_class ( self , cls , name ) :
2012-02-17 23:39:51 +00:00
setattr ( cls , name , ImageSpecFileDescriptor ( self , name ) )
2011-10-04 02:51:03 +00:00
try :
2012-02-18 02:08:06 +00:00
# Make sure we don't modify an inherited ImageKitMeta instance
ik = cls . __dict__ [ ' ik ' ]
except KeyError :
try :
base = getattr ( cls , ' _ik ' )
except AttributeError :
ik = ImageKitMeta ( )
else :
# Inherit all the spec fields.
ik = ImageKitMeta ( base . spec_fields )
2011-10-04 02:51:03 +00:00
setattr ( cls , ' _ik ' , ik )
2012-02-02 04:26:39 +00:00
ik . spec_fields . append ( name )
2011-10-04 02:51:03 +00:00
2011-09-21 15:37:29 +00:00
# Connect to the signals only once for this class.
uid = ' %s . %s ' % ( cls . __module__ , cls . __name__ )
2012-02-12 22:06:37 +00:00
post_init . connect ( ImageSpecField . _post_init_receiver , sender = cls ,
2012-02-11 05:04:56 +00:00
dispatch_uid = uid )
2012-02-12 22:06:37 +00:00
post_save . connect ( ImageSpecField . _post_save_receiver , sender = cls ,
2012-02-11 05:04:56 +00:00
dispatch_uid = uid )
2012-02-12 22:06:37 +00:00
post_delete . connect ( ImageSpecField . _post_delete_receiver , sender = cls ,
2012-02-11 05:04:56 +00:00
dispatch_uid = uid )
2011-09-21 15:37:29 +00:00
2012-02-12 21:18:34 +00:00
# Register the field with the image_cache_backend
2012-02-02 03:37:58 +00:00
try :
2012-02-12 21:18:34 +00:00
self . image_cache_backend . register_field ( cls , self , name )
2012-02-02 03:37:58 +00:00
except AttributeError :
pass
2012-02-11 05:04:56 +00:00
@staticmethod
def _post_save_receiver ( sender , instance = None , created = False , raw = False , * * kwargs ) :
if not raw :
old_hashes = instance . _ik . _source_hashes . copy ( )
2012-02-12 22:06:37 +00:00
new_hashes = ImageSpecField . _update_source_hashes ( instance )
2012-02-11 05:04:56 +00:00
for attname in instance . _ik . spec_fields :
if old_hashes [ attname ] != new_hashes [ attname ] :
getattr ( instance , attname ) . invalidate ( )
@staticmethod
def _update_source_hashes ( instance ) :
"""
Stores hashes of the source image files so that they can be compared
later to see whether the source image has changed ( and therefore whether
the spec file needs to be regenerated ) .
"""
instance . _ik . _source_hashes = dict ( ( f . attname , hash ( f . source_file ) ) \
for f in instance . _ik . spec_files )
return instance . _ik . _source_hashes
@staticmethod
def _post_delete_receiver ( sender , instance = None , * * kwargs ) :
for spec_file in instance . _ik . spec_files :
spec_file . clear ( )
@staticmethod
def _post_init_receiver ( sender , instance , * * kwargs ) :
2012-02-12 22:06:37 +00:00
ImageSpecField . _update_source_hashes ( instance )
2011-09-21 15:37:29 +00:00
2012-02-12 01:11:56 +00:00
class ProcessedImageField ( models . ImageField ) :
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-22 04:24:13 +00:00
attr_class = ProcessedImageFieldFile
2012-04-10 01:25:46 +00:00
def __init__ ( self , processors = None , format = None , options = None ,
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-04-21 01:43:59 +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 ' ] )
2011-09-22 04:24:13 +00:00
models . ImageField . __init__ ( self , verbose_name , name , width_field ,
height_field , * * kwargs )
2012-02-12 01:11:56 +00:00
self . generator = SpecFileGenerator ( processors , format = format ,
2012-02-12 01:20:11 +00:00
options = options , autoconvert = autoconvert )
2011-09-22 04:24:13 +00:00
def get_filename ( self , filename ) :
2012-02-12 04:27:25 +00:00
filename = os . path . normpath ( self . storage . get_valid_name (
os . path . basename ( filename ) ) )
2011-09-22 04:24:13 +00:00
name , ext = os . path . splitext ( filename )
2012-04-20 01:18:42 +00:00
ext = suggest_extension ( filename , self . generator . format )
2012-02-16 13:47:22 +00:00
return u ' %s %s ' % ( name , ext )
2011-10-24 21:58:38 +00:00
try :
from south . modelsinspector import add_introspection_rules
except ImportError :
pass
else :
2012-02-12 19:53:49 +00:00
add_introspection_rules ( [ ] , [ r ' ^imagekit \ .models \ .fields \ .ProcessedImageField$ ' ] )