Back ImageSpecFields with spec registry

This marks a major step towards centralizing some of the "spec" logic
and creating a single access point for them. Because `ImageSpecFields`
are just alternative interfaces for defining and registering specs,
they can be accessed and overridden in the same manner as other specs
(like those used by template tags): via the spec registry.
This commit is contained in:
Matthew Tretter 2012-10-04 22:56:26 -04:00
parent 99ba61d605
commit f289ff3199
2 changed files with 79 additions and 14 deletions

View file

@ -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
# "<app>:<model>_<field>"
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)

View file

@ -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)