2012-07-20 01:53:50 +00:00
from django . conf import settings
from hashlib import md5
2012-02-12 04:48:54 +00:00
import os
2012-07-20 01:53:50 +00:00
import pickle
2012-12-01 22:03:51 +00:00
from . . exceptions import UnknownExtensionError
2012-12-02 01:41:08 +00:00
from . . files import GeneratedImageCacheFile , IKContentFile
2012-10-13 02:36:13 +00:00
from . . imagecache . backends import get_default_image_cache_backend
from . . imagecache . strategies import StrategyWrapper
from . . processors import ProcessorPipeline
2012-12-02 03:09:34 +00:00
from . . utils import ( open_image , extension_to_format , img_to_fobj ,
suggest_extension )
2012-12-01 22:03:51 +00:00
from . . registry import generator_registry , register
2012-10-05 02:02:29 +00:00
2012-10-05 01:37:20 +00:00
class BaseImageSpec ( object ) :
2012-11-03 04:08:47 +00:00
"""
An object that defines how an new image should be generated from a source
image .
"""
2012-12-07 00:54:26 +00:00
cache_file_storage = None
""" A Django storage system to use to save a generated cache file. """
2012-11-03 04:08:47 +00:00
image_cache_backend = None
"""
An object responsible for managing the state of cached files . Defaults to an
instance of ` ` IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND ` `
"""
image_cache_strategy = settings . IMAGEKIT_DEFAULT_IMAGE_CACHE_STRATEGY
"""
A dictionary containing callbacks that allow you to customize how and when
the image cache is validated . Defaults to
` ` IMAGEKIT_DEFAULT_SPEC_FIELD_IMAGE_CACHE_STRATEGY ` ` .
"""
def __init__ ( self , * * kwargs ) :
self . image_cache_backend = self . image_cache_backend or get_default_image_cache_backend ( )
self . image_cache_strategy = StrategyWrapper ( self . image_cache_strategy )
2012-12-02 03:23:25 +00:00
def generate ( self ) :
2012-11-03 04:08:47 +00:00
raise NotImplementedError
# TODO: I don't like this interface. Is there a standard Python one? pubsub?
2012-12-12 03:53:13 +00:00
def _handle_source_event ( self , event_name , source ) :
2012-12-02 02:20:33 +00:00
file = GeneratedImageCacheFile ( self )
2012-11-03 04:08:47 +00:00
self . image_cache_strategy . invoke_callback ( ' on_ %s ' % event_name , file )
class ImageSpec ( BaseImageSpec ) :
"""
An object that defines how to generate a new image from a source file using
PIL - based processors . ( See : mod : ` imagekit . processors ` )
"""
2012-10-21 02:15:25 +00:00
2012-10-05 01:37:20 +00:00
processors = None
2012-10-21 02:15:25 +00:00
""" A list of processors to run on the original image. """
2012-10-05 01:37:20 +00:00
format = None
2012-10-21 02:15:25 +00:00
"""
The format of the output file . If not provided , ImageSpecField will try to
guess the appropriate format based on the extension of the filename and the
format of the input image .
"""
2012-10-05 01:37:20 +00:00
options = None
2012-10-21 02:15:25 +00:00
"""
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 .
"""
2012-10-05 01:37:20 +00:00
autoconvert = True
2012-10-21 02:15:25 +00:00
"""
Specifies whether automatic conversion using ` ` prepare_image ( ) ` ` should be
performed prior to saving .
2012-10-05 01:37:20 +00:00
2012-10-21 02:15:25 +00:00
"""
2012-12-12 03:53:13 +00:00
def __init__ ( self , source , * * kwargs ) :
self . source = source
2012-10-21 02:15:25 +00:00
self . processors = self . processors or [ ]
2012-12-02 02:20:33 +00:00
self . kwargs = kwargs
2012-11-03 04:08:47 +00:00
super ( ImageSpec , self ) . __init__ ( )
2012-02-12 04:48:54 +00:00
2012-12-07 00:54:26 +00:00
@property
def cache_file_name ( self ) :
2012-12-12 03:53:13 +00:00
source_filename = self . source . name
2012-12-02 03:09:34 +00:00
ext = suggest_extension ( source_filename , self . format )
return os . path . normpath ( os . path . join (
settings . IMAGEKIT_CACHE_DIR ,
os . path . splitext ( source_filename ) [ 0 ] ,
' %s %s ' % ( self . get_hash ( ) , ext ) ) )
return os . path . join ( settings . IMAGEKIT_CACHE_DIR ,
' %s %s ' % ( hash , ext ) )
2012-10-21 02:53:55 +00:00
def get_hash ( self ) :
2012-12-06 04:16:07 +00:00
return md5 ( pickle . dumps ( [
2013-01-09 01:57:19 +00:00
self . source . name ,
2012-12-06 04:16:07 +00:00
self . kwargs ,
self . processors ,
self . format ,
self . options ,
self . autoconvert ,
] ) ) . hexdigest ( )
2012-10-05 01:37:20 +00:00
2012-12-02 02:20:33 +00:00
def generate ( self ) :
2012-12-02 04:18:49 +00:00
# TODO: Move into a generator base class
# TODO: Factor out a generate_image function so you can create a generator and only override the PIL.Image creating part. (The tricky part is how to deal with original_format since generator base class won't have one.)
2012-12-12 03:53:13 +00:00
source = self . source
2012-12-02 02:20:33 +00:00
filename = self . kwargs . get ( ' filename ' )
2012-12-12 03:53:13 +00:00
img = open_image ( source )
2012-02-12 04:48:54 +00:00
original_format = img . format
# Run the processors
2012-10-14 22:48:17 +00:00
processors = self . processors
2012-02-12 04:48:54 +00:00
img = ProcessorPipeline ( processors or [ ] ) . process ( img )
options = dict ( self . options or { } )
# Determine the format.
format = self . format
2012-04-21 03:26:16 +00:00
if filename and not format :
2012-02-12 04:48:54 +00:00
# Try to guess the format from the extension.
extension = os . path . splitext ( filename ) [ 1 ] . lower ( )
if extension :
try :
format = extension_to_format ( extension )
except UnknownExtensionError :
pass
2012-04-21 03:26:16 +00:00
format = format or img . format or original_format or ' JPEG '
2012-02-12 04:48:54 +00:00
imgfile = img_to_fobj ( img , format , * * options )
2012-11-03 04:27:03 +00:00
# TODO: Is this the right place to wrap the file? Can we use a mixin instead? Is it even still having the desired effect? Re: #111
2012-04-21 03:30:30 +00:00
content = IKContentFile ( filename , imgfile . read ( ) , format = format )
2012-10-17 02:59:40 +00:00
return content
2012-02-12 04:48:54 +00:00
2012-09-06 04:07:40 +00:00
2012-12-05 05:22:39 +00:00
def create_spec_class ( class_attrs ) :
2012-12-06 04:38:10 +00:00
class DynamicSpecBase ( ImageSpec ) :
def __reduce__ ( self ) :
2012-12-06 04:51:30 +00:00
try :
getstate = self . __getstate__
except AttributeError :
state = self . __dict__
else :
state = getstate ( )
return ( create_spec , ( class_attrs , state ) )
2012-12-06 04:38:10 +00:00
return type ( ' DynamicSpec ' , ( DynamicSpecBase , ) , class_attrs )
2012-12-01 19:11:04 +00:00
2012-12-06 04:51:30 +00:00
def create_spec ( class_attrs , state ) :
2012-12-05 05:22:39 +00:00
cls = create_spec_class ( class_attrs )
2012-12-06 04:51:30 +00:00
instance = cls . __new__ ( cls ) # Create an instance without calling the __init__ (which may have required args).
try :
setstate = instance . __setstate__
except AttributeError :
instance . __dict__ = state
else :
setstate ( state )
return instance
2012-12-05 05:22:39 +00:00
2012-10-05 02:56:26 +00:00
class SpecHost ( object ) :
"""
An object that ostensibly has a spec attribute but really delegates to the
spec registry .
"""
2012-10-26 02:31:37 +00:00
def __init__ ( self , spec = None , spec_id = None , * * kwargs ) :
2012-11-07 04:50:23 +00:00
spec_attrs = dict ( ( k , v ) for k , v in kwargs . items ( ) if v is not None )
2012-10-26 02:31:37 +00:00
2012-11-07 04:50:23 +00:00
if spec_attrs :
2012-10-05 03:27:19 +00:00
if spec :
2012-10-05 02:56:26 +00:00
raise TypeError ( ' You can provide either an image spec or '
' arguments for the ImageSpec constructor, but not both. ' )
2012-10-05 03:27:19 +00:00
else :
2012-12-01 19:11:04 +00:00
spec = create_spec_class ( spec_attrs )
2012-10-05 02:56:26 +00:00
self . _original_spec = spec
2012-10-05 03:15:16 +00:00
if spec_id :
2012-10-05 03:22:25 +00:00
self . set_spec_id ( spec_id )
2012-10-05 02:56:26 +00:00
2012-10-05 03:22:25 +00:00
def set_spec_id ( self , id ) :
2012-10-05 02:56:26 +00:00
"""
2012-10-05 03:22:25 +00:00
Sets the spec id for this object . Useful for when the id isn ' t
2012-10-05 02:56:26 +00:00
known when the instance is constructed ( e . g . for ImageSpecFields whose
generated ` spec_id ` s are only known when they are contributed to a
2012-10-05 03:22:25 +00:00
class ) . If the object was initialized with a spec , it will be registered
under the provided id .
2012-10-05 02:56:26 +00:00
"""
self . spec_id = id
2012-12-04 03:48:14 +00:00
register . spec ( id , self . _original_spec )
2012-10-05 02:56:26 +00:00
2012-10-21 03:21:01 +00:00
def get_spec ( self , * * kwargs ) :
2012-10-05 02:56:26 +00:00
"""
Look up the spec by the spec id . We do this ( instead of storing the
spec as an attribute ) so that users can override apps ' specs--without
having to edit model definitions - - simply by registering another spec
with the same id .
"""
if not getattr ( self , ' spec_id ' , None ) :
raise Exception ( ' Object %s has no spec id. ' % self )
2012-12-01 22:03:51 +00:00
return generator_registry . get ( self . spec_id , * * kwargs )