mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-03-17 13:50:24 +00:00
Compare commits
12 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96f0b5da4d | ||
|
|
b398c2cee4 | ||
|
|
2bcb93da22 | ||
|
|
00b4095e40 | ||
|
|
bc0b0a8a75 | ||
|
|
b176c0cafe | ||
|
|
9f69d7e056 | ||
|
|
ab3593ed54 | ||
|
|
a14fc91c8d | ||
|
|
6929128180 | ||
|
|
a9a152cdfa | ||
|
|
31eab7666a |
42 changed files with 254 additions and 450 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -4,14 +4,11 @@
|
|||
*.pyc
|
||||
.DS_Store
|
||||
.tox
|
||||
.idea
|
||||
.vscode
|
||||
MANIFEST
|
||||
build
|
||||
dist
|
||||
/tests/media/*
|
||||
!/tests/media/reference.png
|
||||
!/tests/media/lenna.png
|
||||
/venv
|
||||
/venv3
|
||||
/.env
|
||||
/tags
|
||||
|
|
|
|||
34
.travis.yml
34
.travis.yml
|
|
@ -1,35 +1,7 @@
|
|||
sudo: false
|
||||
|
||||
language: python
|
||||
python:
|
||||
- "3.8"
|
||||
- "3.7"
|
||||
- "3.6"
|
||||
- "3.5"
|
||||
env:
|
||||
- DJANGO="master"
|
||||
- DJANGO="30"
|
||||
- DJANGO="22"
|
||||
- DJANGO="21"
|
||||
- DJANGO="21"
|
||||
- DJANGO="20"
|
||||
- DJANGO="111"
|
||||
|
||||
install:
|
||||
- pip install tox
|
||||
|
||||
script:
|
||||
- tox -e py$(python -c 'import sys;print("".join(map(str, sys.version_info[:2])))')-django${DJANGO}
|
||||
|
||||
jobs:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: DJANGO="master"
|
||||
exclude:
|
||||
- python: "3.5"
|
||||
env: DJANGO="30"
|
||||
- python: "3.5"
|
||||
env: DJANGO="master"
|
||||
|
||||
- 2.7
|
||||
install: pip install tox --use-mirrors
|
||||
script: tox
|
||||
notifications:
|
||||
irc: "irc.freenode.org#imagekit"
|
||||
|
|
|
|||
18
MANIFEST.in
18
MANIFEST.in
|
|
@ -1,18 +1,6 @@
|
|||
include AUTHORS
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include testrunner.py
|
||||
include setup.cfg
|
||||
include tests/*.py
|
||||
include tests/assets/Lenna.png
|
||||
include tests/assets/lenna-*.jpg
|
||||
include tests/media/lenna.png
|
||||
prune tests/media/CACHE
|
||||
prune tests/media/b
|
||||
prune tests/media/photos
|
||||
include docs/Makefile
|
||||
include docs/conf.py
|
||||
include docs/make.bat
|
||||
include docs/*.rst
|
||||
recursive-include docs/_themes LICENSE README.rst flask_theme_support.py theme.conf *.css_t *.css *.html
|
||||
recursive-include imagekit/templates *.html
|
||||
recursive-include docs *
|
||||
recursive-include imagekit/templates *
|
||||
prune tests
|
||||
|
|
|
|||
46
README.rst
46
README.rst
|
|
@ -1,6 +1,6 @@
|
|||
|Build Status|_
|
||||
|
||||
.. |Build Status| image:: https://travis-ci.org/matthewwithanm/django-imagekit.svg?branch=develop
|
||||
.. |Build Status| image:: https://travis-ci.org/matthewwithanm/django-imagekit.png?branch=develop
|
||||
.. _Build Status: https://travis-ci.org/matthewwithanm/django-imagekit
|
||||
|
||||
ImageKit is a Django app for processing images. Need a thumbnail? A
|
||||
|
|
@ -39,7 +39,6 @@ Installation
|
|||
Usage Overview
|
||||
==============
|
||||
|
||||
.. _specs:
|
||||
|
||||
Specs
|
||||
-----
|
||||
|
|
@ -71,8 +70,8 @@ your model class:
|
|||
options={'quality': 60})
|
||||
|
||||
profile = Profile.objects.all()[0]
|
||||
print(profile.avatar_thumbnail.url) # > /media/CACHE/images/982d5af84cddddfd0fbf70892b4431e4.jpg
|
||||
print(profile.avatar_thumbnail.width) # > 100
|
||||
print profile.avatar_thumbnail.url # > /media/CACHE/images/982d5af84cddddfd0fbf70892b4431e4.jpg
|
||||
print profile.avatar_thumbnail.width # > 100
|
||||
|
||||
As you can probably tell, ImageSpecFields work a lot like Django's
|
||||
ImageFields. The difference is that they're automatically generated by
|
||||
|
|
@ -98,8 +97,8 @@ class:
|
|||
options={'quality': 60})
|
||||
|
||||
profile = Profile.objects.all()[0]
|
||||
print(profile.avatar_thumbnail.url) # > /media/avatars/MY-avatar.jpg
|
||||
print(profile.avatar_thumbnail.width) # > 100
|
||||
print profile.avatar_thumbnail.url # > /media/avatars/MY-avatar.jpg
|
||||
print profile.avatar_thumbnail.width # > 100
|
||||
|
||||
This is pretty similar to our previous example. We don't need to specify a
|
||||
"source" any more since we're not processing another image field, but we do need
|
||||
|
|
@ -145,7 +144,7 @@ on, or what should be done with the result; that's up to you:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
source_file = open('/path/to/myimage.jpg', 'rb')
|
||||
source_file = open('/path/to/myimage.jpg')
|
||||
image_generator = Thumbnail(source=source_file)
|
||||
result = image_generator.generate()
|
||||
|
||||
|
|
@ -160,7 +159,7 @@ example, if you wanted to save it to disk:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
dest = open('/path/to/dest.jpg', 'wb')
|
||||
dest = open('/path/to/dest.jpg', 'w')
|
||||
dest.write(result.read())
|
||||
dest.close()
|
||||
|
||||
|
|
@ -407,37 +406,6 @@ Django admin classes:
|
|||
|
||||
admin.site.register(Photo, PhotoAdmin)
|
||||
|
||||
To use specs defined outside of models:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.contrib import admin
|
||||
from imagekit.admin import AdminThumbnail
|
||||
from imagekit import ImageSpec
|
||||
from imagekit.processors import ResizeToFill
|
||||
from imagekit.cachefiles import ImageCacheFile
|
||||
|
||||
from .models import Photo
|
||||
|
||||
class AdminThumbnailSpec(ImageSpec):
|
||||
processors = [ResizeToFill(100, 30)]
|
||||
format = 'JPEG'
|
||||
options = {'quality': 60 }
|
||||
|
||||
def cached_admin_thumb(instance):
|
||||
# `image` is the name of the image field on the model
|
||||
cached = ImageCacheFile(AdminThumbnailSpec(instance.image))
|
||||
# only generates the first time, subsequent calls use cache
|
||||
cached.generate()
|
||||
return cached
|
||||
|
||||
class PhotoAdmin(admin.ModelAdmin):
|
||||
list_display = ('__str__', 'admin_thumbnail')
|
||||
admin_thumbnail = AdminThumbnail(image_field=cached_admin_thumb)
|
||||
|
||||
admin.site.register(Photo, PhotoAdmin)
|
||||
|
||||
|
||||
AdminThumbnail can even use a custom template. For more information, see
|
||||
``imagekit.admin.AdminThumbnail``.
|
||||
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ A simple example of a custom source group class is as follows:
|
|||
def files(self):
|
||||
os.chdir(self.dir)
|
||||
for name in glob.glob('*.jpg'):
|
||||
yield open(name, 'rb')
|
||||
yield open(name)
|
||||
|
||||
Instances of this class could then be registered with one or more spec id:
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Caching
|
|||
|
||||
|
||||
Default Backend Workflow
|
||||
========================
|
||||
================
|
||||
|
||||
|
||||
``ImageSpec``
|
||||
|
|
@ -29,8 +29,6 @@ objects, but they've got a little trick up their sleeve: they represent files
|
|||
that may not actually exist!
|
||||
|
||||
|
||||
.. _cache-file-strategy:
|
||||
|
||||
Cache File Strategy
|
||||
-------------------
|
||||
|
||||
|
|
@ -57,8 +55,6 @@ The default strategy only defines the first two of these, as follows:
|
|||
file.generate()
|
||||
|
||||
|
||||
.. _cache-file-backend:
|
||||
|
||||
Cache File Backend
|
||||
------------------
|
||||
|
||||
|
|
@ -104,21 +100,13 @@ ImageKit. Each has its own pros and cons.
|
|||
Caching Data About Generated Files
|
||||
----------------------------------
|
||||
|
||||
Generally, once a file is generated, you will never be removing it, so by
|
||||
default ImageKit will use default cache to cache the state of generated
|
||||
files "forever" (or only 5 minutes when ``DEBUG = True``).
|
||||
|
||||
The time for which ImageKit will cache state is configured with
|
||||
``IMAGEKIT_CACHE_TIMEOUT``. If set to ``None`` this means "never expire"
|
||||
(default when ``DEBUG = False``). You can reduce this timeout if you want
|
||||
or set it to some numeric value in seconds if your cache backend behaves
|
||||
differently and for example do not cache values if timeout is ``None``.
|
||||
|
||||
If you clear your cache durring deployment or some other reason probably
|
||||
you do not want to lose the cache for generated images especcialy if you
|
||||
are using some slow remote storage (like Amazon S3). Then you can configure
|
||||
seprate cache (for example redis) in your ``CACHES`` config and tell ImageKit
|
||||
to use it instead of the default cache by setting ``IMAGEKIT_CACHE_BACKEND``.
|
||||
The easiest, and most significant improvement you can make to improve the
|
||||
performance of your site is to have ImageKit cache the state of your generated
|
||||
files. The default cache file backend will already do this (if ``DEBUG`` is
|
||||
``False``), using your default Django cache backend, but you can make it way
|
||||
better by setting ``IMAGEKIT_CACHE_BACKEND``. Generally, once a file is
|
||||
generated, you will never be removing it; therefore, if you can, you should set
|
||||
``IMAGEKIT_CACHE_BACKEND`` to a cache backend that will cache forever.
|
||||
|
||||
|
||||
Pre-Generating Images
|
||||
|
|
@ -189,11 +177,6 @@ Or, in Python:
|
|||
def on_source_saved(self, file):
|
||||
file.generate()
|
||||
|
||||
.. note::
|
||||
|
||||
If you use custom storage backend for some specs,
|
||||
(storage passed to the field different than configured one)
|
||||
it's required the storage to be pickleable
|
||||
|
||||
|
||||
__ https://pypi.python.org/pypi/django-celery
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ execfile(os.path.join(os.path.dirname(__file__), '..', 'imagekit',
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = re.match(r'\d+\.\d+', pkgmeta['__version__']).group()
|
||||
version = re.match('\d+\.\d+', pkgmeta['__version__']).group()
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = pkgmeta['__version__']
|
||||
|
||||
|
|
|
|||
|
|
@ -44,24 +44,11 @@ Settings
|
|||
|
||||
.. attribute:: IMAGEKIT_CACHE_BACKEND
|
||||
|
||||
:default: ``'default'``
|
||||
:default: If ``DEBUG`` is ``True``, ``'django.core.cache.backends.dummy.DummyCache'``.
|
||||
Otherwise, ``'default'``.
|
||||
|
||||
The Django cache backend alias to retrieve the shared cache instance defined
|
||||
in your settings, as described in the `Django cache section`_.
|
||||
|
||||
The cache is then used to store information like the state of cached
|
||||
images (i.e. validated or not).
|
||||
|
||||
.. _`Django cache section`: https://docs.djangoproject.com/en/1.8/topics/cache/#accessing-the-cache
|
||||
|
||||
|
||||
.. attribute:: IMAGEKIT_CACHE_TIMEOUT
|
||||
|
||||
:default: ``None``
|
||||
|
||||
Use when you need to override the timeout used to cache file state.
|
||||
By default it is "cache forever".
|
||||
It's highly recommended that you use a very high timeout.
|
||||
The Django cache backend to be used to store information like the state of
|
||||
cached images (i.e. validated or not).
|
||||
|
||||
|
||||
.. attribute:: IMAGEKIT_CACHE_PREFIX
|
||||
|
|
|
|||
|
|
@ -79,9 +79,12 @@ IK3 provides analogous settings for cache file backends and strategies:
|
|||
IMAGEKIT_DEFAULT_CACHEFILE_BACKEND = 'path.to.MyCacheFileBackend'
|
||||
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = 'path.to.MyCacheFileStrategy'
|
||||
|
||||
See the documentation on :ref:`cache file backends <cache-file-backend>` and :ref:`cache file strategies <cache-file-strategy>`
|
||||
See the documentation on `cache file backends`_ and `cache file strategies`_
|
||||
for more details.
|
||||
|
||||
.. _`cache file backends`:
|
||||
.. _`cache file strategies`:
|
||||
|
||||
|
||||
Conditional model ``processors``
|
||||
--------------------------------
|
||||
|
|
@ -90,7 +93,9 @@ In IK2, an ``ImageSpecField`` could take a ``processors`` callable instead of
|
|||
an iterable, which allowed processing decisions to made based on other
|
||||
properties of the model. IK3 does away with this feature for consistency's sake
|
||||
(if one kwarg could be callable, why not all?), but provides a much more robust
|
||||
solution: the custom ``spec``. See the :doc:`advanced usage <advanced_usage>` documentation for more.
|
||||
solution: the custom ``spec``. See the `advanced usage`_ documentation for more.
|
||||
|
||||
.. _`advanced usage`:
|
||||
|
||||
|
||||
Conditonal ``cache_to`` file names
|
||||
|
|
@ -104,7 +109,9 @@ There is a way to achieve custom file names by overriding your spec's
|
|||
``cachefile_name``, but it is not recommended, as the spec's default
|
||||
behavior is to hash the combination of ``source``, ``processors``, ``format``,
|
||||
and other spec options to ensure that changes to the spec always result in
|
||||
unique file names. See the documentation on :ref:`specs` for more.
|
||||
unique file names. See the documentation on `specs`_ for more.
|
||||
|
||||
.. _`specs`:
|
||||
|
||||
|
||||
Processors have moved to PILKit
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ from django.conf import settings
|
|||
from django.core.files import File
|
||||
from django.core.files.images import ImageFile
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
from django.utils.encoding import smart_str
|
||||
from ..files import BaseIKFile
|
||||
from ..registry import generator_registry
|
||||
from ..signals import content_required, existence_required
|
||||
|
|
@ -144,33 +143,12 @@ class ImageCacheFile(BaseIKFile, ImageFile):
|
|||
# file is hidden link to "file" attribute
|
||||
state.pop('_file', None)
|
||||
|
||||
# remove storage from state as some non-FileSystemStorage can't be
|
||||
# pickled
|
||||
settings_storage = get_singleton(
|
||||
settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
|
||||
'file storage backend'
|
||||
)
|
||||
if state['storage'] == settings_storage:
|
||||
state.pop('storage')
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
if 'storage' not in state:
|
||||
state['storage'] = get_singleton(
|
||||
settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
|
||||
'file storage backend'
|
||||
)
|
||||
self.__dict__.update(state)
|
||||
|
||||
def __nonzero__(self):
|
||||
# Python 2 compatibility
|
||||
return self.__bool__()
|
||||
|
||||
def __repr__(self):
|
||||
return smart_str("<%s: %s>" % (
|
||||
self.__class__.__name__, self if self.name else "None")
|
||||
)
|
||||
|
||||
|
||||
class LazyImageCacheFile(SimpleLazyObject):
|
||||
def __init__(self, generator_id, *args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from ..utils import get_singleton, get_cache, sanitize_cache_key
|
||||
from ..utils import get_singleton, sanitize_cache_key
|
||||
import warnings
|
||||
from copy import copy
|
||||
from django.core.cache import get_cache
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class CacheFileState(object):
|
||||
|
|
@ -53,7 +53,8 @@ class CachedFileBackend(object):
|
|||
@property
|
||||
def cache(self):
|
||||
if not getattr(self, '_cache', None):
|
||||
self._cache = get_cache()
|
||||
from django.conf import settings
|
||||
self._cache = get_cache(settings.IMAGEKIT_CACHE_BACKEND)
|
||||
return self._cache
|
||||
|
||||
def get_key(self, file):
|
||||
|
|
@ -75,7 +76,7 @@ class CachedFileBackend(object):
|
|||
if state == CacheFileState.DOES_NOT_EXIST:
|
||||
self.cache.set(key, state, self.existence_check_timeout)
|
||||
else:
|
||||
self.cache.set(key, state, settings.IMAGEKIT_CACHE_TIMEOUT)
|
||||
self.cache.set(key, state)
|
||||
|
||||
def __getstate__(self):
|
||||
state = copy(self.__dict__)
|
||||
|
|
@ -110,7 +111,7 @@ class Simple(CachedFileBackend):
|
|||
|
||||
def _exists(self, file):
|
||||
return bool(getattr(file, '_file', None)
|
||||
or (file.name and file.storage.exists(file.name)))
|
||||
or file.storage.exists(file.name))
|
||||
|
||||
|
||||
def _generate_file(backend, file, force=False):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from appconf import AppConf
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
class ImageKitConf(AppConf):
|
||||
|
|
@ -14,24 +13,30 @@ class ImageKitConf(AppConf):
|
|||
|
||||
CACHE_BACKEND = None
|
||||
CACHE_PREFIX = 'imagekit:'
|
||||
CACHE_TIMEOUT = None
|
||||
USE_MEMCACHED_SAFE_CACHE_KEY = True
|
||||
|
||||
def configure_cache_backend(self, value):
|
||||
if value is None:
|
||||
from django.core.cache import DEFAULT_CACHE_ALIAS
|
||||
return DEFAULT_CACHE_ALIAS
|
||||
try:
|
||||
from django.core.cache.backends.dummy import DummyCache
|
||||
except ImportError:
|
||||
dummy_cache = 'dummy://'
|
||||
else:
|
||||
dummy_cache = 'django.core.cache.backends.dummy.DummyCache'
|
||||
|
||||
if value not in settings.CACHES:
|
||||
raise ImproperlyConfigured("{0} is not present in settings.CACHES".format(value))
|
||||
# DEFAULT_CACHE_ALIAS doesn't exist in Django<=1.2
|
||||
try:
|
||||
from django.core.cache import DEFAULT_CACHE_ALIAS as default_cache_alias
|
||||
except ImportError:
|
||||
default_cache_alias = 'default'
|
||||
|
||||
return value
|
||||
if settings.DEBUG:
|
||||
value = dummy_cache
|
||||
elif default_cache_alias in getattr(settings, 'CACHES', {}):
|
||||
value = default_cache_alias
|
||||
else:
|
||||
value = getattr(settings, 'CACHE_BACKEND', None) or dummy_cache
|
||||
|
||||
def configure_cache_timeout(self, value):
|
||||
if value is None and settings.DEBUG:
|
||||
# If value is not configured and is DEBUG set it to 5 minutes
|
||||
return 300
|
||||
# Otherwise leave it as is. If it is None then valies will never expire
|
||||
return value
|
||||
|
||||
def configure_default_file_storage(self, value):
|
||||
|
|
|
|||
|
|
@ -49,25 +49,14 @@ class BaseIKFile(File):
|
|||
|
||||
def _get_size(self):
|
||||
self._require_file()
|
||||
if not getattr(self, '_committed', False):
|
||||
if not self._committed:
|
||||
return self.file.size
|
||||
return self.storage.size(self.name)
|
||||
size = property(_get_size)
|
||||
|
||||
def open(self, mode='rb'):
|
||||
self._require_file()
|
||||
try:
|
||||
self.file.open(mode)
|
||||
except ValueError:
|
||||
# if the underlaying file can't be reopened
|
||||
# then we will use the storage to try to open it again
|
||||
if self.file.closed:
|
||||
# clear cached file instance
|
||||
del self.file
|
||||
# Because file is a property we can acces it after
|
||||
# we deleted it
|
||||
return self.file.open(mode)
|
||||
raise
|
||||
self.file.open(mode)
|
||||
|
||||
def _get_closed(self):
|
||||
file = getattr(self, '_file', None)
|
||||
|
|
|
|||
|
|
@ -24,10 +24,6 @@ class ProcessedImageField(ImageField, SpecHost):
|
|||
|
||||
if data and data != initial:
|
||||
spec = self.get_spec(source=data)
|
||||
f = generate(spec)
|
||||
# Name is required in Django 1.4. When we drop support for it
|
||||
# then we can dirrectly return the result from `generate(spec)`
|
||||
f.name = data.name
|
||||
return f
|
||||
data = generate(spec)
|
||||
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -14,15 +14,11 @@ match both. Subsegments are always matched, so "a" will match "a" as
|
|||
well as "a:b" and "a:b:c".""")
|
||||
args = '[generator_ids]'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('generator_id', nargs='*', help='<app_name>:<model>:<field> for model specs')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
generators = generator_registry.get_ids()
|
||||
|
||||
generator_ids = options['generator_id'] if 'generator_id' in options else args
|
||||
if generator_ids:
|
||||
patterns = self.compile_patterns(generator_ids)
|
||||
if args:
|
||||
patterns = self.compile_patterns(args)
|
||||
generators = (id for id in generators if any(p.match(id) for p in patterns))
|
||||
|
||||
for generator_id in generators:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models.signals import class_prepared
|
||||
from .files import ProcessedImageFieldFile
|
||||
|
|
@ -93,7 +92,7 @@ class ProcessedImageField(models.ImageField, SpecHostField):
|
|||
|
||||
def __init__(self, processors=None, format=None, options=None,
|
||||
verbose_name=None, name=None, width_field=None, height_field=None,
|
||||
autoconvert=None, spec=None, spec_id=None, **kwargs):
|
||||
autoconvert=True, spec=None, spec_id=None, **kwargs):
|
||||
"""
|
||||
The ProcessedImageField constructor accepts all of the arguments that
|
||||
the :class:`django.db.models.ImageField` constructor accepts, as well
|
||||
|
|
@ -101,10 +100,6 @@ class ProcessedImageField(models.ImageField, SpecHostField):
|
|||
:class:`imagekit.models.ImageSpecField`.
|
||||
|
||||
"""
|
||||
# if spec is not provided then autoconvert will be True by default
|
||||
if spec is None and autoconvert is None:
|
||||
autoconvert = True
|
||||
|
||||
SpecHost.__init__(self, processors=processors, format=format,
|
||||
options=options, autoconvert=autoconvert, spec=spec,
|
||||
spec_id=spec_id)
|
||||
|
|
@ -116,11 +111,9 @@ class ProcessedImageField(models.ImageField, SpecHostField):
|
|||
return super(ProcessedImageField, self).contribute_to_class(cls, name)
|
||||
|
||||
|
||||
# If the project does not use south, then we will not try to add introspection
|
||||
if 'south' in settings.INSTALLED_APPS:
|
||||
try:
|
||||
from south.modelsinspector import add_introspection_rules
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
add_introspection_rules([], [r'^imagekit\.models\.fields\.ProcessedImageField$'])
|
||||
try:
|
||||
from south.modelsinspector import add_introspection_rules
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
add_introspection_rules([], [r'^imagekit\.models\.fields\.ProcessedImageField$'])
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
__title__ = 'django-imagekit'
|
||||
__author__ = 'Matthew Tretter, Venelin Stoykov, Eric Eldredge, Bryan Veloso, Greg Newman, Chris Drackett, Justin Driscoll'
|
||||
__version__ = '4.0.2'
|
||||
__author__ = 'Matthew Tretter, Eric Eldredge, Bryan Veloso, Greg Newman, Chris Drackett, Justin Driscoll'
|
||||
__version__ = '3.2.7'
|
||||
__license__ = 'BSD'
|
||||
__all__ = ['__title__', '__author__', '__version__', '__license__']
|
||||
|
|
|
|||
|
|
@ -145,24 +145,18 @@ class ImageSpec(BaseImageSpec):
|
|||
|
||||
# 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.)
|
||||
|
||||
closed = self.source.closed
|
||||
if closed:
|
||||
# Django file object should know how to reopen itself if it was closed
|
||||
# https://code.djangoproject.com/ticket/13750
|
||||
self.source.open()
|
||||
|
||||
try:
|
||||
img = open_image(self.source)
|
||||
new_image = process_image(img,
|
||||
processors=self.processors,
|
||||
format=self.format,
|
||||
autoconvert=self.autoconvert,
|
||||
options=self.options)
|
||||
finally:
|
||||
if closed:
|
||||
# We need to close the file if it was opened by us
|
||||
self.source.close()
|
||||
except ValueError:
|
||||
|
||||
# Re-open the file -- https://code.djangoproject.com/ticket/13750
|
||||
self.source.open()
|
||||
img = open_image(self.source)
|
||||
|
||||
new_image = process_image(img, processors=self.processors,
|
||||
format=self.format, autoconvert=self.autoconvert,
|
||||
options=self.options)
|
||||
self.source.close()
|
||||
return new_image
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -87,15 +87,12 @@ class ModelSignalRouter(object):
|
|||
if isinstance(instance, src.model_class))
|
||||
|
||||
@ik_model_receiver
|
||||
def post_save_receiver(self, sender, instance=None, created=False, update_fields=None, raw=False, **kwargs):
|
||||
def post_save_receiver(self, sender, instance=None, created=False, raw=False, **kwargs):
|
||||
if not raw:
|
||||
self.init_instance(instance)
|
||||
old_hashes = instance._ik.get('source_hashes', {}).copy()
|
||||
new_hashes = self.update_source_hashes(instance)
|
||||
for attname in self.get_source_fields(instance):
|
||||
if update_fields and attname not in update_fields:
|
||||
continue
|
||||
|
||||
file = getattr(instance, attname)
|
||||
if file and old_hashes.get(attname) != new_hashes[attname]:
|
||||
self.dispatch_signal(source_saved, file, sender, instance,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from django import template
|
|||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from ..compat import parse_bits
|
||||
from .compat import parse_bits
|
||||
from ..cachefiles import ImageCacheFile
|
||||
from ..registry import generator_registry
|
||||
from ..lib import force_text
|
||||
|
|
|
|||
|
|
@ -72,26 +72,6 @@ def autodiscover():
|
|||
if _autodiscovered:
|
||||
return
|
||||
|
||||
try:
|
||||
from django.utils.module_loading import autodiscover_modules
|
||||
except ImportError:
|
||||
# Django<1.7
|
||||
_autodiscover_modules_fallback()
|
||||
else:
|
||||
autodiscover_modules('imagegenerators')
|
||||
_autodiscovered = True
|
||||
|
||||
|
||||
def _autodiscover_modules_fallback():
|
||||
"""
|
||||
Auto-discover INSTALLED_APPS imagegenerators.py modules and fail silently
|
||||
when not present. This forces an import on them to register any admin bits
|
||||
they may want.
|
||||
|
||||
Copied from django.contrib.admin
|
||||
|
||||
Used for Django versions < 1.7
|
||||
"""
|
||||
from django.conf import settings
|
||||
try:
|
||||
from importlib import import_module
|
||||
|
|
@ -99,6 +79,8 @@ def _autodiscover_modules_fallback():
|
|||
from django.utils.importlib import import_module
|
||||
from django.utils.module_loading import module_has_submodule
|
||||
|
||||
_autodiscovered = True
|
||||
|
||||
for app in settings.INSTALLED_APPS:
|
||||
# As of Django 1.7, settings.INSTALLED_APPS may contain classes instead of modules, hence the try/except
|
||||
# See here: https://docs.djangoproject.com/en/dev/releases/1.7/#introspecting-applications
|
||||
|
|
@ -150,13 +132,16 @@ def generate(generator):
|
|||
|
||||
"""
|
||||
content = generator.generate()
|
||||
f = File(content)
|
||||
# The size of the File must be known or Django will try to open a file
|
||||
# without a name and raise an Exception.
|
||||
f.size = len(content.read())
|
||||
# After getting the size reset the file pointer for future reads.
|
||||
content.seek(0)
|
||||
return f
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
def call_strategy_method(file, method_name):
|
||||
|
|
@ -166,17 +151,6 @@ def call_strategy_method(file, method_name):
|
|||
fn(file)
|
||||
|
||||
|
||||
def get_cache():
|
||||
try:
|
||||
from django.core.cache import caches
|
||||
except ImportError:
|
||||
# Django < 1.7
|
||||
from django.core.cache import get_cache
|
||||
return get_cache(settings.IMAGEKIT_CACHE_BACKEND)
|
||||
|
||||
return caches[settings.IMAGEKIT_CACHE_BACKEND]
|
||||
|
||||
|
||||
def sanitize_cache_key(key):
|
||||
if settings.IMAGEKIT_USE_MEMCACHED_SAFE_CACHE_KEY:
|
||||
# Memcached keys can't contain whitespace or control characters.
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
[bdist_wheel]
|
||||
universal = 1
|
||||
27
setup.py
27
setup.py
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#/usr/bin/env python
|
||||
import codecs
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
|
@ -7,27 +7,25 @@ import sys
|
|||
|
||||
# Workaround for multiprocessing/nose issue. See http://bugs.python.org/msg170215
|
||||
try:
|
||||
import multiprocessing # NOQA
|
||||
import multiprocessing
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
if 'publish' in sys.argv:
|
||||
os.system('python setup.py sdist bdist_wheel upload')
|
||||
os.system('python setup.py sdist upload')
|
||||
sys.exit()
|
||||
|
||||
|
||||
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 = {}
|
||||
exec_file(os.path.join(os.path.dirname(__file__),
|
||||
'imagekit', 'pkgmeta.py'), pkgmeta)
|
||||
'imagekit', 'pkgmeta.py'), pkgmeta)
|
||||
|
||||
|
||||
setup(
|
||||
|
|
@ -41,16 +39,16 @@ setup(
|
|||
maintainer_email='bryan@revyver.com',
|
||||
license='BSD',
|
||||
url='http://github.com/matthewwithanm/django-imagekit/',
|
||||
packages=find_packages(exclude=['*.tests', '*.tests.*', 'tests.*', 'tests']),
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
tests_require=[
|
||||
'beautifulsoup4>=4.4.0',
|
||||
'nose>=1.3.6',
|
||||
'nose-progressive>=1.5.1',
|
||||
'django-nose>=1.4',
|
||||
'Pillow',
|
||||
'mock>=1.0.1',
|
||||
'beautifulsoup4==4.1.3',
|
||||
'nose>=1.3.6,<1.4',
|
||||
'nose-progressive==1.5.1',
|
||||
'django-nose>=1.2,<=1.4',
|
||||
'Pillow<3.0',
|
||||
'mock==1.0.1',
|
||||
],
|
||||
test_suite='testrunner.run_tests',
|
||||
install_requires=[
|
||||
|
|
@ -70,11 +68,12 @@ setup(
|
|||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Topic :: Utilities'
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,4 @@ def run_tests():
|
|||
cls = get_runner(settings)
|
||||
runner = cls()
|
||||
failures = runner.run_tests(['tests'])
|
||||
# Clean autogenerated junk before exit
|
||||
from tests.utils import clear_imagekit_test_files
|
||||
clear_imagekit_test_files()
|
||||
sys.exit(failures)
|
||||
|
|
|
|||
BIN
tests/assets/Lenna.png
Normal file
BIN
tests/assets/Lenna.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 463 KiB |
BIN
tests/assets/lenna-800x600-white-border.jpg
Normal file
BIN
tests/assets/lenna-800x600-white-border.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 198 KiB |
BIN
tests/assets/lenna-800x600.jpg
Normal file
BIN
tests/assets/lenna-800x600.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
BIN
tests/media/lenna.png
Normal file
BIN
tests/media/lenna.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 463 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 117 KiB |
|
|
@ -1,17 +1,10 @@
|
|||
from django.db import models
|
||||
|
||||
from imagekit import ImageSpec
|
||||
from imagekit.models import ProcessedImageField
|
||||
from imagekit.models import ImageSpecField
|
||||
from imagekit.processors import Adjust, ResizeToFill, SmartCrop
|
||||
|
||||
|
||||
class Thumbnail(ImageSpec):
|
||||
processors = [ResizeToFill(100, 60)]
|
||||
format = 'JPEG'
|
||||
options = {'quality': 60}
|
||||
|
||||
|
||||
class ImageModel(models.Model):
|
||||
image = models.ImageField(upload_to='b')
|
||||
|
||||
|
|
@ -34,10 +27,6 @@ class ProcessedImageFieldModel(models.Model):
|
|||
options={'quality': 90}, upload_to='p')
|
||||
|
||||
|
||||
class ProcessedImageFieldWithSpecModel(models.Model):
|
||||
processed = ProcessedImageField(spec=Thumbnail, upload_to='p')
|
||||
|
||||
|
||||
class CountingCacheFileStrategy(object):
|
||||
def __init__(self):
|
||||
self.on_existence_required_count = 0
|
||||
|
|
|
|||
|
|
@ -47,23 +47,6 @@ NOSE_ARGS = [
|
|||
if os.getenv('TERM'):
|
||||
NOSE_ARGS.append('--with-progressive')
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
CACHE_BACKEND = 'locmem://'
|
||||
|
||||
# Django >= 1.8
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.static',
|
||||
'django.template.context_processors.tz',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
from unittest import mock
|
||||
from django.conf import settings
|
||||
from hashlib import md5
|
||||
from imagekit.cachefiles import ImageCacheFile, LazyImageCacheFile
|
||||
|
|
@ -49,31 +48,6 @@ def test_no_source_error():
|
|||
file.generate()
|
||||
|
||||
|
||||
def test_repr_does_not_send_existence_required():
|
||||
"""
|
||||
Ensure that `__repr__` method does not send `existance_required` signal
|
||||
|
||||
Cachefile strategy may be configured to generate file on
|
||||
`existance_required`.
|
||||
To generate images, backend passes `ImageCacheFile` instance to worker.
|
||||
Both celery and RQ calls `__repr__` method for each argument to enque call.
|
||||
And if `__repr__` of object will send this signal, we will get endless
|
||||
recursion
|
||||
|
||||
"""
|
||||
with mock.patch('imagekit.cachefiles.existence_required') as signal:
|
||||
# import here to apply mock
|
||||
from imagekit.cachefiles import ImageCacheFile
|
||||
|
||||
spec = TestSpec(source=get_unique_image_file())
|
||||
file = ImageCacheFile(
|
||||
spec,
|
||||
cachefile_backend=DummyAsyncCacheFileBackend()
|
||||
)
|
||||
file.__repr__()
|
||||
eq_(signal.send.called, False)
|
||||
|
||||
|
||||
def test_memcached_cache_key():
|
||||
"""
|
||||
Ensure the default cachefile backend is sanitizing its cache key for
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
from nose.tools import assert_false, assert_true
|
||||
|
||||
from .models import Thumbnail
|
||||
from .utils import create_photo
|
||||
|
||||
|
||||
def test_do_not_leak_open_files():
|
||||
instance = create_photo('leak-test.jpg')
|
||||
source_file = instance.original_image
|
||||
# Ensure the FieldFile is closed before generation
|
||||
source_file.close()
|
||||
image_generator = Thumbnail(source=source_file)
|
||||
image_generator.generate()
|
||||
assert_true(source_file.closed)
|
||||
|
||||
|
||||
def test_do_not_close_open_files_after_generate():
|
||||
instance = create_photo('do-not-close-test.jpg')
|
||||
source_file = instance.original_image
|
||||
# Ensure the FieldFile is opened before generation
|
||||
source_file.open()
|
||||
image_generator = Thumbnail(source=source_file)
|
||||
image_generator.generate()
|
||||
assert_false(source_file.closed)
|
||||
source_file.close()
|
||||
|
|
@ -5,9 +5,7 @@ from imagekit import forms as ikforms
|
|||
from imagekit.processors import SmartCrop
|
||||
from nose.tools import eq_
|
||||
from . import imagegenerators # noqa
|
||||
from .models import (ProcessedImageFieldModel,
|
||||
ProcessedImageFieldWithSpecModel,
|
||||
ImageModel)
|
||||
from .models import ProcessedImageFieldModel, ImageModel
|
||||
from .utils import get_image_file
|
||||
|
||||
|
||||
|
|
@ -21,16 +19,6 @@ def test_model_processedimagefield():
|
|||
eq_(instance.processed.height, 50)
|
||||
|
||||
|
||||
def test_model_processedimagefield_with_spec():
|
||||
instance = ProcessedImageFieldWithSpecModel()
|
||||
file = File(get_image_file())
|
||||
instance.processed.save('whatever.jpeg', file)
|
||||
instance.save()
|
||||
|
||||
eq_(instance.processed.width, 100)
|
||||
eq_(instance.processed.height, 60)
|
||||
|
||||
|
||||
def test_form_processedimagefield():
|
||||
class TestForm(forms.ModelForm):
|
||||
image = ikforms.ProcessedImageField(spec_id='tests:testform_image',
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
from django.template import TemplateSyntaxError
|
||||
from nose.tools import eq_, assert_false, raises, assert_not_equal
|
||||
from . import imagegenerators # noqa
|
||||
from .utils import render_tag, get_html_attrs, clear_imagekit_cache
|
||||
from .utils import render_tag, get_html_attrs
|
||||
|
||||
|
||||
def test_img_tag():
|
||||
ttag = r"""{% generateimage 'testspec' source=img %}"""
|
||||
clear_imagekit_cache()
|
||||
attrs = get_html_attrs(ttag)
|
||||
expected_attrs = set(['src', 'width', 'height'])
|
||||
eq_(set(attrs.keys()), expected_attrs)
|
||||
|
|
@ -16,7 +15,6 @@ def test_img_tag():
|
|||
|
||||
def test_img_tag_attrs():
|
||||
ttag = r"""{% generateimage 'testspec' source=img -- alt="Hello" %}"""
|
||||
clear_imagekit_cache()
|
||||
attrs = get_html_attrs(ttag)
|
||||
eq_(attrs.get('alt'), 'Hello')
|
||||
|
||||
|
|
@ -30,7 +28,7 @@ def test_dangling_html_attrs_delimiter():
|
|||
@raises(TemplateSyntaxError)
|
||||
def test_html_attrs_assignment():
|
||||
"""
|
||||
You can either use generateimage as an assignment tag or specify html attrs,
|
||||
You can either use generateimage as an assigment tag or specify html attrs,
|
||||
but not both.
|
||||
|
||||
"""
|
||||
|
|
@ -44,13 +42,11 @@ def test_single_dimension_attr():
|
|||
|
||||
"""
|
||||
ttag = r"""{% generateimage 'testspec' source=img -- width="50" %}"""
|
||||
clear_imagekit_cache()
|
||||
attrs = get_html_attrs(ttag)
|
||||
assert_false('height' in attrs)
|
||||
|
||||
|
||||
def test_assignment_tag():
|
||||
ttag = r"""{% generateimage 'testspec' source=img as th %}{{ th.url }}{{ th.height }}{{ th.width }}"""
|
||||
clear_imagekit_cache()
|
||||
ttag = r"""{% generateimage 'testspec' source=img as th %}{{ th.url }}"""
|
||||
html = render_tag(ttag)
|
||||
assert_not_equal(html.strip(), '')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from nose.tools import assert_false
|
||||
from unittest.mock import Mock, PropertyMock, patch
|
||||
from mock import Mock, PropertyMock, patch
|
||||
from .models import Photo
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from nose.tools import assert_true, assert_false
|
||||
from imagekit.cachefiles import ImageCacheFile
|
||||
from unittest.mock import Mock
|
||||
from mock import Mock
|
||||
from .utils import create_image
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from imagekit.cachefiles.backends import Simple as SimpleCFBackend
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ deserialized. This is important when using IK with Celery.
|
|||
|
||||
from imagekit.cachefiles import ImageCacheFile
|
||||
from .imagegenerators import TestSpec
|
||||
from .utils import create_photo, pickleback, get_unique_image_file, clear_imagekit_cache
|
||||
from .utils import create_photo, pickleback, get_unique_image_file
|
||||
|
||||
|
||||
def test_imagespecfield():
|
||||
clear_imagekit_cache()
|
||||
instance = create_photo('pickletest2.jpg')
|
||||
thumbnail = pickleback(instance.thumbnail)
|
||||
thumbnail.generate()
|
||||
|
|
@ -23,21 +22,16 @@ def test_circular_ref():
|
|||
This corresponds to #234
|
||||
|
||||
"""
|
||||
clear_imagekit_cache()
|
||||
instance = create_photo('pickletest3.jpg')
|
||||
instance.thumbnail # Cause thumbnail to be added to instance's __dict__
|
||||
pickleback(instance)
|
||||
|
||||
|
||||
|
||||
def test_cachefiles():
|
||||
clear_imagekit_cache()
|
||||
spec = TestSpec(source=get_unique_image_file())
|
||||
file = ImageCacheFile(spec)
|
||||
file.url
|
||||
# remove link to file from spec source generator
|
||||
# test __getstate__ of ImageCacheFile
|
||||
file.generator.source = None
|
||||
restored_file = pickleback(file)
|
||||
assert file is not restored_file
|
||||
# Assertion for #437 and #451
|
||||
assert file.storage is restored_file.storage
|
||||
pickleback(file)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
from django.template import TemplateSyntaxError
|
||||
from nose.tools import eq_, raises, assert_not_equal
|
||||
from . import imagegenerators # noqa
|
||||
from .utils import render_tag, get_html_attrs, clear_imagekit_cache
|
||||
from .utils import render_tag, get_html_attrs
|
||||
|
||||
|
||||
def test_img_tag():
|
||||
ttag = r"""{% thumbnail '100x100' img %}"""
|
||||
clear_imagekit_cache()
|
||||
attrs = get_html_attrs(ttag)
|
||||
expected_attrs = set(['src', 'width', 'height'])
|
||||
eq_(set(attrs.keys()), expected_attrs)
|
||||
|
|
@ -16,7 +15,6 @@ def test_img_tag():
|
|||
|
||||
def test_img_tag_attrs():
|
||||
ttag = r"""{% thumbnail '100x100' img -- alt="Hello" %}"""
|
||||
clear_imagekit_cache()
|
||||
attrs = get_html_attrs(ttag)
|
||||
eq_(attrs.get('alt'), 'Hello')
|
||||
|
||||
|
|
@ -42,7 +40,7 @@ def test_too_many_args():
|
|||
@raises(TemplateSyntaxError)
|
||||
def test_html_attrs_assignment():
|
||||
"""
|
||||
You can either use thumbnail as an assignment tag or specify html attrs,
|
||||
You can either use thumbnail as an assigment tag or specify html attrs,
|
||||
but not both.
|
||||
|
||||
"""
|
||||
|
|
@ -52,20 +50,17 @@ def test_html_attrs_assignment():
|
|||
|
||||
def test_assignment_tag():
|
||||
ttag = r"""{% thumbnail '100x100' img as th %}{{ th.url }}"""
|
||||
clear_imagekit_cache()
|
||||
html = render_tag(ttag)
|
||||
assert_not_equal(html, '')
|
||||
|
||||
|
||||
def test_single_dimension():
|
||||
ttag = r"""{% thumbnail '100x' img as th %}{{ th.width }}"""
|
||||
clear_imagekit_cache()
|
||||
html = render_tag(ttag)
|
||||
eq_(html, '100')
|
||||
|
||||
|
||||
def test_alternate_generator():
|
||||
ttag = r"""{% thumbnail '1pxsq' '100x' img as th %}{{ th.width }}"""
|
||||
clear_imagekit_cache()
|
||||
html = render_tag(ttag)
|
||||
eq_(html, '1')
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
from bs4 import BeautifulSoup
|
||||
import os
|
||||
import shutil
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
from django.template import Context, Template
|
||||
from imagekit.cachefiles.backends import Simple, CacheFileState
|
||||
from imagekit.conf import settings
|
||||
from imagekit.lib import Image, StringIO
|
||||
from imagekit.utils import get_cache
|
||||
from nose.tools import assert_true, assert_false
|
||||
import pickle
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
|
@ -19,10 +17,9 @@ def get_image_file():
|
|||
|
||||
http://en.wikipedia.org/wiki/Lenna
|
||||
http://sipi.usc.edu/database/database.php?volume=misc&image=12
|
||||
https://lintian.debian.org/tags/license-problem-non-free-img-lenna.html
|
||||
https://github.com/libav/libav/commit/8895bf7b78650c0c21c88cec0484e138ec511a4b
|
||||
|
||||
"""
|
||||
path = os.path.join(settings.MEDIA_ROOT, 'reference.png')
|
||||
path = os.path.join(settings.MEDIA_ROOT, 'lenna.png')
|
||||
return open(path, 'r+b')
|
||||
|
||||
|
||||
|
|
@ -64,7 +61,7 @@ def render_tag(ttag):
|
|||
|
||||
|
||||
def get_html_attrs(ttag):
|
||||
return BeautifulSoup(render_tag(ttag), features="html.parser").img.attrs
|
||||
return BeautifulSoup(render_tag(ttag)).img.attrs
|
||||
|
||||
|
||||
def assert_file_is_falsy(file):
|
||||
|
|
@ -84,23 +81,3 @@ class DummyAsyncCacheFileBackend(Simple):
|
|||
|
||||
def generate(self, file, force=False):
|
||||
pass
|
||||
|
||||
|
||||
def clear_imagekit_cache():
|
||||
cache = get_cache()
|
||||
cache.clear()
|
||||
# Clear IMAGEKIT_CACHEFILE_DIR
|
||||
cache_dir = os.path.join(settings.MEDIA_ROOT, settings.IMAGEKIT_CACHEFILE_DIR)
|
||||
if os.path.exists(cache_dir):
|
||||
shutil.rmtree(cache_dir)
|
||||
|
||||
|
||||
def clear_imagekit_test_files():
|
||||
clear_imagekit_cache()
|
||||
for fname in os.listdir(settings.MEDIA_ROOT):
|
||||
if fname != 'reference.png':
|
||||
path = os.path.join(settings.MEDIA_ROOT, fname)
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
os.remove(path)
|
||||
|
|
|
|||
136
tox.ini
136
tox.ini
|
|
@ -1,18 +1,132 @@
|
|||
[tox]
|
||||
envlist =
|
||||
py38-django{master,30,22,21,20,111},
|
||||
py37-django{master,30,22,21,20,111},
|
||||
py36-django{master,30,22,21,20,111},
|
||||
py35-django{21,20,111},
|
||||
py34-django18, py34-django17, py34-django16,
|
||||
py33-django18, py33-django17, py33-django16, py33-django15,
|
||||
py32-django18, py32-django17, py32-django16, py32-django15,
|
||||
py27-django18, py27-django17, py27-django16, py27-django15, py27-django14, py27-django13, py27-django12,
|
||||
py26-django15, py26-django14, py26-django13, py26-django12
|
||||
|
||||
[testenv]
|
||||
commands = python setup.py test
|
||||
|
||||
[testenv:py34-django18]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
djangomaster: git+https://github.com/django/django.git@master#egg=Django
|
||||
django30: Django>=3.0,<3.1
|
||||
django22: Django>=2.2,<3.0
|
||||
django21: Django>=2.1,<2.2
|
||||
django20: Django>=2.0,<2.1
|
||||
django111: Django>=1.11,<2.0
|
||||
django{21,20,111}: django-nose==1.4.5
|
||||
Django>=1.8,<1.9
|
||||
django-nose==1.4
|
||||
|
||||
[testenv:py34-django17]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
Django>=1.7,<1.8
|
||||
django-nose==1.4
|
||||
|
||||
[testenv:py34-django16]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
|
||||
[testenv:py33-django18]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
Django>=1.8,<1.9
|
||||
django-nose==1.4
|
||||
|
||||
[testenv:py33-django17]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
Django>=1.7,<1.8
|
||||
django-nose==1.4
|
||||
|
||||
[testenv:py33-django16]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
|
||||
[testenv:py33-django15]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
|
||||
[testenv:py32-django18]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
Django>=1.8,<1.9
|
||||
django-nose==1.4
|
||||
|
||||
[testenv:py32-django17]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
Django>=1.7,<1.8
|
||||
django-nose==1.4
|
||||
|
||||
[testenv:py32-django16]
|
||||
basepython = python3.2
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
|
||||
[testenv:py32-django15]
|
||||
basepython = python3.2
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
|
||||
[testenv:py27-django18]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
Django>=1.8,<1.9
|
||||
django-nose==1.4
|
||||
|
||||
[testenv:py27-django17]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
Django>=1.7,<1.8
|
||||
django-nose==1.4
|
||||
|
||||
[testenv:py27-django16]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
Django>=1.6,<1.7
|
||||
|
||||
[testenv:py27-django15]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
|
||||
[testenv:py27-django14]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
Django>=1.4,<1.5
|
||||
|
||||
[testenv:py27-django13]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
Django>=1.3,<1.4
|
||||
django-nose==1.2
|
||||
|
||||
[testenv:py27-django12]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
Django>=1.2,<1.3
|
||||
django-nose==1.2
|
||||
|
||||
[testenv:py26-django15]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
Django>=1.5,<1.6
|
||||
|
||||
[testenv:py26-django14]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
Django>=1.4,<1.5
|
||||
|
||||
[testenv:py26-django13]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
Django>=1.3,<1.4
|
||||
django-nose==1.2
|
||||
|
||||
[testenv:py26-django12]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
Django>=1.2,<1.3
|
||||
django-nose==1.2
|
||||
|
|
|
|||
Loading…
Reference in a new issue