summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Lindsley <daniel@toastdriven.com>2013-05-14 19:31:16 -0700
committerDaniel Lindsley <daniel@toastdriven.com>2013-05-14 19:32:04 -0700
commit33793f7c3edd8ff144ff2e9434367267c20af26a (patch)
tree2fc05d98ee3d9c2bb9fd9a208647ad280d52b78c
parentc792c83cad54f064b6ba13e285e95a90e2c61f09 (diff)
downloaddjango-33793f7c3edd8ff144ff2e9434367267c20af26a.tar.gz
Fixed #19934 - Use of Pillow is now preferred over PIL.
This starts the deprecation period for PIL (support to end in 1.8).
-rw-r--r--django/core/files/images.py8
-rw-r--r--django/core/management/validation.py8
-rw-r--r--django/forms/fields.py16
-rw-r--r--django/utils/image.py146
-rw-r--r--docs/faq/contributing.txt2
-rw-r--r--docs/internals/deprecation.txt6
-rw-r--r--docs/ref/forms/fields.txt12
-rw-r--r--docs/releases/1.6.txt7
-rw-r--r--tests/file_storage/tests.py20
-rw-r--r--tests/model_fields/models.py17
-rw-r--r--tests/model_fields/test_imagefield.py8
-rw-r--r--tests/model_forms/models.py12
-rw-r--r--tests/serializers_regress/models.py2
13 files changed, 198 insertions, 66 deletions
diff --git a/django/core/files/images.py b/django/core/files/images.py
index 0d87ae853e..e1d6091658 100644
--- a/django/core/files/images.py
+++ b/django/core/files/images.py
@@ -1,7 +1,7 @@
"""
Utility functions for handling images.
-Requires PIL, as you might imagine.
+Requires Pillow (or PIL), as you might imagine.
"""
import zlib
@@ -35,11 +35,7 @@ def get_image_dimensions(file_or_path, close=False):
'close' to True to close the file at the end if it is initially in an open
state.
"""
- # Try to import PIL in either of the two ways it can end up installed.
- try:
- from PIL import ImageFile as PILImageFile
- except ImportError:
- import ImageFile as PILImageFile
+ from django.utils.image import ImageFile as PILImageFile
p = PILImageFile.Parser()
if hasattr(file_or_path, 'read'):
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index 94d604346b..0f0eade569 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -105,14 +105,10 @@ def get_validation_errors(outfile, app=None):
if isinstance(f, models.FileField) and not f.upload_to:
e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name)
if isinstance(f, models.ImageField):
- # Try to import PIL in either of the two ways it can end up installed.
try:
- from PIL import Image
+ from django.utils.image import Image
except ImportError:
- try:
- import Image
- except ImportError:
- e.add(opts, '"%s": To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name)
+ e.add(opts, '"%s": To use ImageFields, you need to install Pillow. Get it at https://pypi.python.org/pypi/Pillow.' % f.name)
if isinstance(f, models.BooleanField) and getattr(f, 'null', False):
e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders):
diff --git a/django/forms/fields.py b/django/forms/fields.py
index 146a10d635..4ce57d34a3 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -602,13 +602,9 @@ class ImageField(FileField):
if f is None:
return None
- # Try to import PIL in either of the two ways it can end up installed.
- try:
- from PIL import Image
- except ImportError:
- import Image
+ from django.utils.image import Image
- # We need to get a file object for PIL. We might have a path or we might
+ # We need to get a file object for Pillow. We might have a path or we might
# have to read the data into memory.
if hasattr(data, 'temporary_file_path'):
file = data.temporary_file_path()
@@ -623,12 +619,8 @@ class ImageField(FileField):
# image in memory, which is a DoS vector. See #3848 and #18520.
# verify() must be called immediately after the constructor.
Image.open(file).verify()
- except ImportError:
- # Under PyPy, it is possible to import PIL. However, the underlying
- # _imaging C module isn't available, so an ImportError will be
- # raised. Catch and re-raise.
- raise
- except Exception: # Python Imaging Library doesn't recognize it as an image
+ except Exception:
+ # Pillow (or PIL) doesn't recognize it as an image.
six.reraise(ValidationError, ValidationError(self.error_messages['invalid_image']), sys.exc_info()[2])
if hasattr(f, 'seek') and callable(f.seek):
f.seek(0)
diff --git a/django/utils/image.py b/django/utils/image.py
new file mode 100644
index 0000000000..ed9b210973
--- /dev/null
+++ b/django/utils/image.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+"""
+To provide a shim layer over Pillow/PIL situation until the PIL support is
+removed.
+
+
+Combinations To Account For
+===========================
+
+* Pillow:
+
+ * never has ``_imaging`` under any Python
+ * has the ``Image.alpha_composite``, which may aid in detection
+
+* PIL
+
+ * CPython 2.x may have _imaging (& work)
+ * CPython 2.x may *NOT* have _imaging (broken & needs a error message)
+ * CPython 3.x doesn't work
+ * PyPy will *NOT* have _imaging (but works?)
+
+Restated, that looks like:
+
+* If we're on Python 2.x, it could be either Pillow or PIL:
+
+ * If ``import _imaging`` results in ``ImportError``, either they have a
+ working Pillow installation or a broken PIL installation, so we need to
+ detect further:
+
+ * To detect, we first ``import Image``.
+ * If ``Image`` has a ``alpha_composite`` attribute present, only Pillow
+ has this, so we assume it's working.
+ * If ``Image`` DOES NOT have a ``alpha_composite``attribute, it must be
+ PIL & is a broken (likely C compiler-less) install, which we need to
+ warn the user about.
+
+ * If ``import _imaging`` works, it must be PIL & is a working install.
+
+* Python 3.x
+
+ * If ``import Image`` works, it must be Pillow, since PIL isn't Python 3.x
+ compatible.
+
+* PyPy
+
+ * If ``import _imaging`` results in ``ImportError``, it could be either
+ Pillow or PIL, both of which work without it on PyPy, so we're fine.
+
+
+Approach
+========
+
+* Attempt to import ``Image``
+
+ * ``ImportError`` - nothing is installed, toss an exception
+ * Either Pillow or the PIL is installed, so continue detecting
+
+* Attempt to ``hasattr(Image, 'alpha_composite')``
+
+ * If it works, it's Pillow & working
+ * If it fails, we've got a PIL install, continue detecting
+
+ * The only option here is that we're on Python 2.x or PyPy, of which
+ we only care about if we're on CPython.
+ * If we're on CPython, attempt to ``import _imaging``
+
+ * ``ImportError`` - Bad install, toss an exception
+
+"""
+import warnings
+
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.translation import ugettext_lazy as _
+
+
+Image = None
+_imaging = None
+ImageFile = None
+
+
+def _detect_image_library():
+ global Image
+ global _imaging
+ global ImageFile
+
+ # Skip re-attempting to import if we've already run detection.
+ if Image is not None:
+ return Image, _imaging, ImageFile
+
+ # Assume it's not there.
+ PIL_imaging = False
+
+ try:
+ # Try from the Pillow (or one variant of PIL) install location first.
+ from PIL import Image as PILImage
+ except ImportError as err:
+ try:
+ # If that failed, try the alternate import syntax for PIL.
+ import Image as PILImage
+ except ImportError as err:
+ # Neither worked, so it's likely not installed.
+ raise ImproperlyConfigured(
+ _(u"Neither Pillow nor PIL could be imported: %s" % err)
+ )
+
+ # ``Image.alpha_composite`` was added to Pillow in SHA: e414c6 & is not
+ # available in any version of the PIL.
+ if hasattr(PILImage, u'alpha_composite'):
+ PIL_imaging = False
+ else:
+ # We're dealing with the PIL. Determine if we're on CPython & if
+ # ``_imaging`` is available.
+ import platform
+
+ # This is the Alex Approved™ way.
+ # See http://mail.python.org/pipermail//pypy-dev/2011-November/008739.html
+ if platform.python_implementation().lower() == u'cpython':
+ # We're on CPython (likely 2.x). Since a C compiler is needed to
+ # produce a fully-working PIL & will create a ``_imaging`` module,
+ # we'll attempt to import it to verify their kit works.
+ try:
+ import _imaging as PIL_imaging
+ except ImportError as err:
+ raise ImproperlyConfigured(
+ _(u"The '_imaging' module for the PIL could not be " +
+ u"imported: %s" % err)
+ )
+
+ # Try to import ImageFile as well.
+ try:
+ from PIL import ImageFile as PILImageFile
+ except ImportError:
+ import ImageFile as PILImageFile
+
+ # Finally, warn about deprecation...
+ if PIL_imaging is not False:
+ warnings.warn(
+ "Support for the PIL will be removed in Django 1.8. Please " +
+ "uninstall it & install Pillow instead.",
+ PendingDeprecationWarning
+ )
+
+ return PILImage, PIL_imaging, PILImageFile
+
+
+Image, _imaging, ImageFile = _detect_image_library()
diff --git a/docs/faq/contributing.txt b/docs/faq/contributing.txt
index 6f2dfd906f..20950e88c5 100644
--- a/docs/faq/contributing.txt
+++ b/docs/faq/contributing.txt
@@ -27,7 +27,7 @@ to make it dead easy, even for someone who may not be intimately familiar with
that area of the code, to understand the problem and verify the fix:
* Are there clear instructions on how to reproduce the bug? If this
- touches a dependency (such as PIL), a contrib module, or a specific
+ touches a dependency (such as Pillow/PIL), a contrib module, or a specific
database, are those instructions clear enough even for someone not
familiar with it?
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 862907b2a8..774de2a2fd 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -365,6 +365,12 @@ these changes.
* ``django.conf.urls.shortcut`` and ``django.views.defaults.shortcut`` will be
removed.
+* Support for the Python Imaging Library (PIL) module will be removed, as it
+ no longer appears to be actively maintained & does not work on Python 3.
+ You are advised to install `Pillow`_, which should be used instead.
+
+.. _`Pillow`: https://pypi.python.org/pypi/Pillow
+
* The following private APIs will be removed:
- ``django.db.close_connection()``
diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
index 29f889445d..054f45c430 100644
--- a/docs/ref/forms/fields.txt
+++ b/docs/ref/forms/fields.txt
@@ -608,19 +608,21 @@ For each field, we describe the default widget used if you don't specify
* Normalizes to: An ``UploadedFile`` object that wraps the file content
and file name into a single object.
* Validates that file data has been bound to the form, and that the
- file is of an image format understood by PIL.
+ file is of an image format understood by Pillow/PIL.
* Error message keys: ``required``, ``invalid``, ``missing``, ``empty``,
``invalid_image``
- Using an ``ImageField`` requires that the `Python Imaging Library`_ (PIL)
- is installed and supports the image formats you use. If you encounter a
- ``corrupt image`` error when you upload an image, it usually means PIL
+ Using an ``ImageField`` requires that either `Pillow`_ (recommended) or the
+ `Python Imaging Library`_ (PIL) are installed and supports the image
+ formats you use. If you encounter a ``corrupt image`` error when you
+ upload an image, it usually means either Pillow or PIL
doesn't understand its format. To fix this, install the appropriate
- library and reinstall PIL.
+ library and reinstall Pillow or PIL.
When you use an ``ImageField`` on a form, you must also remember to
:ref:`bind the file data to the form <binding-uploaded-files>`.
+.. _Pillow: http://python-imaging.github.io/Pillow/
.. _Python Imaging Library: http://www.pythonware.com/products/pil/
``IntegerField``
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 5780229eb5..7469783659 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -220,6 +220,13 @@ Minor features
* Added ``BCryptSHA256PasswordHasher`` to resolve the password truncation issue
with bcrypt.
+* `Pillow`_ is now the preferred image manipulation library to use with Django.
+ `PIL`_ is pending deprecation (support to be removed in Django 1.8).
+ To upgrade, you should **first** uninstall PIL, **then** install Pillow.
+
+.. _`Pillow`: https://pypi.python.org/pypi/Pillow
+.. _`PIL`: https://pypi.python.org/pypi/PIL
+
Backwards incompatible changes in 1.6
=====================================
diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
index 5e6adee894..e4b71dba82 100644
--- a/tests/file_storage/tests.py
+++ b/tests/file_storage/tests.py
@@ -29,16 +29,10 @@ from django.utils._os import upath
from django.test.utils import override_settings
from servers.tests import LiveServerBase
-# Try to import PIL in either of the two ways it can end up installed.
-# Checking for the existence of Image is enough for CPython, but
-# for PyPy, you need to check for the underlying modules
try:
- from PIL import Image, _imaging
-except ImportError:
- try:
- import Image, _imaging
- except ImportError:
- Image = None
+ from django.utils.image import Image
+except ImproperlyConfigured:
+ Image = None
class GetStorageClassTests(SimpleTestCase):
@@ -494,7 +488,7 @@ class DimensionClosingBug(unittest.TestCase):
"""
Test that get_image_dimensions() properly closes files (#8817)
"""
- @unittest.skipUnless(Image, "PIL not installed")
+ @unittest.skipUnless(Image, "Pillow/PIL not installed")
def test_not_closing_of_files(self):
"""
Open files passed into get_image_dimensions() should stay opened.
@@ -505,7 +499,7 @@ class DimensionClosingBug(unittest.TestCase):
finally:
self.assertTrue(not empty_io.closed)
- @unittest.skipUnless(Image, "PIL not installed")
+ @unittest.skipUnless(Image, "Pillow/PIL not installed")
def test_closing_of_filenames(self):
"""
get_image_dimensions() called with a filename should closed the file.
@@ -542,7 +536,7 @@ class InconsistentGetImageDimensionsBug(unittest.TestCase):
Test that get_image_dimensions() works properly after various calls
using a file handler (#11158)
"""
- @unittest.skipUnless(Image, "PIL not installed")
+ @unittest.skipUnless(Image, "Pillow/PIL not installed")
def test_multiple_calls(self):
"""
Multiple calls of get_image_dimensions() should return the same size.
@@ -556,7 +550,7 @@ class InconsistentGetImageDimensionsBug(unittest.TestCase):
self.assertEqual(image_pil.size, size_1)
self.assertEqual(size_1, size_2)
- @unittest.skipUnless(Image, "PIL not installed")
+ @unittest.skipUnless(Image, "Pillow/PIL not installed")
def test_bug_19457(self):
"""
Regression test for #19457
diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py
index c3b2f7fccb..2d602d6412 100644
--- a/tests/model_fields/models.py
+++ b/tests/model_fields/models.py
@@ -1,17 +1,12 @@
import os
import tempfile
-# Try to import PIL in either of the two ways it can end up installed.
-# Checking for the existence of Image is enough for CPython, but for PyPy,
-# you need to check for the underlying modules.
+from django.core.exceptions import ImproperlyConfigured
try:
- from PIL import Image, _imaging
-except ImportError:
- try:
- import Image, _imaging
- except ImportError:
- Image = None
+ from django.utils.image import Image
+except ImproperlyConfigured:
+ Image = None
from django.core.files.storage import FileSystemStorage
from django.db import models
@@ -87,7 +82,7 @@ class VerboseNameField(models.Model):
field9 = models.FileField("verbose field9", upload_to="unused")
field10 = models.FilePathField("verbose field10")
field11 = models.FloatField("verbose field11")
- # Don't want to depend on PIL in this test
+ # Don't want to depend on Pillow/PIL in this test
#field_image = models.ImageField("verbose field")
field12 = models.IntegerField("verbose field12")
field13 = models.IPAddressField("verbose field13")
@@ -119,7 +114,7 @@ class Document(models.Model):
###############################################################################
# ImageField
-# If PIL available, do these tests.
+# If Pillow/PIL available, do these tests.
if Image:
class TestImageFieldFile(ImageFieldFile):
"""
diff --git a/tests/model_fields/test_imagefield.py b/tests/model_fields/test_imagefield.py
index df0215db3d..457892ddb8 100644
--- a/tests/model_fields/test_imagefield.py
+++ b/tests/model_fields/test_imagefield.py
@@ -3,20 +3,24 @@ from __future__ import absolute_import
import os
import shutil
+from django.core.exceptions import ImproperlyConfigured
from django.core.files import File
from django.core.files.images import ImageFile
from django.test import TestCase
from django.utils._os import upath
from django.utils.unittest import skipIf
-from .models import Image
+try:
+ from .models import Image
+except ImproperlyConfigured:
+ Image = None
if Image:
from .models import (Person, PersonWithHeight, PersonWithHeightAndWidth,
PersonDimensionsFirst, PersonTwoImages, TestImageFieldFile)
from .models import temp_storage_dir
else:
- # PIL not available, create dummy classes (tests will be skipped anyway)
+ # Pillow not available, create dummy classes (tests will be skipped anyway)
class Person():
pass
PersonWithHeight = PersonWithHeightAndWidth = PersonDimensionsFirst = Person
diff --git a/tests/model_forms/models.py b/tests/model_forms/models.py
index 25c780f1c2..a79d9b8c5b 100644
--- a/tests/model_forms/models.py
+++ b/tests/model_forms/models.py
@@ -11,6 +11,7 @@ from __future__ import unicode_literals
import os
import tempfile
+from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import FileSystemStorage
from django.db import models
from django.utils import six
@@ -91,14 +92,7 @@ class TextFile(models.Model):
return self.description
try:
- # If PIL is available, try testing ImageFields. Checking for the existence
- # of Image is enough for CPython, but for PyPy, you need to check for the
- # underlying modules If PIL is not available, ImageField tests are omitted.
- # Try to import PIL in either of the two ways it can end up installed.
- try:
- from PIL import Image, _imaging
- except ImportError:
- import Image, _imaging
+ from django.utils.image import Image
test_images = True
@@ -137,7 +131,7 @@ try:
def __str__(self):
return self.description
-except ImportError:
+except ImproperlyConfigured:
test_images = False
@python_2_unicode_compatible
diff --git a/tests/serializers_regress/models.py b/tests/serializers_regress/models.py
index 21fe073122..21a3448a8e 100644
--- a/tests/serializers_regress/models.py
+++ b/tests/serializers_regress/models.py
@@ -2,7 +2,7 @@
A test spanning all the capabilities of all the serializers.
This class sets up a model for each model field type
-(except for image types, because of the PIL dependency).
+(except for image types, because of the Pillow/PIL dependency).
"""
from django.db import models