django-imagekit/imagekit/models/fields/__init__.py

149 lines
5.7 KiB
Python
Raw Normal View History

2012-02-14 16:34:51 +00:00
import os
2008-12-28 21:48:21 +00:00
from django.db import models
from django.db.models.signals import post_init, post_save, post_delete
2012-10-05 01:37:20 +00:00
from .files import ProcessedImageFieldFile
from .utils import ImageSpecFileDescriptor, ImageKitMeta
from ...base import ImageSpec
from ...utils import suggest_extension
2012-02-12 01:11:56 +00:00
class ImageSpecField(object):
2011-10-31 14:12:03 +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
"""
2012-04-10 01:25:46 +00:00
def __init__(self, processors=None, format=None, options=None,
2012-10-05 01:37:20 +00:00
image_field=None, storage=None, autoconvert=True,
image_cache_backend=None, image_cache_strategy=None):
2011-09-21 15:37:29 +00:00
2012-10-05 01:37:20 +00:00
# The spec accepts a callable value for processors, but it
2012-02-12 04:22:06 +00:00
# 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
2012-10-05 01:37:20 +00:00
self.spec = ImageSpec(
processors=p,
format=format,
options=options,
storage=storage,
autoconvert=autoconvert,
image_cache_backend=image_cache_backend,
image_cache_strategy=image_cache_strategy,
)
self.image_field = image_field
2012-10-05 01:37:20 +00:00
@property
def storage(self):
return self.spec.storage
2011-09-21 15:37:29 +00:00
def contribute_to_class(self, cls, name):
setattr(cls, name, ImageSpecFileDescriptor(self, name))
try:
# 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)
setattr(cls, '_ik', ik)
ik.spec_fields.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__)
2012-02-12 22:06:37 +00:00
post_init.connect(ImageSpecField._post_init_receiver, sender=cls,
dispatch_uid=uid)
2012-02-12 22:06:37 +00:00
post_save.connect(ImageSpecField._post_save_receiver, sender=cls,
dispatch_uid=uid)
2012-02-12 22:06:37 +00:00
post_delete.connect(ImageSpecField._post_delete_receiver, sender=cls,
dispatch_uid=uid)
2011-09-21 15:37:29 +00:00
# Register the field with the image_cache_backend
try:
2012-10-05 01:37:20 +00:00
self.spec.image_cache_backend.register_field(cls, self, name)
except AttributeError:
pass
@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)
for attname in instance._ik.spec_fields:
file = getattr(instance, attname)
if created:
2012-10-05 01:37:20 +00:00
file.field.spec.image_cache_strategy.invoke_callback('source_create', file)
elif old_hashes[attname] != new_hashes[attname]:
2012-10-05 01:37:20 +00:00
file.field.spec.image_cache_strategy.invoke_callback('source_change', file)
@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:
2012-10-05 01:37:20 +00:00
spec_file.field.spec.image_cache_strategy.invoke_callback('source_delete', spec_file)
@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.
"""
attr_class = ProcessedImageFieldFile
2012-04-10 01:25:46 +00:00
def __init__(self, processors=None, format=None, options=None,
verbose_name=None, name=None, width_field=None, height_field=None,
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
as the ``processors``, ``format``, and ``options`` arguments of
:class:`imagekit.models.ImageSpecField`.
2011-09-26 00:38:43 +00:00
"""
models.ImageField.__init__(self, verbose_name, name, width_field,
height_field, **kwargs)
2012-10-05 01:37:20 +00:00
self.spec = ImageSpec(processors, format=format, options=options,
autoconvert=autoconvert)
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)))
name, ext = os.path.splitext(filename)
2012-10-05 01:37:20 +00:00
ext = suggest_extension(filename, self.spec.format)
2012-02-16 13:47:22 +00:00
return u'%s%s' % (name, ext)
try:
from south.modelsinspector import add_introspection_rules
except ImportError:
pass
else:
add_introspection_rules([], [r'^imagekit\.models\.fields\.ProcessedImageField$'])