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 ..receivers import configure_receivers
from ...base import ImageSpec from ...base import ImageSpec
from ...utils import suggest_extension 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 The heart and soul of the ImageKit library, ImageSpecField allows you to add
variants of uploaded images to your models. variants of uploaded images to your models.
""" """
def __init__(self, processors=None, format=None, options=None, def __init__(self, processors=None, format=None, options=None,
image_field=None, storage=None, autoconvert=True, image_field=None, storage=None, autoconvert=None,
image_cache_backend=None, image_cache_strategy=None): image_cache_backend=None, image_cache_strategy=None, spec=None,
id=None):
# The spec accepts a callable value for processors, but it # The spec accepts a callable value for processors, but it
# takes different arguments than the callable that ImageSpecField # takes different arguments than the callable that ImageSpecField
# expects, so we create a partial application and pass that instead. # 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. # 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 \ p = lambda file: processors(instance=file.instance,
callable(processors) else processors file=file) if callable(processors) else processors
self.spec = ImageSpec( SpecHost.__init__(self, processors=p, format=format,
processors=p, options=options, storage=storage, autoconvert=autoconvert,
format=format, image_cache_backend=image_cache_backend,
options=options, image_cache_strategy=image_cache_strategy, spec=spec, id=id)
storage=storage,
autoconvert=autoconvert,
image_cache_backend=image_cache_backend,
image_cache_strategy=image_cache_strategy,
)
self.image_field = image_field self.image_field = image_field
@ -59,6 +56,16 @@ class ImageSpecField(object):
setattr(cls, '_ik', ik) setattr(cls, '_ik', ik)
ik.spec_fields.append(name) 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 # Register the field with the image_cache_backend
try: try:
self.spec.image_cache_backend.register_field(cls, self, name) self.spec.image_cache_backend.register_field(cls, self, name)

View file

@ -164,3 +164,61 @@ class ImageSpec(BaseImageSpec):
storage.save(filename, content) storage.save(filename, content)
return 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)