diff --git a/imagekit/models/fields/__init__.py b/imagekit/models/fields/__init__.py index 34af0c7..2747ebf 100644 --- a/imagekit/models/fields/__init__.py +++ b/imagekit/models/fields/__init__.py @@ -8,34 +8,31 @@ from .utils import ImageSpecFileDescriptor, ImageKitMeta from ..receivers import configure_receivers from ...base import ImageSpec from ...utils import suggest_extension +from ...specs import SpecHost -class ImageSpecField(object): +class ImageSpecField(SpecHost): """ The heart and soul of the ImageKit library, ImageSpecField allows you to add variants of uploaded images to your models. """ def __init__(self, processors=None, format=None, options=None, - image_field=None, storage=None, autoconvert=True, - image_cache_backend=None, image_cache_strategy=None): + image_field=None, storage=None, autoconvert=None, + image_cache_backend=None, image_cache_strategy=None, spec=None, + id=None): # The spec 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 + p = lambda file: processors(instance=file.instance, + file=file) if callable(processors) else processors - 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, - ) + SpecHost.__init__(self, processors=p, format=format, + options=options, storage=storage, autoconvert=autoconvert, + image_cache_backend=image_cache_backend, + image_cache_strategy=image_cache_strategy, spec=spec, id=id) self.image_field = image_field @@ -59,6 +56,16 @@ class ImageSpecField(object): setattr(cls, '_ik', ik) ik.spec_fields.append(name) + # Generate a spec_id to register the spec with. The default spec id is + # ":_" + if not self.spec_id: + self.spec_id = (u'%s:%s_%s' % (cls._meta.app_label, + cls._meta.object_name, name)).lower() + + # Register the spec with the id. This allows specs to be overridden + # later, from outside of the model definition. + self.register_spec(self.spec_id) + # Register the field with the image_cache_backend try: self.spec.image_cache_backend.register_field(cls, self, name) diff --git a/imagekit/specs.py b/imagekit/specs.py index a81b82c..d2e0cba 100644 --- a/imagekit/specs.py +++ b/imagekit/specs.py @@ -164,3 +164,61 @@ class ImageSpec(BaseImageSpec): storage.save(filename, content) return content + + +class SpecHost(object): + """ + An object that ostensibly has a spec attribute but really delegates to the + spec registry. + + """ + def __init__(self, processors=None, format=None, options=None, + storage=None, autoconvert=None, image_cache_backend=None, + image_cache_strategy=None, spec=None, id=None): + + if spec: + if any([processors, format, options, storage, autoconvert, + image_cache_backend, image_cache_strategy]): + raise TypeError('You can provide either an image spec or' + ' arguments for the ImageSpec constructor, but not both.') + else: + spec = ImageSpec( + processors=processors, + format=format, + options=options, + storage=storage, + autoconvert=autoconvert, + image_cache_backend=image_cache_backend, + image_cache_strategy=image_cache_strategy, + ) + + self._original_spec = spec + self.spec_id = None + + if id: + # If an id is given, register the spec immediately. + self.register_spec(id) + + def register_spec(self, id): + """ + Registers the spec with the specified id. Useful for when the id isn't + known when the instance is constructed (e.g. for ImageSpecFields whose + generated `spec_id`s are only known when they are contributed to a + class) + + """ + self.spec_id = id + spec_registry.register(id, self._original_spec) + + @property + def spec(self): + """ + 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) + return spec_registry.get_spec(self.spec_id)