Fixed #19934 - Use of Pillow is now preferred over PIL.

This starts the deprecation period for PIL (support to end in 1.8).
This commit is contained in:
Daniel Lindsley 2013-05-14 19:31:16 -07:00
parent c792c83cad
commit 33793f7c3e
13 changed files with 198 additions and 66 deletions

View File

@ -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'):

View File

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

View File

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

146
django/utils/image.py Normal file
View File

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

View File

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

View File

@ -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()``

View File

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

View File

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

View File

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

View File

@ -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):
"""

View File

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

View File

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

View File

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