mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-03-16 21:30:23 +00:00
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:
commit
ce9a62c02c
13 changed files with 129 additions and 54 deletions
2
AUTHORS
2
AUTHORS
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 '')
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
14
setup.py
14
setup.py
|
|
@ -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'
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
12
tox.ini
|
|
@ -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 =
|
||||
|
|
|
|||
Loading…
Reference in a new issue