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:
parent
c792c83cad
commit
33793f7c3e
|
@ -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'):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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()``
|
||||
|
|
|
@ -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``
|
||||
|
|
|
@ -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
|
||||
=====================================
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue