Merge branch 'python3' of https://github.com/vstoykov/django-imagekit into vstoykov-python3

* 'python3' of https://github.com/vstoykov/django-imagekit:
  Add Venelin Stoykov to AUTHORS
  Improve logic of contributing ImageSpecFields to Models
  Use force_bytes from imagekit.lib in test_cachefiles
  Remove @vstoykov's note. Seems like the right place to me (:
  Move force_bytes into lib module
  Don't use a raw string with \u escapes
  Fix sanitizing cache key under Python 3
  Add module to sys.modules
  Test for Python 3
  Insert importer at beginning of list
  Delay Django import until needed
  Add Python 3 suport and drop support for Python 2.5

Conflicts:
	imagekit/cachefiles/__init__.py
This commit is contained in:
Bryan Veloso 2013-12-26 17:30:42 -08:00
commit ce9a62c02c
13 changed files with 129 additions and 54 deletions

View file

@ -28,6 +28,7 @@ Contributors
* `Jannis Leidel`_
* `Sean Bell`_
* `Saul Shanabrook`_
* `Venelin Stoykov`_
.. _Justin Driscoll: http://github.com/jdriscoll
.. _HZDG: http://hzdg.com
@ -49,3 +50,4 @@ Contributors
.. _Jannis Leidel: https://github.com/jezdez
.. _Sean Bell: https://github.com/seanbell
.. _Saul Shanabrook: https://github.com/saulshanabrook
.. _Venelin Stoykov: https://github.com/vstoykov

View file

@ -121,7 +121,7 @@ class ImageCacheFile(BaseIKFile, ImageFile):
)
)
def __nonzero__(self):
def __bool__(self):
if not self.name:
return False
@ -138,6 +138,10 @@ class ImageCacheFile(BaseIKFile, ImageFile):
return state
def __nonzero__(self):
# Python 2 compatibility
return self.__bool__()
class LazyImageCacheFile(SimpleLazyObject):
def __init__(self, generator_id, *args, **kwargs):

View file

@ -1,4 +1,7 @@
import six
from django.utils.functional import LazyObject
from ..lib import force_text
from ..utils import get_singleton
@ -35,7 +38,7 @@ class DictStrategy(object):
class StrategyWrapper(LazyObject):
def __init__(self, strategy):
if isinstance(strategy, basestring):
if isinstance(strategy, six.string_types):
strategy = get_singleton(strategy, 'cache file strategy')
elif isinstance(strategy, dict):
strategy = DictStrategy(strategy)
@ -50,7 +53,7 @@ class StrategyWrapper(LazyObject):
self._wrapped = state['_wrapped']
def __unicode__(self):
return unicode(self._wrapped)
return force_text(self._wrapped)
def __str__(self):
return str(self._wrapped)

View file

@ -1,6 +1,9 @@
from django.core.files.base import File, ContentFile
from django.utils.encoding import smart_str, smart_unicode
from __future__ import unicode_literals
import os
from django.core.files.base import File, ContentFile
from django.utils.encoding import smart_str
from .lib import smart_text
from .utils import format_to_mimetype, extension_to_mimetype
@ -92,4 +95,5 @@ class IKContentFile(ContentFile):
return smart_str(self.file.name or '')
def __unicode__(self):
return smart_unicode(self.file.name or u'')
# Python 2
return smart_text(self.file.name or '')

View file

@ -1,12 +1,16 @@
from copy import copy
from hashlib import md5
from pickle import Pickler, MARK, DICT
from types import DictionaryType
from pickle import MARK, DICT
try:
from pickle import _Pickler
except ImportError:
# Python 2 compatible
from pickle import Pickler as _Pickler
from .lib import StringIO
class CanonicalizingPickler(Pickler):
dispatch = copy(Pickler.dispatch)
class CanonicalizingPickler(_Pickler):
dispatch = copy(_Pickler.dispatch)
def save_set(self, obj):
rv = obj.__reduce_ex__(0)
@ -20,9 +24,9 @@ class CanonicalizingPickler(Pickler):
write(MARK + DICT)
self.memoize(obj)
self._batch_setitems(sorted(obj.iteritems()))
self._batch_setitems(sorted(obj.items()))
dispatch[DictionaryType] = save_dict
dispatch[dict] = save_dict
def pickle(obj):

View file

@ -1,4 +1,3 @@
from django.utils.importlib import import_module
import re
import sys
@ -22,8 +21,11 @@ class ProcessorImporter(object):
if name in sys.modules:
return sys.modules[name]
from django.utils.importlib import import_module
new_name = self.pattern.sub(r'pilkit.processors\1', name)
return import_module(new_name)
mod = import_module(new_name)
sys.modules[name] = mod
return mod
sys.meta_path.append(ProcessorImporter())
sys.meta_path.insert(0, ProcessorImporter())

View file

@ -19,9 +19,12 @@ except ImportError:
raise ImportError('ImageKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from io import BytesIO as StringIO
except:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
try:
from logging import NullHandler
@ -31,3 +34,19 @@ except ImportError:
class NullHandler(Handler):
def emit(self, record):
pass
# Try to import `force_text` available from Django 1.5
# This function will replace `unicode` used in the code
# If Django version is under 1.5 then use `force_unicde`
# It is used for compatibility between Python 2 and Python 3
try:
from django.utils.encoding import force_text, force_bytes, smart_text
except ImportError:
# Django < 1.5
from django.utils.encoding import (force_unicode as force_text,
smart_str as force_bytes,
smart_unicode as smart_text)
__all__ = ['Image', 'ImageColor', 'ImageChops', 'ImageEnhance', 'ImageFile',
'ImageFilter', 'ImageDraw', 'ImageStat', 'StringIO', 'NullHandler',
'force_text', 'force_bytes', 'smart_text']

View file

@ -1,4 +1,8 @@
from __future__ import unicode_literals
from django.db import models
from django.db.models.signals import class_prepared
from django.dispatch import receiver
from .files import ProcessedImageFieldFile
from .utils import ImageSpecFileDescriptor
from ...specs import SpecHost
@ -13,7 +17,7 @@ class SpecHostField(SpecHost):
# Generate a spec_id to register the spec with. The default spec id is
# "<app>:<model>_<field>"
if not spec_id:
spec_id = (u'%s:%s:%s' % (cls._meta.app_label,
spec_id = ('%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
@ -44,27 +48,38 @@ class ImageSpecField(SpecHostField):
def contribute_to_class(self, cls, name):
# If the source field name isn't defined, figure it out.
source = self.source
if not source:
image_fields = [f.attname for f in cls._meta.fields if
isinstance(f, models.ImageField)]
if len(image_fields) == 0:
raise Exception(
'%s does not define any ImageFields, so your %s'
' ImageSpecField has no image to act on.' %
(cls.__name__, name))
elif len(image_fields) > 1:
raise Exception(
'%s defines multiple ImageFields, but you have not'
' specified a source for your %s ImageSpecField.' %
(cls.__name__, name))
source = image_fields[0]
setattr(cls, name, ImageSpecFileDescriptor(self, name, source))
self._set_spec_id(cls, name)
def register_source_group(source):
setattr(cls, name, ImageSpecFileDescriptor(self, name, source))
self._set_spec_id(cls, name)
# Add the model and field as a source for this spec id
register.source_group(self.spec_id, ImageFieldSourceGroup(cls, source))
if self.source:
register_source_group(self.source)
else:
# The source argument is not defined
# Then we need to see if there is only one ImageField in that model
# But we need to do that after full model initialization
@receiver(class_prepared, sender=cls, weak=False)
def handle_model_preparation(sender, **kwargs):
image_fields = [f.attname for f in cls._meta.fields if
isinstance(f, models.ImageField)]
if len(image_fields) == 0:
raise Exception(
'%s does not define any ImageFields, so your %s'
' ImageSpecField has no image to act on.' %
(cls.__name__, name))
elif len(image_fields) > 1:
raise Exception(
'%s defines multiple ImageFields, but you have not'
' specified a source for your %s ImageSpecField.' %
(cls.__name__, name))
register_source_group(image_fields[0])
# Add the model and field as a source for this spec id
register.source_group(self.spec_id, ImageFieldSourceGroup(cls, source))
class ProcessedImageField(models.ImageField, SpecHostField):

View file

@ -1,9 +1,13 @@
from __future__ import unicode_literals
from django import template
from django.utils.html import escape
from django.utils.safestring import mark_safe
from .compat import parse_bits
from ..cachefiles import ImageCacheFile
from ..registry import generator_registry
from ..lib import force_text
register = template.Library()
@ -40,7 +44,7 @@ class GenerateImageAssignmentNode(template.Node):
self._variable_name = variable_name
def get_variable_name(self, context):
return unicode(self._variable_name)
return force_text(self._variable_name)
def render(self, context):
variable_name = self.get_variable_name(context)
@ -70,7 +74,7 @@ class GenerateImageTagNode(template.Node):
attrs['src'] = file.url
attr_str = ' '.join('%s="%s"' % (escape(k), escape(v)) for k, v in
attrs.items())
return mark_safe(u'<img %s />' % attr_str)
return mark_safe('<img %s />' % attr_str)
class ThumbnailAssignmentNode(template.Node):
@ -83,7 +87,7 @@ class ThumbnailAssignmentNode(template.Node):
self._generator_kwargs = generator_kwargs
def get_variable_name(self, context):
return unicode(self._variable_name)
return force_text(self._variable_name)
def render(self, context):
variable_name = self.get_variable_name(context)
@ -131,7 +135,7 @@ class ThumbnailImageTagNode(template.Node):
attrs['src'] = file.url
attr_str = ' '.join('%s="%s"' % (escape(k), escape(v)) for k, v in
attrs.items())
return mark_safe(u'<img %s />' % attr_str)
return mark_safe('<img %s />' % attr_str)
def parse_ik_tag_bits(parser, bits):

View file

@ -1,17 +1,18 @@
from __future__ import unicode_literals
import logging
import re
from tempfile import NamedTemporaryFile
from hashlib import md5
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files import File
from django.utils.importlib import import_module
from hashlib import md5
from pilkit.utils import *
import re
from .lib import NullHandler
from .lib import NullHandler, force_bytes
bad_memcached_key_chars = re.compile(ur'[\u0000-\u001f\s]+')
bad_memcached_key_chars = re.compile('[\u0000-\u001f\\s]+')
_autodiscovered = False
@ -32,7 +33,7 @@ def get_by_qname(path, desc):
module, objname = path[:dot], path[dot + 1:]
try:
mod = import_module(module)
except ImportError, e:
except ImportError as e:
raise ImproperlyConfigured('Error importing %s module %s: "%s"' %
(desc, module, e))
try:
@ -147,7 +148,7 @@ def sanitize_cache_key(key):
# The also can't be > 250 chars long. Since we don't know what the
# user's cache ``KEY_FUNCTION`` setting is like, we'll limit it to 200.
if len(new_key) >= 200:
new_key = '%s:%s' % (new_key[:200-33], md5(key).hexdigest())
new_key = '%s:%s' % (new_key[:200-33], md5(force_bytes(key)).hexdigest())
key = new_key
return key

View file

@ -19,10 +19,12 @@ if 'publish' in sys.argv:
read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read()
def exec_file(filepath, globalz=None, localz=None):
exec(read(filepath), globalz, localz)
# Load package meta from the pkgmeta module without loading imagekit.
pkgmeta = {}
execfile(os.path.join(os.path.dirname(__file__),
exec_file(os.path.join(os.path.dirname(__file__),
'imagekit', 'pkgmeta.py'), pkgmeta)
@ -42,15 +44,16 @@ setup(
include_package_data=True,
tests_require=[
'beautifulsoup4==4.1.3',
'nose==1.2.1',
'nose-progressive==1.3',
'django-nose==1.1',
'nose==1.3.0',
'nose-progressive==1.5',
'django-nose==1.2',
'Pillow<3.0',
],
test_suite='testrunner.run_tests',
install_requires=[
'django-appconf>=0.5',
'pilkit>=0.2.0',
'six',
],
extras_require={
'async': ['django-celery>=3.0'],
@ -62,9 +65,10 @@ setup(
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Topic :: Utilities'
],
)

View file

@ -2,6 +2,7 @@ from django.conf import settings
from hashlib import md5
from imagekit.cachefiles import ImageCacheFile, LazyImageCacheFile
from imagekit.cachefiles.backends import Simple
from imagekit.lib import force_bytes
from nose.tools import raises, eq_
from .imagegenerators import TestSpec
from .utils import (assert_file_is_truthy, assert_file_is_falsy,
@ -73,7 +74,7 @@ def test_memcached_cache_key():
eq_(backend.get_key(file), '%s%s:%s' % (
settings.IMAGEKIT_CACHE_PREFIX,
'1' * (200 - len(':') - 32 - len(settings.IMAGEKIT_CACHE_PREFIX)),
md5('%s%s-state' % (settings.IMAGEKIT_CACHE_PREFIX, filename)).hexdigest()))
md5(force_bytes('%s%s-state' % (settings.IMAGEKIT_CACHE_PREFIX, filename))).hexdigest()))
def test_lazyfile_stringification():

12
tox.ini
View file

@ -1,11 +1,23 @@
[tox]
envlist =
py33-django15,
py32-django15,
py27-django15, py27-django14, py27-django13, py27-django12,
py26-django15, py26-django14, py26-django13, py26-django12
[testenv]
commands = python setup.py test
[testenv:py33-django15]
basepython = python3.3
deps =
Django>=1.5,<1.6
[testenv:py32-django15]
basepython = python3.2
deps =
Django>=1.5,<1.6
[testenv:py27-django15]
basepython = python2.7
deps =