From 4965a774074780f3e4858bcc975476f71edf2c2c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 21 Mar 2014 10:54:53 -0400 Subject: [PATCH] Removed PIL compatability layer per deprecation timeline. refs #19934. --- django/core/files/images.py | 12 +- django/db/models/fields/files.py | 5 +- django/forms/fields.py | 4 +- django/utils/image.py | 157 ------------------ docs/faq/contributing.txt | 2 +- docs/ref/forms/fields.txt | 14 +- docs/ref/models/fields.txt | 4 +- docs/topics/security.txt | 4 +- tests/files/tests.py | 16 +- .../test_ordinary_fields.py | 5 +- tests/model_fields/models.py | 10 +- tests/model_fields/test_imagefield.py | 16 +- tests/model_forms/models.py | 6 +- tests/model_forms/tests.py | 2 +- tests/serializers_regress/models.py | 2 +- 15 files changed, 48 insertions(+), 211 deletions(-) delete mode 100644 django/utils/image.py diff --git a/django/core/files/images.py b/django/core/files/images.py index b289d789ad..8883ae4416 100644 --- a/django/core/files/images.py +++ b/django/core/files/images.py @@ -1,7 +1,7 @@ """ Utility functions for handling images. -Requires Pillow (or PIL), as you might imagine. +Requires Pillow as you might imagine. """ import zlib @@ -35,9 +35,9 @@ 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. """ - from django.utils.image import ImageFile as PILImageFile + from PIL import ImageFile as PillowImageFile - p = PILImageFile.Parser() + p = PillowImageFile.Parser() if hasattr(file_or_path, 'read'): file = file_or_path file_pos = file.tell() @@ -46,9 +46,9 @@ def get_image_dimensions(file_or_path, close=False): file = open(file_or_path, 'rb') close = True try: - # Most of the time PIL only needs a small chunk to parse the image and - # get the dimensions, but with some TIFF files PIL needs to parse the - # whole file. + # Most of the time Pillow only needs a small chunk to parse the image + # and get the dimensions, but with some TIFF files Pillow needs to + # parse the whole file. chunk_size = 1024 while 1: data = file.read(chunk_size) diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 0b2a24a895..6cb49db24a 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -4,7 +4,6 @@ import os from django import forms from django.db.models.fields import Field from django.core import checks -from django.core.exceptions import ImproperlyConfigured from django.core.files.base import File from django.core.files.storage import default_storage from django.core.files.images import ImageFile @@ -386,8 +385,8 @@ class ImageField(FileField): def _check_image_library_installed(self): try: - from django.utils.image import Image # NOQA - except ImproperlyConfigured: + from PIL import Image # NOQA + except ImportError: return [ checks.Error( 'Cannot use ImageField because Pillow is not installed.', diff --git a/django/forms/fields.py b/django/forms/fields.py index 63ed153f0b..0ee66a9db3 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -641,7 +641,7 @@ class ImageField(FileField): if f is None: return None - from django.utils.image import Image + from PIL import Image # We need to get a file object for Pillow. We might have a path or we might # have to read the data into memory. @@ -659,7 +659,7 @@ class ImageField(FileField): # verify() must be called immediately after the constructor. Image.open(file).verify() except Exception: - # Pillow (or PIL) doesn't recognize it as an image. + # Pillow doesn't recognize it as an image. six.reraise(ValidationError, ValidationError( self.error_messages['invalid_image'], code='invalid_image', diff --git a/django/utils/image.py b/django/utils/image.py deleted file mode 100644 index 795300c1e4..0000000000 --- a/django/utils/image.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- -""" -To provide a shim layer over Pillow/PIL situation until the PIL support is -removed. See #19934. - - -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?) - * On some platforms (Homebrew and RHEL6 reported) _imaging isn't available, - the needed import is from PIL import _imaging (refs #21355) - -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 ``from PIL import _imaging`` and - ``import _imaging`` - - * ``ImportError`` - Bad install, toss an exception - -""" -from __future__ import unicode_literals - -import warnings - -from django.core.exceptions import ImproperlyConfigured -from django.utils.deprecation import RemovedInDjango18Warning -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( - _("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, '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() == '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: - from PIL import _imaging as PIL_imaging - except ImportError: - try: - import _imaging as PIL_imaging - except ImportError as err: - raise ImproperlyConfigured( - _("The '_imaging' module for the PIL could not be " - "imported: %s") % err - ) - - # Try to import ImageFile as well. - try: - from PIL import ImageFile as PILImageFile - except ImportError: - # This import cannot fail unless Pillow/PIL install is completely - # broken (e.g. missing Python modules). - 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.", - RemovedInDjango18Warning - ) - - return PILImage, PIL_imaging, PILImageFile - - -Image, _imaging, ImageFile = _detect_image_library() diff --git a/docs/faq/contributing.txt b/docs/faq/contributing.txt index 20950e88c5..cdf2b6c6ca 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 Pillow/PIL), a contrib module, or a specific + touches a dependency (such as Pillow), a contrib module, or a specific database, are those instructions clear enough even for someone not familiar with it? diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 975b77ef29..5363284b39 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -619,22 +619,20 @@ 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 Pillow/PIL. + file is of an image format understood by Pillow. * Error message keys: ``required``, ``invalid``, ``missing``, ``empty``, ``invalid_image`` - 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 Pillow or PIL. + Using an ``ImageField`` requires that `Pillow`_ is installed with support + for the image formats you use. If you encounter a ``corrupt image`` error + when you upload an image, it usually means that Pillow doesn't understand + its format. To fix this, install the appropriate library and reinstall + Pillow. When you use an ``ImageField`` on a form, you must also remember to :ref:`bind the file data to the form `. .. _Pillow: http://python-imaging.github.io/Pillow/ -.. _Python Imaging Library: http://www.pythonware.com/products/pil/ ``IntegerField`` ~~~~~~~~~~~~~~~~ diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 39af0c64b0..feaf846b24 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -841,9 +841,9 @@ optional arguments: Name of a model field which will be auto-populated with the width of the image each time the model instance is saved. -Requires the `Python Imaging Library`_. +Requires the `Pillow`_ library. -.. _Python Imaging Library: http://www.pythonware.com/products/pil/ +.. _Pillow: http://python-imaging.github.io/Pillow/ By default, :class:`ImageField` instances are created as ``varchar(100)`` columns in your database. As with other fields, you can change the maximum diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 1ae5ddf78e..aaee7d8977 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -226,8 +226,8 @@ User-uploaded content served in ways that do not follow security best practices. Specifically, an HTML file can be uploaded as an image if that file contains a valid PNG header followed by malicious HTML. This file will pass verification of the - libraries that Django uses for :class:`~django.db.models.ImageField` image - processing (PIL or Pillow). When this file is subsequently displayed to a + library that Django uses for :class:`~django.db.models.ImageField` image + processing (Pillow). When this file is subsequently displayed to a user, it may be displayed as HTML depending on the type and configuration of your web server. diff --git a/tests/files/tests.py b/tests/files/tests.py index b9f8b5228a..4f5e171712 100644 --- a/tests/files/tests.py +++ b/tests/files/tests.py @@ -8,7 +8,6 @@ import tempfile import unittest import zlib -from django.core.exceptions import ImproperlyConfigured from django.core.files import File from django.core.files.move import file_move_safe from django.core.files.base import ContentFile @@ -18,10 +17,11 @@ from django.utils._os import upath from django.utils import six try: - from django.utils.image import Image - from django.core.files import images -except ImproperlyConfigured: + from PIL import Image +except ImportError: Image = None +else: + from django.core.files import images class FileTests(unittest.TestCase): @@ -112,7 +112,7 @@ class DimensionClosingBug(unittest.TestCase): """ Test that get_image_dimensions() properly closes files (#8817) """ - @unittest.skipUnless(Image, "Pillow/PIL not installed") + @unittest.skipUnless(Image, "Pillow not installed") def test_not_closing_of_files(self): """ Open files passed into get_image_dimensions() should stay opened. @@ -123,7 +123,7 @@ class DimensionClosingBug(unittest.TestCase): finally: self.assertTrue(not empty_io.closed) - @unittest.skipUnless(Image, "Pillow/PIL not installed") + @unittest.skipUnless(Image, "Pillow not installed") def test_closing_of_filenames(self): """ get_image_dimensions() called with a filename should closed the file. @@ -163,7 +163,7 @@ class InconsistentGetImageDimensionsBug(unittest.TestCase): Test that get_image_dimensions() works properly after various calls using a file handler (#11158) """ - @unittest.skipUnless(Image, "Pillow/PIL not installed") + @unittest.skipUnless(Image, "Pillow not installed") def test_multiple_calls(self): """ Multiple calls of get_image_dimensions() should return the same size. @@ -177,7 +177,7 @@ class InconsistentGetImageDimensionsBug(unittest.TestCase): self.assertEqual(image_pil.size, size_1) self.assertEqual(size_1, size_2) - @unittest.skipUnless(Image, "Pillow/PIL not installed") + @unittest.skipUnless(Image, "Pillow not installed") def test_bug_19457(self): """ Regression test for #19457 diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py index 47e3d14d3f..3ad7e250b0 100644 --- a/tests/invalid_models_tests/test_ordinary_fields.py +++ b/tests/invalid_models_tests/test_ordinary_fields.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import unittest from django.core.checks import Error -from django.core.exceptions import ImproperlyConfigured from django.db import connection, models from .base import IsolatedModelsTestCase @@ -379,8 +378,8 @@ class ImageFieldTests(IsolatedModelsTestCase): def test_pillow_installed(self): try: - import django.utils.image # NOQA - except ImproperlyConfigured: + from PIL import Image # NOQA + except ImportError: pillow_installed = False else: pillow_installed = True diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py index c4e99c949a..5fe4104851 100644 --- a/tests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -2,11 +2,9 @@ import os import tempfile import warnings -from django.core.exceptions import ImproperlyConfigured - try: - from django.utils.image import Image -except ImproperlyConfigured: + from PIL import Image +except ImportError: Image = None from django.core.files.storage import FileSystemStorage @@ -114,7 +112,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 Pillow/PIL in this test + # Don't want to depend on Pillow in this test #field_image = models.ImageField("verbose field") field12 = models.IntegerField("verbose field12") with warnings.catch_warnings(record=True) as w: @@ -151,7 +149,7 @@ class Document(models.Model): ############################################################################### # ImageField -# If Pillow/PIL available, do these tests. +# If Pillow 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 90b7a65727..be8584a571 100644 --- a/tests/model_fields/test_imagefield.py +++ b/tests/model_fields/test_imagefield.py @@ -20,7 +20,7 @@ if Image: PersonDimensionsFirst, PersonTwoImages, TestImageFieldFile) from .models import temp_storage_dir else: - # Pillow/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 @@ -93,7 +93,7 @@ class ImageFieldTestMixin(object): self.assertEqual(getattr(instance, height_field_name), height) -@skipIf(Image is None, "Pillow/PIL is required to test ImageField") +@skipIf(Image is None, "Pillow is required to test ImageField") class ImageFieldTests(ImageFieldTestMixin, TestCase): """ Tests for ImageField that don't need to be run with each of the @@ -180,7 +180,7 @@ class ImageFieldTests(ImageFieldTestMixin, TestCase): self.assertEqual(p.mugshot, loaded_p.mugshot) -@skipIf(Image is None, "Pillow/PIL is required to test ImageField") +@skipIf(Image is None, "Pillow is required to test ImageField") class ImageFieldTwoDimensionsTests(ImageFieldTestMixin, TestCase): """ Tests behavior of an ImageField and its dimensions fields. @@ -294,7 +294,7 @@ class ImageFieldTwoDimensionsTests(ImageFieldTestMixin, TestCase): self.assertEqual(p.mugshot.was_opened, True) -@skipIf(Image is None, "Pillow/PIL is required to test ImageField") +@skipIf(Image is None, "Pillow is required to test ImageField") class ImageFieldNoDimensionsTests(ImageFieldTwoDimensionsTests): """ Tests behavior of an ImageField with no dimension fields. @@ -303,7 +303,7 @@ class ImageFieldNoDimensionsTests(ImageFieldTwoDimensionsTests): PersonModel = Person -@skipIf(Image is None, "Pillow/PIL is required to test ImageField") +@skipIf(Image is None, "Pillow is required to test ImageField") class ImageFieldOneDimensionTests(ImageFieldTwoDimensionsTests): """ Tests behavior of an ImageField with one dimensions field. @@ -312,7 +312,7 @@ class ImageFieldOneDimensionTests(ImageFieldTwoDimensionsTests): PersonModel = PersonWithHeight -@skipIf(Image is None, "Pillow/PIL is required to test ImageField") +@skipIf(Image is None, "Pillow is required to test ImageField") class ImageFieldDimensionsFirstTests(ImageFieldTwoDimensionsTests): """ Tests behavior of an ImageField where the dimensions fields are @@ -322,7 +322,7 @@ class ImageFieldDimensionsFirstTests(ImageFieldTwoDimensionsTests): PersonModel = PersonDimensionsFirst -@skipIf(Image is None, "Pillow/PIL is required to test ImageField") +@skipIf(Image is None, "Pillow is required to test ImageField") class ImageFieldUsingFileTests(ImageFieldTwoDimensionsTests): """ Tests behavior of an ImageField when assigning it a File instance @@ -333,7 +333,7 @@ class ImageFieldUsingFileTests(ImageFieldTwoDimensionsTests): File = File -@skipIf(Image is None, "Pillow/PIL is required to test ImageField") +@skipIf(Image is None, "Pillow is required to test ImageField") class TwoImageFieldTests(ImageFieldTestMixin, TestCase): """ Tests a model with two ImageFields. diff --git a/tests/model_forms/models.py b/tests/model_forms/models.py index 59b18dc39d..28feaa97cc 100644 --- a/tests/model_forms/models.py +++ b/tests/model_forms/models.py @@ -13,7 +13,7 @@ import os import tempfile from django.core import validators -from django.core.exceptions import ImproperlyConfigured, ValidationError +from django.core.exceptions import ValidationError from django.core.files.storage import FileSystemStorage from django.db import models from django.utils import six @@ -154,7 +154,7 @@ class FilePathModel(models.Model): try: - from django.utils.image import Image # NOQA: detect if Pillow is installed + from PIL import Image # NOQA: detect if Pillow is installed test_images = True @@ -193,7 +193,7 @@ try: def __str__(self): return self.description -except ImproperlyConfigured: +except ImportError: test_images = False diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 468f83686e..4504ecb91d 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -1852,7 +1852,7 @@ class FileAndImageFieldTests(TestCase): names.sort() self.assertEqual(names, ['---------', '__init__.py', 'models.py', 'tests.py']) - @skipUnless(test_images, "Pillow/PIL not installed") + @skipUnless(test_images, "Pillow not installed") def test_image_field(self): # ImageField and FileField are nearly identical, but they differ slighty when # it comes to validation. This specifically tests that #6302 is fixed for diff --git a/tests/serializers_regress/models.py b/tests/serializers_regress/models.py index 2f8d0bf0e8..c2d7789259 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 Pillow/PIL dependency). +(except for image types, because of the Pillow dependency). """ import warnings