[Python-modules-commits] [sorl-thumbnail] 01/15: Import sorl-thumbnail_12.3+git20160928.orig.tar.gz

Wolfgang Borgert debacle at moszumanska.debian.org
Wed Oct 5 01:39:41 UTC 2016


This is an automated email from the git hooks/post-receive script.

debacle pushed a commit to branch master
in repository sorl-thumbnail.

commit 3ffdcfe77474be5c4dadbc0c1a48660f0b6de824
Author: W. Martin Borgert <debacle at debian.org>
Date:   Tue Oct 4 01:55:58 2016 +0200

    Import sorl-thumbnail_12.3+git20160928.orig.tar.gz
---
 .travis.yml                                      |  65 +++---------
 AUTHORS                                          |   3 +-
 README.rst                                       |  25 +++--
 docs/contributing.rst                            |  10 +-
 docs/installation.rst                            |   2 +-
 docs/reference/settings.rst                      |   2 +-
 docs/requirements.rst                            |   7 +-
 docs/template.rst                                |   6 +-
 setup.py                                         |   8 +-
 sorl/__init__.py                                 |   3 +-
 sorl/thumbnail/admin/__init__.py                 |   8 +-
 sorl/thumbnail/admin/compat.py                   |  91 -----------------
 sorl/thumbnail/admin/current.py                  |  12 ++-
 sorl/thumbnail/base.py                           |  24 +++--
 sorl/thumbnail/compat.py                         |  46 ---------
 sorl/thumbnail/conf/defaults.py                  |  10 +-
 sorl/thumbnail/engines/convert_engine.py         |   2 +-
 sorl/thumbnail/engines/pil_engine.py             |  10 +-
 sorl/thumbnail/engines/vipsthumbnail_engine.py   | 122 +++++++++++++++++++++++
 sorl/thumbnail/fields.py                         |   7 --
 sorl/thumbnail/helpers.py                        |   7 +-
 sorl/thumbnail/images.py                         |  29 ++++--
 sorl/thumbnail/kvstores/cached_db_kvstore.py     |   5 +-
 sorl/thumbnail/kvstores/dynamodb_kvstore.py      |  38 +++++++
 sorl/thumbnail/migrations/0001_initial.py        |   5 +-
 sorl/thumbnail/templatetags/thumbnail.py         |  19 +++-
 tests/.coveragerc                                |  11 +-
 tests/data/aspect_test.jpg                       | Bin 0 -> 5723 bytes
 tests/data/broken.jpeg                           | Bin 0 -> 480 bytes
 tests/settings/default.py                        |   9 +-
 tests/settings/dynamodb.py                       |   8 ++
 tests/settings/vipsthumbnail.py                  |   4 +
 tests/thumbnail_tests/compat.py                  |  11 +-
 tests/thumbnail_tests/templates/thumbnaild4.html |   5 +
 tests/thumbnail_tests/test_backends.py           |   1 +
 tests/thumbnail_tests/test_engines.py            |  60 +++++++++++
 tests/thumbnail_tests/urls.py                    |  16 +--
 tox.ini                                          |  13 +--
 vagrant.sh                                       |   8 +-
 39 files changed, 417 insertions(+), 295 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 1597464..a7caf81 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,34 +10,6 @@ cache:
     - .tox
 
 env:
-  - TOX_ENV=py27-django14-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
-  - TOX_ENV=py27-django14-imagemagick APT=imagemagick
-  - TOX_ENV=py27-django14-graphicsmagick APT=graphicsmagick
-  - TOX_ENV=py27-django14-redis
-  - TOX_ENV=py27-django14-wand
-  - TOX_ENV=py27-django14-pgmagick APT='libgraphicsmagick++1-dev libboost-python-dev'
-  - TOX_ENV=py27-django14-dbm
-  - TOX_ENV=py27-django15-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
-  - TOX_ENV=py27-django15-imagemagick APT=imagemagick
-  - TOX_ENV=py27-django15-graphicsmagick APT=graphicsmagick
-  - TOX_ENV=py27-django15-redis
-  - TOX_ENV=py27-django15-wand
-  - TOX_ENV=py27-django15-pgmagick APT='libgraphicsmagick++1-dev libboost-python-dev'
-  - TOX_ENV=py27-django15-dbm
-  - TOX_ENV=py27-django16-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
-  - TOX_ENV=py27-django16-imagemagick APT=imagemagick
-  - TOX_ENV=py27-django16-graphicsmagick APT=graphicsmagick
-  - TOX_ENV=py27-django16-redis
-  - TOX_ENV=py27-django16-wand
-  - TOX_ENV=py27-django16-pgmagick APT='libgraphicsmagick++1-dev libboost-python-dev'
-  - TOX_ENV=py27-django16-dbm
-  - TOX_ENV=py27-django17-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
-  - TOX_ENV=py27-django17-imagemagick APT=imagemagick
-  - TOX_ENV=py27-django17-graphicsmagick APT=graphicsmagick
-  - TOX_ENV=py27-django17-redis
-  - TOX_ENV=py27-django17-wand
-  - TOX_ENV=py27-django17-pgmagick APT='libgraphicsmagick++1-dev libboost-python-dev'
-  - TOX_ENV=py27-django17-dbm
   - TOX_ENV=py27-django18-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
   - TOX_ENV=py27-django18-imagemagick APT=imagemagick
   - TOX_ENV=py27-django18-graphicsmagick APT=graphicsmagick
@@ -45,30 +17,24 @@ env:
   - TOX_ENV=py27-django18-wand
   - TOX_ENV=py27-django18-pgmagick APT='libgraphicsmagick++1-dev libboost-python-dev'
   - TOX_ENV=py27-django18-dbm
-  - TOX_ENV=py34-django15-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
-  - TOX_ENV=py34-django15-imagemagick APT=imagemagick
-  - TOX_ENV=py34-django15-graphicsmagick APT=graphicsmagick
-  - TOX_ENV=py34-django15-redis
-  - TOX_ENV=py34-django15-wand
-  - TOX_ENV=py34-django15-dbm
-  - TOX_ENV=py34-django16-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
-  - TOX_ENV=py34-django16-imagemagick APT=imagemagick
-  - TOX_ENV=py34-django16-graphicsmagick APT=graphicsmagick
-  - TOX_ENV=py34-django16-redis
-  - TOX_ENV=py34-django16-wand
-  - TOX_ENV=py34-django16-dbm
-  - TOX_ENV=py34-django17-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
-  - TOX_ENV=py34-django17-imagemagick APT=imagemagick
-  - TOX_ENV=py34-django17-graphicsmagick APT=graphicsmagick
-  - TOX_ENV=py34-django17-redis
-  - TOX_ENV=py34-django17-wand
-  - TOX_ENV=py34-django17-dbm
   - TOX_ENV=py34-django18-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
   - TOX_ENV=py34-django18-imagemagick APT=imagemagick
   - TOX_ENV=py34-django18-graphicsmagick APT=graphicsmagick
   - TOX_ENV=py34-django18-redis
   - TOX_ENV=py34-django18-wand
   - TOX_ENV=py34-django18-dbm
+  - TOX_ENV=py34-django19-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
+  - TOX_ENV=py34-django19-imagemagick APT=imagemagick
+  - TOX_ENV=py34-django19-graphicsmagick APT=graphicsmagick
+  - TOX_ENV=py34-django19-redis
+  - TOX_ENV=py34-django19-wand
+  - TOX_ENV=py34-django19-dbm
+  - TOX_ENV=py34-django110-pil APT='libjpeg62 libjpeg62-dev zlib1g-dev'
+  - TOX_ENV=py34-django110-imagemagick APT=imagemagick
+  - TOX_ENV=py34-django110-graphicsmagick APT=graphicsmagick
+  - TOX_ENV=py34-django110-redis
+  - TOX_ENV=py34-django110-wand
+  - TOX_ENV=py34-django110-dbm
 
 before_install:
   - sudo apt-get update -qq
@@ -77,12 +43,13 @@ before_install:
 after_failure:
   - cat /home/travis/.pip/pip.log
 
-after_success:
-  - coveralls
+# after_success:
+#  - coveralls
 
 install:
   - pip install pip wheel
-  - pip install -q coveralls flake8 tox
+#  - pip install -q coveralls flake8 tox
+  - pip install -q flake8 tox
 
 script:
   - env | sort
diff --git a/AUTHORS b/AUTHORS
index adbc7e8..81da9c0 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -5,4 +5,5 @@ The PRIMARY AUTHORS are (and/or have been):
   * Mikko Hellsing
   * M. César Señoranis
   * Rolf Erik Lekang
-  * Frankie Robertson
\ No newline at end of file
+  * Frankie Robertson
+  * Valentin v. Seggern (Support for AWS Dynamo DB)
diff --git a/README.rst b/README.rst
index 37d5525..b0d3eac 100644
--- a/README.rst
+++ b/README.rst
@@ -5,11 +5,11 @@ Thumbnails for Django.
 Features at a glance
 ====================
 
-- Support for Django 1.4, 1.5, 1.6, 1.7 and 1.8
-- Python 3 support (for Django 1.5, 1.6, 1.7, 1.8)
+- Support for Django 1.8, 1.9, 1.10, following the `Django supported versions policy`_
+- Python 3 support
 - Storage support
-- Pluggable Engine support for `Pillow`_, `ImageMagick`_, `PIL`_, `Wand`_ and `pgmagick`_
-- Pluggable Key Value Store support (cached db, redis)
+- Pluggable Engine support for `Pillow`_, `ImageMagick`_, `PIL`_, `Wand`_, `pgmagick`_, and `vipsthumbnail`_
+- Pluggable Key Value Store support (cached db, redis, and dynamodb by AWS)
 - Pluggable Backend support
 - Admin integration with possibility to delete
 - Dummy generation (placeholders)
@@ -126,21 +126,22 @@ You can use the 'get_thumbnail'::
 
 See more examples in the section `Low level API examples`_ in the Documentation
 
---------------------------
+
 Frequently asked questions
---------------------------
+==========================
 
 Is so slow in Amazon S3 !
 -------------------------
 
-Posible related to the implementation of your Amazon S3 Backend, see the issue `#351`_
+Posible related to the implementation of your Amazon S3 Backend, see the `issue #351`_
 due the storage backend reviews if there is an existing thumbnail when tries to
 generate the thumbnail that makes an extensive use of the S3 API
 
-A fast workaround if you are not willing to tweak your storage backend is to set
-the `THUMBNAIL_FORCE_OVERWRITE` setting to `True` by default is `False`, so it will
-avoid to overly query the S3 API
+A fast workaround if you are not willing to tweak your storage backend is to set::
+
+   THUMBNAIL_FORCE_OVERWRITE = True
 
+So it will avoid to overly query the S3 API.
 
 
 
@@ -156,8 +157,10 @@ avoid to overly query the S3 API
 .. _`PIL`: http://www.pythonware.com/products/pil/
 .. _`Wand`: http://docs.wand-py.org/
 .. _`pgmagick`: http://pgmagick.readthedocs.org/en/latest/
+.. _`vipsthumbnail`: http://www.vips.ecs.soton.ac.uk/index.php?title=VIPS
 
 .. _`Template examples`: http://sorl-thumbnail.readthedocs.org/en/latest/examples.html#template-examples
 .. _`Model examples`: http://sorl-thumbnail.readthedocs.org/en/latest/examples.html#model-examples
 .. _`Low level API examples`: http://sorl-thumbnail.readthedocs.org/en/latest/examples.html#low-level-api-examples
-.. _ `#351`: https://github.com/mariocesar/sorl-thumbnail/issues/351
\ No newline at end of file
+.. _`issue #351`: https://github.com/mariocesar/sorl-thumbnail/issues/351
+.. _`Django supported versions policy`: https://www.djangoproject.com/download/#supported-versions
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 26e4329..3301b09 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -19,6 +19,10 @@ Django versions, we provide an easy way to test locally across all of them.
 We use `Vagrant`_ for simple interaction with virtual machines and
 `tox`_ for managing python virtual environments.
 
+Some dependencies like pgmagick takes a lot of time to compiling. To speed up your
+vagrant box you can edit `Vagrant file`_ with mem and cpu or simply install `vagrant-faster`_.
+The resulting .tox folder containing all virtualenvs requires ~
+
 * `Install Vagrant`_
 * ``cd`` in your source directory
 * Run ``vagrant up`` to prepare VM. It will download Ubuntu image and install all necessary dependencies.
@@ -27,9 +31,9 @@ We use `Vagrant`_ for simple interaction with virtual machines and
 
 To run only tests against only one configuration use ``-e`` option::
 
-    tox -e py33-1.6-pil
+    tox -e py34-django16-pil
 
-Py33 stands for python version, 1.6 is Django version and the latter is image library.
+Py34 stands for python version, 1.6 is Django version and the latter is image library.
 For full list of tox environments, see ``tox.ini``
 
 You can get away without using Vagrant if you install all packages locally yourself,
@@ -39,6 +43,8 @@ however, this is not recommended.
 .. _Vagrant: http://www.vagrantup.com/
 .. _tox: https://testrun.org/tox/latest/
 .. _Install Vagrant: http://docs.vagrantup.com/v2/installation/index.html
+.. _Vagrant file: https://docs.vagrantup.com/v2/virtualbox/configuration.html
+.. _vagrant-faster: https://github.com/rdsubhas/vagrant-faster
 
 Sending pull requests
 =====================
diff --git a/docs/installation.rst b/docs/installation.rst
index 79974e0..9402faa 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -21,5 +21,5 @@ Setup
 3. If you are using the cached database key value store you need to sync the
    database::
 
-    python manage.py syncdb
+    python manage.py migrate
 
diff --git a/docs/reference/settings.rst b/docs/reference/settings.rst
index e109766..f02f214 100644
--- a/docs/reference/settings.rst
+++ b/docs/reference/settings.rst
@@ -198,7 +198,7 @@ The port for Redis server. Only applicable for the Redis Key Value Store
 
 Filename of the DBM database. Depending on the DBM engine selected by your
 Python installation, this will be used as a prefix because multiple files may be
-created.
+created. This can be an absolute path.
 
 
 ``THUMBNAIL_DBM_MODE``
diff --git a/docs/requirements.rst b/docs/requirements.rst
index daff3a9..dcfbba5 100644
--- a/docs/requirements.rst
+++ b/docs/requirements.rst
@@ -4,11 +4,11 @@ Requirements
 
 Base requirements
 =================
-- `Python`_ 2.5+
+- `Python`_ 2.7+
 - `Django`_
 - :ref:`kvstore-requirements`
 - :ref:`image-library`
-  
+
 .. _kvstore-requirements:
 
 Key Value Store
@@ -42,7 +42,7 @@ Image Library
 You need to have an image library installed. sorl-thumbnail ships with support
 for `Python Imaging Library`_, `pgmagick`_, `ImageMagick`_ (or `GraphicsMagick`)
 command line tools. `pgmagick`_ are python bindings for `GraphicsMagick`_
-(Magick++)`, 
+(Magick++)`,
 
 The `ImageMagick`_ based engine ``sorl.thumbnail.engines.convert_engine.Engine``
 by default calls ``convert`` and ``identify`` shell commands. You can change the
@@ -120,4 +120,3 @@ Ubuntu installation::
 .. _Python: http://www.python.org/
 .. _pgmagick: http://bitbucket.org/hhatto/pgmagick/src
 .. _wand: http://wand-py.org
-
diff --git a/docs/template.rst b/docs/template.rst
index aa4846b..bfdc020 100644
--- a/docs/template.rst
+++ b/docs/template.rst
@@ -42,6 +42,8 @@ Source can be an ImageField, FileField, a file name (assuming default_storage),
 a url. What we need to know is name and storage, see how ImageFile figures
 these things out::
 
+    from django.utils.encoding import force_text
+
     class ImageFile(BaseImageFile):
         _size = None
 
@@ -52,7 +54,7 @@ these things out::
             if hasattr(file_, 'name'):
                 self.name = file_.name
             else:
-                self.name = force_unicode(file_)
+                self.name = force_text(file_)
             # figure out storage
             if storage is not None:
                 self.storage = storage
@@ -110,7 +112,7 @@ above text. After it is rescaled it will apply the cropping options. There are
 some differences to the `css background-position`_:
 
 - Only % and px are valid lengths (units)
-- ``noop`` (No Operation) is a valid option which means there is no 
+- ``noop`` (No Operation) is a valid option which means there is no
   cropping after the initial rescaling to minimum of width and height.
 
 There are many overlapping options here for example ``center`` is equivalent to
diff --git a/setup.py b/setup.py
index 6cd7202..b17c9c4 100644
--- a/setup.py
+++ b/setup.py
@@ -27,7 +27,7 @@ setup(
     platforms='any',
     zip_safe=False,
     classifiers=[
-        'Development Status :: 5 - Production/Stable',
+        'Development Status :: 2 - Pre-Alpha',
         'Environment :: Web Environment',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: BSD License',
@@ -39,11 +39,9 @@ setup(
         'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
         'Topic :: Multimedia :: Graphics',
         'Framework :: Django',
-        'Framework :: Django :: 1.4',
-        'Framework :: Django :: 1.5',
-        'Framework :: Django :: 1.6',
-        'Framework :: Django :: 1.7',
         'Framework :: Django :: 1.8',
+        'Framework :: Django :: 1.9',
+        'Framework :: Django :: 1.10',
     ],
     cmdclass={"test": TestCommand},
 )
diff --git a/sorl/__init__.py b/sorl/__init__.py
index 5af7ec6..19fb7cf 100644
--- a/sorl/__init__.py
+++ b/sorl/__init__.py
@@ -5,10 +5,9 @@ import logging
 
 __author__ = "Mikko Hellsing"
 __license__ = "BSD"
-__version__ = '12.3'
+__version__ = '12.4a1'
 __maintainer__ = "Mario César Señoranis Ayala"
 __email__ = "mariocesar at humanzilla.com"
-__status__ = "Production/Stable"
 
 
 class NullHandler(logging.Handler):
diff --git a/sorl/thumbnail/admin/__init__.py b/sorl/thumbnail/admin/__init__.py
index c4c7cd8..cd51e2b 100644
--- a/sorl/thumbnail/admin/__init__.py
+++ b/sorl/thumbnail/admin/__init__.py
@@ -1,8 +1,4 @@
-try:
-    from django.forms import ClearableFileInput
-except ImportError:
-    from .compat import AdminImageMixin
-else:
-    from .current import AdminImageMixin
+from django.forms import ClearableFileInput
+from .current import AdminImageMixin
 
 AdminInlineImageMixin = AdminImageMixin # backwards compatibility
diff --git a/sorl/thumbnail/admin/compat.py b/sorl/thumbnail/admin/compat.py
deleted file mode 100644
index ac1cf31..0000000
--- a/sorl/thumbnail/admin/compat.py
+++ /dev/null
@@ -1,91 +0,0 @@
-from __future__ import unicode_literals
-from django import forms
-from django.utils.safestring import mark_safe
-from django.utils.translation import ugettext_lazy as _
-from sorl.thumbnail.fields import ImageField, ImageFormField
-from sorl.thumbnail.shortcuts import get_thumbnail
-
-
-class ClearableImageFormField(forms.MultiValueField):
-    def __init__(self, max_length=None, **kwargs):
-        fields = (
-            ImageFormField(max_length=max_length, **kwargs),
-            forms.BooleanField()
-        )
-        super(ClearableImageFormField, self).__init__(fields, **kwargs)
-
-    def compress(self, data_list):
-        if data_list:
-            if not data_list[0] and data_list[1]:
-                return False
-            return data_list[0]
-
-
-class AdminImageWidget(forms.FileInput):
-    """
-    An ImageField Widget for django.contrib.admin that shows a thumbnailed
-    image as well as a link to the current one if it hase one.
-    """
-
-    def render(self, name, value, attrs=None):
-        output = super(AdminImageWidget, self).render(name, value, attrs)
-        if value and hasattr(value, 'url'):
-            ext = 'JPG'
-            try:
-                aux_ext = str(value).split('.')
-                if aux_ext[len(aux_ext)-1].lower() == 'png':
-                    ext = 'PNG'
-            except:
-                pass
-            try:
-                mini = get_thumbnail(value, 'x80', upscale=False, format=ext)
-            except Exception:
-                pass
-            else:
-                output = (
-                    '<div style="float:left">'
-                    '<a style="width:%spx;display:block;margin:0 0 10px" class="thumbnail"'
-                    'target="_blank" href="%s">'
-                    '<img src="%s"></a>%s</div>'
-                ) % (mini.width, value.url, mini.url, output)
-        return mark_safe(output)
-
-
-class AdminClearWidget(forms.CheckboxInput):
-    def render(self, name, value, attrs=None):
-        output = super(AdminClearWidget, self).render(name, value, attrs)
-        output = (
-            '<div style="clear:both;padding-top:5px">'
-            '<label for="id_%s">%s:</label>%s'
-            '</div>'
-        ) % (name, _('Clear image'), output)
-        return mark_safe(output)
-
-
-class AdminClearableImageWidget(forms.MultiWidget):
-    def __init__(self, attrs=None):
-        widgets = (AdminImageWidget(attrs=attrs), AdminClearWidget())
-        super(AdminClearableImageWidget, self).__init__(widgets, attrs)
-
-    def decompress(self, value):
-        if value:
-            return (value, False)
-        return (None, None)
-
-
-class AdminImageMixin(object):
-    """
-    This is a mix-in for ModelAdmin subclasses to make ``ImageField`` show nicer
-    form class and widget
-    """
-
-    def formfield_for_dbfield(self, db_field, **kwargs):
-        if isinstance(db_field, ImageField):
-            if not db_field.blank:
-                return db_field.formfield(widget=AdminImageWidget)
-            return db_field.formfield(
-                form_class=ClearableImageFormField,
-                widget=AdminClearableImageWidget,
-            )
-        sup = super(AdminImageMixin, self)
-        return sup.formfield_for_dbfield(db_field, **kwargs)
diff --git a/sorl/thumbnail/admin/current.py b/sorl/thumbnail/admin/current.py
index 9b77988..175de85 100644
--- a/sorl/thumbnail/admin/current.py
+++ b/sorl/thumbnail/admin/current.py
@@ -16,9 +16,11 @@ class AdminImageWidget(forms.ClearableFileInput):
     image as well as a link to the current one if it hase one.
     """
 
-    template_with_initial = '%(clear_template)s<br />%(input_text)s: %(input)s'
-    template_with_clear = ('%(clear)s <label style="width:auto" for="%(clear_checkbox_id)s">'
-                           '%(clear_checkbox_label)s</label>')
+    template_with_initial = (
+        '%(clear_template)s <br>'
+        '<label>%(input_text)s: %(input)s</label>'
+    )
+    template_with_clear = '<label>%(clear_checkbox_label)s: %(clear)s</label>'
 
     def render(self, name, value, attrs=None):
         output = super(AdminImageWidget, self).render(name, value, attrs)
@@ -28,12 +30,14 @@ class AdminImageWidget(forms.ClearableFileInput):
                 aux_ext = str(value).split('.')
                 if aux_ext[len(aux_ext) - 1].lower() == 'png':
                     ext = 'PNG'
+                elif aux_ext[len(aux_ext) - 1].lower() == 'gif':
+                    ext = 'GIF'
             except:
                 pass
             try:
                 mini = get_thumbnail(value, 'x80', upscale=False, format=ext)
             except Exception as e:
-                logger.warn("Unable to get the thumbnail", exc_info=e)
+                logger.warning("Unable to get the thumbnail", exc_info=e)
             else:
                 try:
                     output = (
diff --git a/sorl/thumbnail/base.py b/sorl/thumbnail/base.py
index b826fed..c9ed177 100644
--- a/sorl/thumbnail/base.py
+++ b/sorl/thumbnail/base.py
@@ -4,7 +4,8 @@ import logging
 import os
 import re
 
-from sorl.thumbnail.compat import string_type, text_type
+from django.utils.six import string_types
+
 from sorl.thumbnail.conf import settings, defaults as default_settings
 from sorl.thumbnail.helpers import tokey, serialize
 from sorl.thumbnail.images import ImageFile, DummyImageFile
@@ -17,6 +18,8 @@ logger = logging.getLogger(__name__)
 EXTENSIONS = {
     'JPEG': 'jpg',
     'PNG': 'png',
+    'GIF': 'gif',
+    'WEBP': 'webp',
 }
 
 
@@ -54,6 +57,10 @@ class ThumbnailBackend(object):
             return 'JPEG'
         elif file_extension == '.png':
             return 'PNG'
+        elif file_extension == '.gif':
+            return 'GIF'
+        elif file_extension == '.webp':
+            return 'WEBP'
         else:
             from django.conf import settings
 
@@ -65,7 +72,7 @@ class ThumbnailBackend(object):
         options given. First it will try to get it from the key value store,
         secondly it will create it.
         """
-        logger.debug(text_type('Getting thumbnail for file [%s] at [%s]'), file_, geometry_string)
+        logger.debug('Getting thumbnail for file [%s] at [%s]', file_, geometry_string)
 
         if file_:
             source = ImageFile(file_)
@@ -98,7 +105,7 @@ class ThumbnailBackend(object):
 
         # We have to check exists() because the Storage backend does not
         # overwrite in some implementations.
-        if not thumbnail.exists():
+        if settings.THUMBNAIL_FORCE_OVERWRITE or not thumbnail.exists():
             try:
                 source_image = default.engine.get_image(source)
             except IOError as e:
@@ -109,9 +116,10 @@ class ThumbnailBackend(object):
                     # if S3Storage says file doesn't exist remotely, don't try to
                     # create it and exit early.
                     # Will return working empty image type; 404'd image
-                    logger.warn(text_type('Remote file [%s] at [%s] does not exist'),
-                                file_, geometry_string)
-
+                    logger.warning(
+                        'Remote file [%s] at [%s] does not exist',
+                        file_, geometry_string,
+                    )
                     return thumbnail
 
             # We might as well set the size since we have the image in memory
@@ -150,7 +158,7 @@ class ThumbnailBackend(object):
         """
         Creates the thumbnail by using default.engine
         """
-        logger.debug(text_type('Creating thumbnail file [%s] at [%s] with [%s]'),
+        logger.debug('Creating thumbnail file [%s] at [%s] with [%s]',
                      thumbnail.name, geometry_string, options)
         ratio = default.engine.get_image_ratio(source_image, options)
         geometry = parse_geometry(geometry_string, ratio)
@@ -173,7 +181,7 @@ class ThumbnailBackend(object):
         for resolution in settings.THUMBNAIL_ALTERNATIVE_RESOLUTIONS:
             resolution_geometry = (int(geometry[0] * resolution), int(geometry[1] * resolution))
             resolution_options = options.copy()
-            if 'crop' in options and isinstance(options['crop'], string_type):
+            if 'crop' in options and isinstance(options['crop'], string_types):
                 crop = options['crop'].split(" ")
                 for i in range(len(crop)):
                     s = re.match("(\d+)px", crop[i])
diff --git a/sorl/thumbnail/compat.py b/sorl/thumbnail/compat.py
index 30e5c63..644e283 100644
--- a/sorl/thumbnail/compat.py
+++ b/sorl/thumbnail/compat.py
@@ -2,18 +2,13 @@ from __future__ import unicode_literals
 
 import sys
 
-import django
-
 __all__ = [
-    'json',
     'BufferIO',
     'urlopen',
     'urlparse',
     'quote',
     'quote_plus',
     'URLError',
-    'get_cache',
-    'force_unicode', 'text_type'
 ]
 
 PythonVersion = sys.version_info[0]
@@ -21,42 +16,6 @@ PythonVersion = sys.version_info[0]
 PY2 = PythonVersion == 2
 PY3 = PythonVersion == 3
 
-# -- import_module
-from importlib import import_module
-
-# -- Related to django 1.5 incompatibility
-
-if django.VERSION < (1, 5):
-    from django.utils import simplejson as json
-    from django.utils.encoding import force_unicode
-else:
-    import json
-    from django.utils.encoding import force_text as force_unicode
-
-# -- Cache
-
-if django.VERSION >= (1, 7):
-    from django.core.cache import caches
-
-    get_cache = lambda cache_name: caches[cache_name]
-else:
-    from django.core.cache import get_cache
-
-# -- Text
-
-try:
-    from django.utils.encoding import smart_text
-except ImportError:
-    from django.utils.encoding import smart_unicode as smart_text
-
-# -- Ordered Dict
-
-try:
-    from collections import OrderedDict
-except ImportError:
-    from django.utils.datastructures import SortedDict as OrderedDict
-
-
 # -- Python 2 and 3
 
 if PY3:
@@ -69,9 +28,6 @@ if PY3:
 
     from io import BytesIO as BufferIO
 
-    text_type = str
-    string_type = str
-
 
     def b(s):
         return s.encode("latin-1")
@@ -95,8 +51,6 @@ elif PY2:
 
     from cStringIO import StringIO as BufferIO
 
-    text_type = unicode
-    string_type = basestring
     urlsplit = urlparse.urlsplit
 
 
diff --git a/sorl/thumbnail/conf/defaults.py b/sorl/thumbnail/conf/defaults.py
index 83637e9..4b833d3 100644
--- a/sorl/thumbnail/conf/defaults.py
+++ b/sorl/thumbnail/conf/defaults.py
@@ -27,6 +27,10 @@ THUMBNAIL_ENGINE = 'sorl.thumbnail.engines.pil_engine.Engine'
 THUMBNAIL_CONVERT = 'convert'
 THUMBNAIL_IDENTIFY = 'identify'
 
+# Path to ``vipsthumbnail`` and ``vipsheader``
+THUMBNAIL_VIPSTHUMBNAIL = 'vipsthumbnail'
+THUMBNAIL_VIPSHEADER = 'vipsheader'
+
 # Storage for the generated thumbnails
 THUMBNAIL_STORAGE = settings.DEFAULT_FILE_STORAGE
 
@@ -54,7 +58,7 @@ THUMBNAIL_KEY_PREFIX = 'sorl-thumbnail'
 # Thumbnail filename prefix
 THUMBNAIL_PREFIX = 'cache/'
 
-# Image format, common formats are: JPEG, PNG
+# Image format, common formats are: JPEG, PNG, GIF
 # Make sure the backend can handle the format you specify
 THUMBNAIL_FORMAT = 'JPEG'
 
@@ -113,3 +117,7 @@ THUMBNAIL_FILTER_WIDTH = 500
 # Should we flatten images by default (fixes a lot of transparency issues with
 # imagemagick)
 THUMBNAIL_FLATTEN = False
+
+# Whenever we will check an existing thumbnail exists and avoid to overwrite or not.
+# Set this to true if you have an slow .exists() implementation on your storage backend of choice.
+THUMBNAIL_FORCE_OVERWRITE = False
diff --git a/sorl/thumbnail/engines/convert_engine.py b/sorl/thumbnail/engines/convert_engine.py
index 6926aba..18664fc 100644
--- a/sorl/thumbnail/engines/convert_engine.py
+++ b/sorl/thumbnail/engines/convert_engine.py
@@ -2,6 +2,7 @@ from __future__ import unicode_literals, with_statement
 import re
 import os
 import subprocess
+from collections import OrderedDict
 
 from django.utils.encoding import smart_str
 from django.core.files.temp import NamedTemporaryFile
@@ -10,7 +11,6 @@ from sorl.thumbnail.base import EXTENSIONS
 from sorl.thumbnail.compat import b
 from sorl.thumbnail.conf import settings
 from sorl.thumbnail.engines.base import EngineBase
-from sorl.thumbnail.compat import OrderedDict
 
 
 size_re = re.compile(r'^(?:.+) (?:[A-Z]+) (?P<x>\d+)x(?P<y>\d+)')
diff --git a/sorl/thumbnail/engines/pil_engine.py b/sorl/thumbnail/engines/pil_engine.py
index db77187..fef582a 100644
--- a/sorl/thumbnail/engines/pil_engine.py
+++ b/sorl/thumbnail/engines/pil_engine.py
@@ -82,13 +82,13 @@ class Engine(EngineBase):
             elif orientation == 4:
                 image = image.transpose(Image.FLIP_TOP_BOTTOM)
             elif orientation == 5:
-                image = image.rotate(-90).transpose(Image.FLIP_LEFT_RIGHT)
+                image = image.rotate(-90, expand=1).transpose(Image.FLIP_LEFT_RIGHT)
             elif orientation == 6:
-                image = image.rotate(-90)
+                image = image.rotate(-90, expand=1)
             elif orientation == 7:
-                image = image.rotate(90).transpose(Image.FLIP_LEFT_RIGHT)
+                image = image.rotate(90, expand=1).transpose(Image.FLIP_LEFT_RIGHT)
             elif orientation == 8:
-                image = image.rotate(90)
+                image = image.rotate(90, expand=1)
 
         return image
 
@@ -100,7 +100,7 @@ class Engine(EngineBase):
                 newimage = image.convert('RGBA')
                 transparency = image.info.get('transparency')
                 if transparency is not None:
-                    mask = Image.new('L', image.size, color=transparency)
+                    mask = image.convert('RGBA').split()[-1]
                     newimage.putalpha(mask)
                 return newimage
             return image.convert('RGB')
diff --git a/sorl/thumbnail/engines/vipsthumbnail_engine.py b/sorl/thumbnail/engines/vipsthumbnail_engine.py
new file mode 100644
index 0000000..9ad8ccd
--- /dev/null
+++ b/sorl/thumbnail/engines/vipsthumbnail_engine.py
@@ -0,0 +1,122 @@
+from __future__ import unicode_literals, with_statement
+import re
+import os
+import subprocess
+from collections import OrderedDict
+
+from django.utils.encoding import smart_str
+from django.core.files.temp import NamedTemporaryFile
+
+from sorl.thumbnail.base import EXTENSIONS
+from sorl.thumbnail.conf import settings
+from sorl.thumbnail.engines.base import EngineBase
+
+
+size_re = re.compile(r'^(?:.+) (?P<x>\d+)x(?P<y>\d+)')
+
+
+class Engine(EngineBase):
+    """
+    Image object is a dict with source path, options and size
+    """
+
+    def write(self, image, options, thumbnail):
+        """
+        Writes the thumbnail image
+        """
+
+        args = settings.THUMBNAIL_VIPSTHUMBNAIL.split(' ')
+        args.append(image['source'])
+
+        for k in image['options']:
+            v = image['options'][k]
+            args.append('--%s' % k)
+            if v is not None:
+                args.append('%s' % v)
+
+        suffix = '.%s' % EXTENSIONS[options['format']]
+
+        write_options = []
+        if options['format'] == 'JPEG' and options.get(
+                'progressive', settings.THUMBNAIL_PROGRESSIVE):
+            write_options.append("interlace")
+
+        if options['quality']:
+            if options['format'] == 'JPEG':
+                write_options.append("Q=%d" % options['quality'])
+
+        with NamedTemporaryFile(suffix=suffix, mode='rb') as fp:
+            # older vipsthumbails used -o, this was renamed to -f in 8.0, use
+            # -o here for commpatibility
+            args.append("-o")
+            args.append(fp.name + "[%s]" % ",".join(write_options))
+
+            args = map(smart_str, args)
+            p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            p.wait()
+            out, err = p.communicate()
+
+            if err:
+                raise Exception(err)
+
+            thumbnail.write(fp.read())
+
+    def cleanup(self, image):
+        os.remove(image['source'])  # we should not need this now
+
+    def get_image(self, source):
+        """
+        Returns the backend image objects from a ImageFile instance
+        """
+        with NamedTemporaryFile(mode='wb', delete=False) as fp:
+            fp.write(source.read())
+        return {'source': fp.name, 'options': OrderedDict(), 'size': None}
+
+    def get_image_size(self, image):
+        """
+        Returns the image width and height as a tuple
+        """
+        if image['size'] is None:
+            args = settings.THUMBNAIL_VIPSHEADER.split(' ')
+            args.append(image['source'])
+            p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            p.wait()
+            m = size_re.match(str(p.stdout.read()))
+            image['size'] = int(m.group('x')), int(m.group('y'))
+        return image['size']
+
+    def is_valid_image(self, raw_data):
+        """
+        vipsheader will try a lot of formats, including all those supported by
+        imagemagick if compiled with magick support, this can take a while
+        """
+        with NamedTemporaryFile(mode='wb') as fp:
+            fp.write(raw_data)
+            fp.flush()
+            args = settings.THUMBNAIL_VIPSHEADER.split(' ')
+            args.append(fp.name)
+            p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            retcode = p.wait()
+        return retcode == 0
+
+    def _orientation(self, image):
+        # vipsthumbnail also corrects the orientation exif data for
+        # destination
+        image['options']['rotate'] = None
+
+        return image
+
+    def _colorspace(self, image, colorspace):
+        """
+        vipsthumbnail does not support greyscaling of images, but pretend it
+        does
+        """
+        return image
+
+    def _scale(self, image, width, height):
+        """
+        Does the resizing of the image
+        """
+        image['options']['size'] = '%sx%s' % (width, height)
+        image['size'] = (width, height)  # update image size
+        return image
diff --git a/sorl/thumbnail/fields.py b/sorl/thumbnail/fields.py
index af4cd0c..8c39452 100644
--- a/sorl/thumbnail/fields.py
+++ b/sorl/thumbnail/fields.py
@@ -39,13 +39,6 @@ class ImageField(models.ImageField):
         if data is not None:
             setattr(instance, self.name, data or '')
 
-    def south_field_triple(self):
-        from south.modelsinspector import introspector
-
-        cls_name = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
-        args, kwargs = introspector(self)
-        return (cls_name, args, kwargs)
-
 
 class ImageFormField(forms.FileField):
     default_error_messages = {
diff --git a/sorl/thumbnail/helpers.py b/sorl/thumbnail/helpers.py
index 800e4e2..1fd13f0 100644
--- a/sorl/thumbnail/helpers.py
+++ b/sorl/thumbnail/helpers.py
@@ -1,10 +1,13 @@
 from __future__ import unicode_literals
 
 import hashlib
+import json
 import math
+from importlib import import_module
 
 from django.core.exceptions import ImproperlyConfigured
-from sorl.thumbnail.compat import encode, json, smart_text, import_module
+from django.utils.encoding import force_text
+from sorl.thumbnail.compat import encode
 
 
 class ThumbnailError(Exception):
@@ -40,7 +43,7 @@ def tokey(*args):
     """
     Computes a unique key from arguments given.
     """
-    salt = '||'.join([smart_text(arg) for arg in args])
+    salt = '||'.join([force_text(arg) for arg in args])
     hash_ = hashlib.md5(encode(salt))
     return hash_.hexdigest()
 
diff --git a/sorl/thumbnail/images.py b/sorl/thumbnail/images.py
index 22fc887..afc7d5b 100644
--- a/sorl/thumbnail/images.py
+++ b/sorl/thumbnail/images.py
@@ -1,17 +1,19 @@
 # encoding=utf-8
 
 from __future__ import unicode_literals, division
+import json
 import os
 import re
 
 from django.core.files.base import File, ContentFile
-from django.core.files.storage import Storage, default_storage
+from django.core.files.storage import Storage  # , default_storage
+from django.utils.encoding import force_text
 from django.utils.functional import LazyObject, empty
 from sorl.thumbnail import default
 from sorl.thumbnail.conf import settings
-from sorl.thumbnail.compat import (json, urlopen, urlparse, urlsplit,
-                                   quote, quote_plus,
-                                   URLError, force_unicode, encode)
+from sorl.thumbnail.compat import (urlopen, urlparse, urlsplit,
+                                   quote, quote_plus, URLError, encode)
+from sorl.thumbnail.default import storage as default_storage
 from sorl.thumbnail.helpers import ThumbnailError, tokey, get_module_class, deserialize
 from sorl.thumbnail.parsers import parse_geometry
 
@@ -85,7 +87,17 @@ class ImageFile(BaseImageFile):
         if hasattr(file_, 'name'):
             self.name = file_.name
         else:
-            self.name = force_unicode(file_)
+            self.name = force_text(file_)
+
+        # TODO: Add a customizable naming method as a signal
+
+        # Remove query args from names. Fixes cache and signature arguments
+        # from third party services, like Amazon S3 and signature args.
+        self.name = self.name.split('?')[0]
+
... 468 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/sorl-thumbnail.git



More information about the Python-modules-commits mailing list