Merge branch 'ik-next' into cacheables

Conflicts:
	imagekit/management/commands/warmimagecache.py
	imagekit/registry.py
This commit is contained in:
Matthew Tretter 2013-01-28 21:45:37 -05:00
commit cef3a41d86
9 changed files with 109 additions and 45 deletions

View file

@ -5,7 +5,7 @@ from django.utils.encoding import smart_str, smart_unicode
import os
from .signals import before_access
from .utils import (format_to_mimetype, extension_to_mimetype, get_logger,
get_singleton)
get_singleton, generate)
class BaseIKFile(File):
@ -115,7 +115,8 @@ class GeneratedImageCacheFile(BaseIKFile, ImageFile):
def generate(self):
# Generate the file
content = self.generator.generate()
content = generate(self.generator)
actual_name = self.storage.save(self.name, content)
if actual_name != self.name:

View file

@ -1,5 +1,6 @@
from django.forms import ImageField
from ..specs import SpecHost
from ..utils import generate
class ProcessedImageField(ImageField, SpecHost):
@ -22,7 +23,7 @@ class ProcessedImageField(ImageField, SpecHost):
data = super(ProcessedImageField, self).clean(data, initial)
if data:
spec = self.get_spec() # HINTS?!?!?!?!?!
data = spec.apply(data, data.name)
spec = self.get_spec(source=data)
data = generate(spec)
return data

View file

@ -1,13 +1,13 @@
from django.db.models.fields.files import ImageFieldFile
import os
from ...utils import suggest_extension
from ...utils import suggest_extension, generate
class ProcessedImageFieldFile(ImageFieldFile):
def save(self, name, content, save=True):
filename, ext = os.path.splitext(name)
spec = self.field.get_spec() # TODO: What "hints"?
spec = self.field.get_spec(source=content)
ext = suggest_extension(name, spec.format)
new_name = '%s%s' % (filename, ext)
content = spec.apply(content, new_name)
content = generate(spec)
return super(ProcessedImageFieldFile, self).save(new_name, content, save)

View file

@ -29,7 +29,7 @@ class ImageSpecFileDescriptor(object):
self.attname))
else:
source = image_fields[0]
spec = self.field.get_spec(source=source) # TODO: What "hints" should we pass here?
spec = self.field.get_spec(source=source)
file = GeneratedImageCacheFile(spec)
instance.__dict__[self.attname] = file
return file

View file

@ -3,13 +3,11 @@ from django.db.models.fields.files import ImageFieldFile
from hashlib import md5
import os
import pickle
from ..exceptions import UnknownExtensionError
from ..files import GeneratedImageCacheFile, IKContentFile
from ..files import GeneratedImageCacheFile
from ..imagecache.backends import get_default_image_cache_backend
from ..imagecache.strategies import StrategyWrapper
from ..processors import ProcessorPipeline
from ..utils import (open_image, extension_to_format, img_to_fobj,
suggest_extension)
from ..utils import open_image, img_to_fobj, suggest_extension
from ..registry import generator_registry, register
@ -38,7 +36,7 @@ class BaseImageSpec(object):
"""
def __init__(self, **kwargs):
def __init__(self):
self.image_cache_backend = self.image_cache_backend or get_default_image_cache_backend()
self.image_cache_strategy = StrategyWrapper(self.image_cache_strategy)
@ -85,10 +83,9 @@ class ImageSpec(BaseImageSpec):
"""
def __init__(self, source, **kwargs):
def __init__(self, source):
self.source = source
self.processors = self.processors or []
self.kwargs = kwargs
super(ImageSpec, self).__init__()
@property
@ -130,7 +127,6 @@ class ImageSpec(BaseImageSpec):
def get_hash(self):
return md5(pickle.dumps([
self.source.name,
self.kwargs,
self.processors,
self.format,
self.options,
@ -140,9 +136,7 @@ class ImageSpec(BaseImageSpec):
def generate(self):
# 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.)
source = self.source
filename = self.kwargs.get('filename')
img = open_image(source)
img = open_image(self.source)
original_format = img.format
# Run the processors
@ -150,22 +144,8 @@ class ImageSpec(BaseImageSpec):
img = ProcessorPipeline(processors or []).process(img)
options = dict(self.options or {})
# Determine the format.
format = self.format
if filename and not format:
# Try to guess the format from the extension.
extension = os.path.splitext(filename)[1].lower()
if extension:
try:
format = extension_to_format(extension)
except UnknownExtensionError:
pass
format = format or img.format or original_format or 'JPEG'
imgfile = img_to_fobj(img, format, **options)
# TODO: Is this the right place to wrap the file? Can we use a mixin instead? Is it even still having the desired effect? Re: #111
content = IKContentFile(filename, imgfile.read(), format=format)
format = self.format or img.format or original_format or 'JPEG'
content = img_to_fobj(img, format, **options)
return content
@ -230,7 +210,7 @@ class SpecHost(object):
self.spec_id = id
register.generator(id, self._original_spec)
def get_spec(self, **kwargs):
def get_spec(self, source):
"""
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
@ -240,4 +220,4 @@ class SpecHost(object):
"""
if not getattr(self, 'spec_id', None):
raise Exception('Object %s has no spec id.' % self)
return generator_registry.get(self.spec_id, **kwargs)
return generator_registry.get(self.spec_id, source=source)

View file

@ -2,9 +2,11 @@ import logging
import os
import mimetypes
import sys
from tempfile import NamedTemporaryFile
import types
from django.core.exceptions import ImproperlyConfigured
from django.core.files import File
from django.db.models.loading import cache
from django.utils.functional import wraps
from django.utils.importlib import import_module
@ -382,3 +384,41 @@ def get_logger(logger_name='imagekit', add_null_handler=True):
if add_null_handler:
logger.addHandler(logging.NullHandler())
return logger
def get_field_info(field_file):
"""
A utility for easily extracting information about the host model from a
Django FileField (or subclass). This is especially useful for when you want
to alter processors based on a property of the source model. For example::
class MySpec(ImageSpec):
def __init__(self, source):
instance, attname = get_field_info(source)
self.processors = [SmartResize(instance.thumbnail_width,
instance.thumbnail_height)]
"""
return (
getattr(field_file, 'instance', None),
getattr(getattr(field_file, 'field', None), 'attname', None),
)
def generate(generator):
"""
Calls the ``generate()`` method of a generator instance, and then wraps the
result in a Django File object so Django knows how to save it.
"""
content = generator.generate()
# If the file doesn't have a name, Django will raise an Exception while
# trying to save it, so we create a named temporary file.
if not getattr(content, 'name', None):
f = NamedTemporaryFile()
f.write(content.read())
f.seek(0)
content = f
return File(content)

View file

@ -1,9 +1,12 @@
from django.db import models
from imagekit.models import ProcessedImageField
from imagekit.models import ImageSpecField
from imagekit.processors import Adjust
from imagekit.processors import ResizeToFill
from imagekit.processors import SmartCrop
from imagekit.processors import Adjust, ResizeToFill, SmartCrop
class ImageModel(models.Model):
image = models.ImageField(upload_to='b')
class Photo(models.Model):
@ -18,6 +21,11 @@ class Photo(models.Model):
format='JPEG', options={'quality': 90})
class ProcessedImageFieldModel(models.Model):
processed = ProcessedImageField([SmartCrop(50, 50)], format='JPEG',
options={'quality': 90}, upload_to='p')
class AbstractImageModel(models.Model):
original_image = models.ImageField(upload_to='photos')
abstract_class_spec = ImageSpecField()

36
tests/test_fields.py Normal file
View file

@ -0,0 +1,36 @@
from django import forms
from django.core.files.base import File
from django.core.files.uploadedfile import SimpleUploadedFile
from imagekit import forms as ikforms
from imagekit.processors import SmartCrop
from nose.tools import eq_
from . import imagespecs # noqa
from .models import ProcessedImageFieldModel, ImageModel
from .utils import get_image_file
def test_model_processedimagefield():
instance = ProcessedImageFieldModel()
file = File(get_image_file())
instance.processed.save('whatever.jpeg', file)
instance.save()
eq_(instance.processed.width, 50)
eq_(instance.processed.height, 50)
def test_form_processedimagefield():
class TestForm(forms.ModelForm):
image = ikforms.ProcessedImageField(spec_id='tests:testform_image',
processors=[SmartCrop(50, 50)], format='JPEG')
class Meta:
model = ImageModel
upload_file = get_image_file()
file_dict = {'image': SimpleUploadedFile('abc.jpg', upload_file.read())}
form = TestForm({}, file_dict)
instance = form.save()
eq_(instance.image.width, 50)
eq_(instance.image.height, 50)

View file

@ -1,7 +1,7 @@
from bs4 import BeautifulSoup
import os
from django.conf import settings
from django.core.files.base import ContentFile
from django.core.files import File
from django.template import Context, Template
from imagekit.lib import Image, StringIO
import pickle
@ -26,10 +26,8 @@ def create_image():
def create_instance(model_class, image_name):
instance = model_class()
img = get_image_file()
file = ContentFile(img.read())
instance.original_image = file
instance.original_image.save(image_name, file)
img = File(get_image_file())
instance.original_image.save(image_name, img)
instance.save()
img.close()
return instance