2013-07-13 21:13:58 +00:00
from copy import copy
2012-07-20 01:53:50 +00:00
from django . conf import settings
2013-01-09 02:52:56 +00:00
from django . db . models . fields . files import ImageFieldFile
2013-02-05 00:39:25 +00:00
from . . cachefiles . backends import get_default_cachefile_backend
2014-01-21 16:46:19 +00:00
from . . cachefiles . strategies import load_strategy
2013-04-25 06:29:27 +00:00
from . . import hashers
2013-04-05 19:28:36 +00:00
from . . exceptions import AlreadyRegistered , MissingSource
2013-05-10 01:35:09 +00:00
from . . utils import open_image , get_by_qname , process_image
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 .
"""
2013-02-05 00:39:25 +00:00
cachefile_storage = None
""" A Django storage system to use to save a cache file. """
2012-11-03 04:08:47 +00:00
2013-02-05 00:39:25 +00:00
cachefile_backend = None
2012-11-03 04:08:47 +00:00
"""
2013-02-05 00:39:25 +00:00
An object responsible for managing the state of cache files . Defaults to
an instance of ` ` IMAGEKIT_DEFAULT_CACHEFILE_BACKEND ` `
2012-11-03 04:08:47 +00:00
"""
2013-02-05 00:39:25 +00:00
cachefile_strategy = settings . IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY
2012-11-03 04:08:47 +00:00
"""
A dictionary containing callbacks that allow you to customize how and when
2013-01-31 08:51:29 +00:00
the image file is created . Defaults to
2013-02-05 00:39:25 +00:00
` ` IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY ` ` .
2012-11-03 04:08:47 +00:00
"""
2013-01-24 02:35:38 +00:00
def __init__ ( self ) :
2013-02-05 00:39:25 +00:00
self . cachefile_backend = self . cachefile_backend or get_default_cachefile_backend ( )
2014-01-21 16:46:19 +00:00
self . cachefile_strategy = load_strategy ( self . cachefile_strategy )
2012-11-03 04:08:47 +00:00
2012-12-02 03:23:25 +00:00
def generate ( self ) :
2012-11-03 04:08:47 +00:00
raise NotImplementedError
2013-02-26 03:34:24 +00:00
MissingSource = MissingSource
"""
Raised when an operation requiring a source is attempted on a spec that has
no source .
"""
2012-11-03 04:08:47 +00:00
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
2013-02-03 00:21:32 +00:00
processors = [ ]
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
"""
2013-01-24 02:35:38 +00:00
def __init__ ( self , source ) :
2012-12-12 03:53:13 +00:00
self . source = source
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
2013-02-05 00:39:25 +00:00
def cachefile_name ( self ) :
2013-02-26 03:18:33 +00:00
if not self . source :
return None
2013-02-05 00:39:25 +00:00
fn = get_by_qname ( settings . IMAGEKIT_SPEC_CACHEFILE_NAMER , ' namer ' )
2013-02-01 03:37:09 +00:00
return fn ( self )
2012-12-02 03:09:34 +00:00
2013-07-13 21:12:55 +00:00
@property
def source ( self ) :
src = getattr ( self , ' _source ' , None )
if not src :
field_data = getattr ( self , ' _field_data ' , None )
if field_data :
src = self . _source = getattr ( field_data [ ' instance ' ] , field_data [ ' attname ' ] )
del self . _field_data
return src
@source.setter
def source ( self , value ) :
self . _source = value
2013-01-09 02:52:56 +00:00
def __getstate__ ( self ) :
2013-07-13 21:13:58 +00:00
state = copy ( self . __dict__ )
2013-01-09 02:52:56 +00:00
# Unpickled ImageFieldFiles won't work (they're missing a storage
# object). Since they're such a common use case, we special case them.
2013-07-13 21:12:55 +00:00
# Unfortunately, this also requires us to add the source getter to
# lazily retrieve the source on the reconstructed object; simply trying
# to look up the source in ``__setstate__`` would require us to get the
# model instance but, if ``__setstate__`` was called as part of
# deserializing that model, the model wouldn't be fully reconstructed
# yet, preventing us from accessing the source field.
# (This is issue #234.)
2013-01-09 02:52:56 +00:00
if isinstance ( self . source , ImageFieldFile ) :
field = getattr ( self . source , ' field ' )
state [ ' _field_data ' ] = {
' instance ' : getattr ( self . source , ' instance ' , None ) ,
' attname ' : getattr ( field , ' name ' , None ) ,
}
2013-07-13 21:12:55 +00:00
state . pop ( ' _source ' , None )
2013-01-09 02:52:56 +00:00
return state
2012-10-21 02:53:55 +00:00
def get_hash ( self ) :
2013-04-25 06:29:27 +00:00
return hashers . pickle ( [
2013-01-09 01:57:19 +00:00
self . source . name ,
2012-12-06 04:16:07 +00:00
self . processors ,
self . format ,
self . options ,
self . autoconvert ,
2013-04-25 06:29:27 +00:00
] )
2012-10-05 01:37:20 +00:00
2012-12-02 02:20:33 +00:00
def generate ( self ) :
2013-02-26 03:34:24 +00:00
if not self . source :
raise MissingSource ( " The spec ' %s ' has no source file associated "
" with it. " % 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.)
2013-04-21 20:06:13 +00:00
2017-11-17 16:31:23 +00:00
closed = self . source . closed
if closed :
# Django file object should know how to reopen itself if it was closed
# https://code.djangoproject.com/ticket/13750
2013-04-21 20:06:13 +00:00
self . source . open ( )
2017-11-17 16:31:23 +00:00
try :
img = open_image ( self . source )
new_image = process_image ( img ,
processors = self . processors ,
format = self . format ,
autoconvert = self . autoconvert ,
options = self . options )
finally :
if closed :
# We need to close the file if it was opened by us
self . source . close ( )
2015-08-02 07:01:30 +00:00
return new_image
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
2013-04-05 18:11:26 +00:00
if self . _original_spec :
2013-04-05 19:28:36 +00:00
try :
register . generator ( id , self . _original_spec )
except AlreadyRegistered :
# Fields should not cause AlreadyRegistered exceptions. If a
# spec is already registered, that should be used. It is
# especially important that an error is not thrown here because
# of South, which will create duplicate models as part of its
# "fake orm," therefore re-registering specs.
pass
2012-10-05 02:56:26 +00:00
2013-01-24 02:35:38 +00:00
def get_spec ( self , source ) :
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 )
2013-01-24 02:35:38 +00:00
return generator_registry . get ( self . spec_id , source = source )