Merge branch 'release/4.0'

* release/4.0:
  Bump version to 4.0
  Universal wheels!
  Replaces #301 autodiscover works with AppConfig
  Ignore autogenerated CTags file
  Do not try south modelinspector when not needed
  Make it possible to configure IMAGEKIT_CACHE_TIMEOUT
  Test against Django 1.11
  Close the file only if it has been opened locally
  Cleanup caching configuration
  updated readme.rst with a svg badge
  honor post_save's update_fields and only fire the source_saved signal when needed
  Ignore VSCode workspace config files
  Fixed #350: Error when trying to access width/height after url
  Fixes #382: Tests no longer leave junk
  Fixes #379 Support for Django 1.10
  Ignore .idea from git
  Include the test suite in the sourcetarball but do not install it.
  Make travis happy
  Drop support for older Django and Python versions
  Replace Lenna image in tests with a truly free alternative.
  Move compat module outside of templatetags package
  Fix test requirements for django 1.9 and Python3.5
This commit is contained in:
Venelin Stoykov 2017-02-22 15:54:07 +02:00
commit f724cb0118
31 changed files with 312 additions and 184 deletions

5
.gitignore vendored
View file

@ -4,11 +4,14 @@
*.pyc
.DS_Store
.tox
.idea
.vscode
MANIFEST
build
dist
/tests/media/*
!/tests/media/lenna.png
!/tests/media/reference.png
/venv
/venv3
/.env
/tags

View file

@ -1,46 +1,62 @@
language: python
python: "2.7"
sudo: false
env:
- TOX_ENV=py26-django12
- TOX_ENV=py26-django13
- TOX_ENV=py26-django14
- TOX_ENV=py26-django15
- TOX_ENV=py26-django16
- TOX_ENV=py27-django12
- TOX_ENV=py27-django13
- TOX_ENV=py27-django14
- TOX_ENV=py27-django15
- TOX_ENV=py27-django16
- TOX_ENV=py27-django17
- TOX_ENV=py27-django18
- TOX_ENV=py27-django19
- TOX_ENV=py32-django15
- TOX_ENV=py32-django16
- TOX_ENV=py32-django17
- TOX_ENV=py32-django18
- TOX_ENV=py33-django15
- TOX_ENV=py33-django16
- TOX_ENV=py33-django17
- TOX_ENV=py33-django18
- TOX_ENV=py34-django16
- TOX_ENV=py34-django17
- TOX_ENV=py34-django18
- TOX_ENV=py34-django19
- TOX_ENV=py35-django19
matrix:
# Python 3.5 not yet available on travis, watch this to see when it is.
fast_finish: true
allow_failures:
- env: TOX_ENV=py35-django19
install:
- pip install tox --use-mirrors
- pip install tox
script:
- tox -e $TOX_ENV
- tox
matrix:
fast_finish: true
include:
- env: TOXENV=py27-django14
python: 2.7
- env: TOXENV=py27-django15
python: 2.7
- env: TOXENV=py27-django16
python: 2.7
- env: TOXENV=py27-django17
python: 2.7
- env: TOXENV=py27-django18
python: 2.7
- env: TOXENV=py27-django19
python: 2.7
- env: TOXENV=py27-django110
python: 2.7
- env: TOXENV=py27-django111
python: 2.7
- env: TOXENV=py33-django15
python: 3.3
- env: TOXENV=py33-django16
python: 3.3
- env: TOXENV=py33-django17
python: 3.3
- env: TOXENV=py33-django18
python: 3.3
- env: TOXENV=py34-django16
python: 3.4
- env: TOXENV=py34-django17
python: 3.4
- env: TOXENV=py34-django18
python: 3.4
- env: TOXENV=py34-django19
python: 3.4
- env: TOXENV=py34-django110
python: 3.4
- env: TOXENV=py34-django111
python: 3.4
- env: TOXENV=py35-django18
python: 3.5
- env: TOXENV=py35-django19
python: 3.5
- env: TOXENV=py35-django110
python: 3.5
- env: TOXENV=py35-django111
python: 3.5
notifications:
irc: "irc.freenode.org#imagekit"

View file

@ -1,6 +1,18 @@
include AUTHORS
include LICENSE
include README.rst
recursive-include docs *
recursive-include imagekit/templates *
prune tests
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

View file

@ -1,6 +1,6 @@
|Build Status|_
.. |Build Status| image:: https://travis-ci.org/matthewwithanm/django-imagekit.png?branch=develop
.. |Build Status| image:: https://travis-ci.org/matthewwithanm/django-imagekit.svg?branch=develop
.. _Build Status: https://travis-ci.org/matthewwithanm/django-imagekit
ImageKit is a Django app for processing images. Need a thumbnail? A

View file

@ -100,13 +100,21 @@ ImageKit. Each has its own pros and cons.
Caching Data About Generated Files
----------------------------------
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.
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``.
Pre-Generating Images

View file

@ -55,6 +55,15 @@ Settings
.. _`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.
.. attribute:: IMAGEKIT_CACHE_PREFIX
:default: ``'imagekit:'``

View file

@ -2,6 +2,7 @@ from ..utils import get_singleton, get_cache, sanitize_cache_key
import warnings
from copy import copy
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
class CacheFileState(object):
@ -52,8 +53,7 @@ class CachedFileBackend(object):
@property
def cache(self):
if not getattr(self, '_cache', None):
from django.conf import settings
self._cache = get_cache(settings.IMAGEKIT_CACHE_BACKEND)
self._cache = get_cache()
return self._cache
def get_key(self, file):
@ -75,7 +75,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)
self.cache.set(key, state, settings.IMAGEKIT_CACHE_TIMEOUT)
def __getstate__(self):
state = copy(self.__dict__)

View file

@ -1,5 +1,6 @@
from appconf import AppConf
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
class ImageKitConf(AppConf):
@ -13,32 +14,24 @@ 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:
# 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'
from django.core.cache import DEFAULT_CACHE_ALIAS
return DEFAULT_CACHE_ALIAS
caches = getattr(settings, 'CACHES', None)
if caches is None:
# Support Django<=1.2 there is no default `CACHES` setting
try:
from django.core.cache.backends.dummy import DummyCache
except ImportError:
dummy_cache = 'dummy://'
else:
dummy_cache = 'django.core.cache.backends.dummy.DummyCache'
return dummy_cache
if value not in settings.CACHES:
raise ImproperlyConfigured("{0} is not present in settings.CACHES".format(value))
if default_cache_alias in caches:
value = default_cache_alias
else:
raise ValueError("The default cache alias '%s' is not available in CACHES" % default_cache_alias)
return value
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):

View file

@ -56,7 +56,18 @@ class BaseIKFile(File):
def open(self, mode='rb'):
self._require_file()
self.file.open(mode)
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
def _get_closed(self):
file = getattr(self, '_file', None)

View file

@ -24,6 +24,10 @@ class ProcessedImageField(ImageField, SpecHost):
if data and data != initial:
spec = self.get_spec(source=data)
data = generate(spec)
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
return data

View file

@ -1,5 +1,6 @@
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
@ -111,9 +112,11 @@ class ProcessedImageField(models.ImageField, SpecHostField):
return super(ProcessedImageField, self).contribute_to_class(cls, name)
try:
from south.modelsinspector import add_introspection_rules
except ImportError:
pass
else:
add_introspection_rules([], [r'^imagekit\.models\.fields\.ProcessedImageField$'])
# 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$'])

View file

@ -1,5 +1,5 @@
__title__ = 'django-imagekit'
__author__ = 'Matthew Tretter, Venelin Stoykov, Eric Eldredge, Bryan Veloso, Greg Newman, Chris Drackett, Justin Driscoll'
__version__ = '3.3'
__version__ = '4.0'
__license__ = 'BSD'
__all__ = ['__title__', '__author__', '__version__', '__license__']

View file

@ -143,6 +143,7 @@ class ImageSpec(BaseImageSpec):
raise MissingSource("The spec '%s' has no source file associated"
" with it." % self)
file_opened_locally = False
# 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.)
try:
@ -151,12 +152,14 @@ class ImageSpec(BaseImageSpec):
# Re-open the file -- https://code.djangoproject.com/ticket/13750
self.source.open()
file_opened_locally = True
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()
if file_opened_locally:
self.source.close()
return new_image

View file

@ -87,12 +87,15 @@ class ModelSignalRouter(object):
if isinstance(instance, src.model_class))
@ik_model_receiver
def post_save_receiver(self, sender, instance=None, created=False, raw=False, **kwargs):
def post_save_receiver(self, sender, instance=None, created=False, update_fields=None, 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,

View file

@ -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

View file

@ -69,6 +69,30 @@ def autodiscover():
"""
global _autodiscovered
if _autodiscovered:
return
try:
from django.utils.module_loading import autodiscover_modules
except ImportError:
# Django<1.7
_autodiscover_modules_fallback()
else:
autodiscover_modules('imagegenerators')
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
"""
global _autodiscovered
if _autodiscovered:
return
@ -132,16 +156,13 @@ def generate(generator):
"""
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)
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
def call_strategy_method(file, method_name):
@ -151,14 +172,15 @@ def call_strategy_method(file, method_name):
fn(file)
def get_cache(backend, **kwargs):
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(backend, **kwargs)
return get_cache(settings.IMAGEKIT_CACHE_BACKEND)
return caches[backend]
return caches[settings.IMAGEKIT_CACHE_BACKEND]
def sanitize_cache_key(key):

2
setup.cfg Normal file
View file

@ -0,0 +1,2 @@
[bdist_wheel]
universal = 1

View file

@ -13,7 +13,7 @@ except ImportError:
if 'publish' in sys.argv:
os.system('python setup.py sdist upload')
os.system('python setup.py sdist bdist_wheel upload')
sys.exit()
@ -39,16 +39,16 @@ setup(
maintainer_email='bryan@revyver.com',
license='BSD',
url='http://github.com/matthewwithanm/django-imagekit/',
packages=find_packages(),
packages=find_packages(exclude=['*.tests', '*.tests.*', 'tests.*', 'tests']),
zip_safe=False,
include_package_data=True,
tests_require=[
'beautifulsoup4==4.1.3',
'nose>=1.3.6,<1.4',
'nose-progressive==1.5.1',
'django-nose>=1.2,<1.5',
'Pillow<3.0',
'mock==1.0.1',
'beautifulsoup4>=4.4.0',
'nose>=1.3.6',
'nose-progressive>=1.5.1',
'django-nose>=1.4',
'Pillow',
'mock>=1.0.1',
],
test_suite='testrunner.run_tests',
install_requires=[
@ -68,12 +68,11 @@ 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'
],
)

View file

@ -16,4 +16,7 @@ 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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 KiB

BIN
tests/media/reference.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View file

@ -47,6 +47,23 @@ 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',
],
},
},
]

View file

@ -1,11 +1,12 @@
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
from .utils import render_tag, get_html_attrs, clear_imagekit_cache
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)
@ -15,6 +16,7 @@ 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')
@ -42,11 +44,13 @@ 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 }}"""
ttag = r"""{% generateimage 'testspec' source=img as th %}{{ th.url }}{{ th.height }}{{ th.width }}"""
clear_imagekit_cache()
html = render_tag(ttag)
assert_not_equal(html.strip(), '')

View file

@ -6,10 +6,11 @@ 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
from .utils import create_photo, pickleback, get_unique_image_file, clear_imagekit_cache
def test_imagespecfield():
clear_imagekit_cache()
instance = create_photo('pickletest2.jpg')
thumbnail = pickleback(instance.thumbnail)
thumbnail.generate()
@ -22,12 +23,14 @@ 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

View file

@ -1,11 +1,12 @@
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
from .utils import render_tag, get_html_attrs, clear_imagekit_cache
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)
@ -15,6 +16,7 @@ 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')
@ -50,17 +52,20 @@ 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')

View file

@ -1,10 +1,12 @@
from bs4 import BeautifulSoup
import os
from django.conf import settings
import shutil
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
@ -17,9 +19,10 @@ 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, 'lenna.png')
path = os.path.join(settings.MEDIA_ROOT, 'reference.png')
return open(path, 'r+b')
@ -81,3 +84,23 @@ 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)

121
tox.ini
View file

@ -1,32 +1,60 @@
[tox]
envlist =
py35-django19,
py34-django19, py34-django18, py34-django17, py34-django16,
py35-django111, py35-django110, py35-django19, py35-django18,
py34-django111, py34-django110, py34-django19, py34-django18, py34-django17, py34-django16,
py33-django18, py33-django17, py33-django16, py33-django15,
py32-django18, py32-django17, py32-django16, py32-django15,
py27-django19, py27-django18, py27-django17, py27-django16, py27-django15, py27-django14, py27-django13, py27-django12,
py26-django16, py26-django15, py26-django14, py26-django13, py26-django12
py27-django111, py27-django110, py27-django19, py27-django18, py27-django17, py27-django16, py27-django15, py27-django14,
[testenv]
commands = python setup.py test
[testenv:py35-django111]
basepython = python3.5
deps =
Django>=1.11a1,<1.12
django-nose==1.4.4
[testenv:py35-django110]
basepython = python3.5
deps =
Django>=1.10,<1.11
django-nose==1.4.4
[testenv:py35-django19]
basepython = python3.5
deps =
git+https://github.com/django/django.git@stable/1.9.x#egg=Django
Django>=1.9,<1.10
django-nose==1.4.2
[testenv:py35-django18]
basepython = python3.5
deps =
Django>=1.8,<1.9
django-nose==1.4.2
[testenv:py34-django111]
basepython = python3.4
deps =
Django>=1.11a1,<1.12
django-nose==1.4.4
[testenv:py34-django110]
basepython = python3.4
deps =
Django>=1.10,<1.11
django-nose==1.4.4
[testenv:py34-django19]
basepython = python3.4
deps =
git+https://github.com/django/django.git@stable/1.9.x#egg=Django
Django>=1.9,<1.10
django-nose==1.4.2
[testenv:py34-django18]
basepython = python3.4
deps =
Django>=1.8,<1.9
django-nose==1.4
django-nose==1.4.2
[testenv:py34-django17]
basepython = python3.4
@ -38,12 +66,13 @@ deps =
basepython = python3.4
deps =
Django>=1.6,<1.7
django-nose<=1.4.2
[testenv:py33-django18]
basepython = python3.3
deps =
Django>=1.8,<1.9
django-nose==1.4
django-nose==1.4.2
[testenv:py33-django17]
basepython = python3.3
@ -55,45 +84,37 @@ deps =
basepython = python3.3
deps =
Django>=1.6,<1.7
django-nose<=1.4.2
[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
[testenv:py27-django111]
basepython = python2.7
deps =
Django>=1.7,<1.8
django-nose==1.4
Django>=1.11a1,<1.12
django-nose==1.4.4
[testenv:py32-django16]
basepython = python3.2
[testenv:py27-django110]
basepython = python2.7
deps =
Django>=1.6,<1.7
[testenv:py32-django15]
basepython = python3.2
deps =
Django>=1.5,<1.6
Django>=1.10,<1.11
django-nose==1.4.4
[testenv:py27-django19]
basepython = python2.7
deps =
git+https://github.com/django/django.git@stable/1.9.x#egg=Django
git+https://github.com/django-nose/django-nose@master#egg=django-nose
Django>=1.9,<1.10
django-nose==1.4.2
[testenv:py27-django18]
basepython = python2.7
deps =
Django>=1.8,<1.9
django-nose==1.4
django-nose==1.4.2
[testenv:py27-django17]
basepython = python2.7
@ -105,52 +126,16 @@ deps =
basepython = python2.7
deps =
Django>=1.6,<1.7
django-nose<=1.4.2
[testenv:py27-django15]
basepython = python2.7
deps =
Django>=1.5,<1.6
django-nose==1.4
[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-django16]
basepython = python2.6
deps =
Django>=1.6,<1.7
[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
django-nose==1.4