Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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

This starts the deprecation period for PIL (support to end in 1.8).
  • Loading branch information...
commit 33793f7c3edd8ff144ff2e9434367267c20af26a 1 parent c792c83
Daniel Lindsley authored May 14, 2013
8  django/core/files/images.py
... ...
@@ -1,7 +1,7 @@
1 1
 """
2 2
 Utility functions for handling images.
3 3
 
4  
-Requires PIL, as you might imagine.
  4
+Requires Pillow (or PIL), as you might imagine.
5 5
 """
6 6
 import zlib
7 7
 
@@ -35,11 +35,7 @@ def get_image_dimensions(file_or_path, close=False):
35 35
     'close' to True to close the file at the end if it is initially in an open
36 36
     state.
37 37
     """
38  
-    # Try to import PIL in either of the two ways it can end up installed.
39  
-    try:
40  
-        from PIL import ImageFile as PILImageFile
41  
-    except ImportError:
42  
-        import ImageFile as PILImageFile
  38
+    from django.utils.image import ImageFile as PILImageFile
43 39
 
44 40
     p = PILImageFile.Parser()
45 41
     if hasattr(file_or_path, 'read'):
8  django/core/management/validation.py
@@ -105,14 +105,10 @@ def get_validation_errors(outfile, app=None):
105 105
             if isinstance(f, models.FileField) and not f.upload_to:
106 106
                 e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name)
107 107
             if isinstance(f, models.ImageField):
108  
-                # Try to import PIL in either of the two ways it can end up installed.
109 108
                 try:
110  
-                    from PIL import Image
  109
+                    from django.utils.image import Image
111 110
                 except ImportError:
112  
-                    try:
113  
-                        import Image
114  
-                    except ImportError:
115  
-                        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)
  111
+                    e.add(opts, '"%s": To use ImageFields, you need to install Pillow. Get it at https://pypi.python.org/pypi/Pillow.' % f.name)
116 112
             if isinstance(f, models.BooleanField) and getattr(f, 'null', False):
117 113
                 e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
118 114
             if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders):
16  django/forms/fields.py
@@ -602,13 +602,9 @@ def to_python(self, data):
602 602
         if f is None:
603 603
             return None
604 604
 
605  
-        # Try to import PIL in either of the two ways it can end up installed.
606  
-        try:
607  
-            from PIL import Image
608  
-        except ImportError:
609  
-            import Image
  605
+        from django.utils.image import Image
610 606
 
611  
-        # We need to get a file object for PIL. We might have a path or we might
  607
+        # We need to get a file object for Pillow. We might have a path or we might
612 608
         # have to read the data into memory.
613 609
         if hasattr(data, 'temporary_file_path'):
614 610
             file = data.temporary_file_path()
@@ -623,12 +619,8 @@ def to_python(self, data):
623 619
             # image in memory, which is a DoS vector. See #3848 and #18520.
624 620
             # verify() must be called immediately after the constructor.
625 621
             Image.open(file).verify()
626  
-        except ImportError:
627  
-            # Under PyPy, it is possible to import PIL. However, the underlying
628  
-            # _imaging C module isn't available, so an ImportError will be
629  
-            # raised. Catch and re-raise.
630  
-            raise
631  
-        except Exception: # Python Imaging Library doesn't recognize it as an image
  622
+        except Exception:
  623
+            # Pillow (or PIL) doesn't recognize it as an image.
632 624
             six.reraise(ValidationError, ValidationError(self.error_messages['invalid_image']), sys.exc_info()[2])
633 625
         if hasattr(f, 'seek') and callable(f.seek):
634 626
             f.seek(0)
146  django/utils/image.py
... ...
@@ -0,0 +1,146 @@
  1
+# -*- coding: utf-8 -*-
  2
+"""
  3
+To provide a shim layer over Pillow/PIL situation until the PIL support is
  4
+removed.
  5
+
  6
+
  7
+Combinations To Account For
  8
+===========================
  9
+
  10
+* Pillow:
  11
+
  12
+    * never has ``_imaging`` under any Python
  13
+    * has the ``Image.alpha_composite``, which may aid in detection
  14
+
  15
+* PIL
  16
+
  17
+    * CPython 2.x may have _imaging (& work)
  18
+    * CPython 2.x may *NOT* have _imaging (broken & needs a error message)
  19
+    * CPython 3.x doesn't work
  20
+    * PyPy will *NOT* have _imaging (but works?)
  21
+
  22
+Restated, that looks like:
  23
+
  24
+* If we're on Python 2.x, it could be either Pillow or PIL:
  25
+
  26
+    * If ``import _imaging`` results in ``ImportError``, either they have a
  27
+      working Pillow installation or a broken PIL installation, so we need to
  28
+      detect further:
  29
+
  30
+        * To detect, we first ``import Image``.
  31
+        * If ``Image`` has a ``alpha_composite`` attribute present, only Pillow
  32
+          has this, so we assume it's working.
  33
+        * If ``Image`` DOES NOT have a ``alpha_composite``attribute, it must be
  34
+          PIL & is a broken (likely C compiler-less) install, which we need to
  35
+          warn the user about.
  36
+
  37
+    * If ``import _imaging`` works, it must be PIL & is a working install.
  38
+
  39
+* Python 3.x
  40
+
  41
+    * If ``import Image`` works, it must be Pillow, since PIL isn't Python 3.x
  42
+      compatible.
  43
+
  44
+* PyPy
  45
+
  46
+    * If ``import _imaging`` results in ``ImportError``, it could be either
  47
+      Pillow or PIL, both of which work without it on PyPy, so we're fine.
  48
+
  49
+
  50
+Approach
  51
+========
  52
+
  53
+* Attempt to import ``Image``
  54
+
  55
+    * ``ImportError`` - nothing is installed, toss an exception
  56
+    * Either Pillow or the PIL is installed, so continue detecting
  57
+
  58
+* Attempt to ``hasattr(Image, 'alpha_composite')``
  59
+
  60
+    * If it works, it's Pillow & working
  61
+    * If it fails, we've got a PIL install, continue detecting
  62
+
  63
+        * The only option here is that we're on Python 2.x or PyPy, of which
  64
+          we only care about if we're on CPython.
  65
+        * If we're on CPython, attempt to ``import _imaging``
  66
+
  67
+            * ``ImportError`` - Bad install, toss an exception
  68
+
  69
+"""
  70
+import warnings
  71
+
  72
+from django.core.exceptions import ImproperlyConfigured
  73
+from django.utils.translation import ugettext_lazy as _
  74
+
  75
+
  76
+Image = None
  77
+_imaging = None
  78
+ImageFile = None
  79
+
  80
+
  81
+def _detect_image_library():
  82
+    global Image
  83
+    global _imaging
  84
+    global ImageFile
  85
+
  86
+    # Skip re-attempting to import if we've already run detection.
  87
+    if Image is not None:
  88
+        return Image, _imaging, ImageFile
  89
+
  90
+    # Assume it's not there.
  91
+    PIL_imaging = False
  92
+
  93
+    try:
  94
+        # Try from the Pillow (or one variant of PIL) install location first.
  95
+        from PIL import Image as PILImage
  96
+    except ImportError as err:
  97
+        try:
  98
+            # If that failed, try the alternate import syntax for PIL.
  99
+            import Image as PILImage
  100
+        except ImportError as err:
  101
+            # Neither worked, so it's likely not installed.
  102
+            raise ImproperlyConfigured(
  103
+                _(u"Neither Pillow nor PIL could be imported: %s" % err)
  104
+            )
  105
+
  106
+    # ``Image.alpha_composite`` was added to Pillow in SHA: e414c6 & is not
  107
+    # available in any version of the PIL.
  108
+    if hasattr(PILImage, u'alpha_composite'):
  109
+        PIL_imaging = False
  110
+    else:
  111
+        # We're dealing with the PIL. Determine if we're on CPython & if
  112
+        # ``_imaging`` is available.
  113
+        import platform
  114
+
  115
+        # This is the Alex Approved™ way.
  116
+        # See http://mail.python.org/pipermail//pypy-dev/2011-November/008739.html
  117
+        if platform.python_implementation().lower() == u'cpython':
  118
+            # We're on CPython (likely 2.x). Since a C compiler is needed to
  119
+            # produce a fully-working PIL & will create a ``_imaging`` module,
  120
+            # we'll attempt to import it to verify their kit works.
  121
+            try:
  122
+                import _imaging as PIL_imaging
  123
+            except ImportError as err:
  124
+                raise ImproperlyConfigured(
  125
+                    _(u"The '_imaging' module for the PIL could not be " +
  126
+                      u"imported: %s" % err)
  127
+                )
  128
+
  129
+    # Try to import ImageFile as well.
  130
+    try:
  131
+        from PIL import ImageFile as PILImageFile
  132
+    except ImportError:
  133
+        import ImageFile as PILImageFile
  134
+
  135
+    # Finally, warn about deprecation...
  136
+    if PIL_imaging is not False:
  137
+        warnings.warn(
  138
+            "Support for the PIL will be removed in Django 1.8. Please " +
  139
+            "uninstall it & install Pillow instead.",
  140
+            PendingDeprecationWarning
  141
+        )
  142
+
  143
+    return PILImage, PIL_imaging, PILImageFile
  144
+
  145
+
  146
+Image, _imaging, ImageFile = _detect_image_library()
2  docs/faq/contributing.txt
@@ -27,7 +27,7 @@ to make it dead easy, even for someone who may not be intimately familiar with
27 27
 that area of the code, to understand the problem and verify the fix:
28 28
 
29 29
 * Are there clear instructions on how to reproduce the bug? If this
30  
-  touches a dependency (such as PIL), a contrib module, or a specific
  30
+  touches a dependency (such as Pillow/PIL), a contrib module, or a specific
31 31
   database, are those instructions clear enough even for someone not
32 32
   familiar with it?
33 33
 
6  docs/internals/deprecation.txt
@@ -365,6 +365,12 @@ these changes.
365 365
 * ``django.conf.urls.shortcut`` and ``django.views.defaults.shortcut`` will be
366 366
   removed.
367 367
 
  368
+* Support for the Python Imaging Library (PIL) module will be removed, as it
  369
+  no longer appears to be actively maintained & does not work on Python 3.
  370
+  You are advised to install `Pillow`_, which should be used instead.
  371
+
  372
+.. _`Pillow`: https://pypi.python.org/pypi/Pillow
  373
+
368 374
 * The following private APIs will be removed:
369 375
 
370 376
   - ``django.db.close_connection()``
12  docs/ref/forms/fields.txt
@@ -608,19 +608,21 @@ For each field, we describe the default widget used if you don't specify
608 608
     * Normalizes to: An ``UploadedFile`` object that wraps the file content
609 609
       and file name into a single object.
610 610
     * Validates that file data has been bound to the form, and that the
611  
-      file is of an image format understood by PIL.
  611
+      file is of an image format understood by Pillow/PIL.
612 612
     * Error message keys: ``required``, ``invalid``, ``missing``, ``empty``,
613 613
       ``invalid_image``
614 614
 
615  
-    Using an ``ImageField`` requires that the `Python Imaging Library`_ (PIL)
616  
-    is installed and supports the image formats you use. If you encounter a
617  
-    ``corrupt image`` error when you upload an image, it usually means PIL
  615
+    Using an ``ImageField`` requires that either `Pillow`_ (recommended) or the
  616
+    `Python Imaging Library`_ (PIL) are installed and supports the image
  617
+    formats you use. If you encounter a ``corrupt image`` error when you
  618
+    upload an image, it usually means either Pillow or PIL
618 619
     doesn't understand its format. To fix this, install the appropriate
619  
-    library and reinstall PIL.
  620
+    library and reinstall Pillow or PIL.
620 621
 
621 622
     When you use an ``ImageField`` on a form, you must also remember to
622 623
     :ref:`bind the file data to the form <binding-uploaded-files>`.
623 624
 
  625
+.. _Pillow: http://python-imaging.github.io/Pillow/
624 626
 .. _Python Imaging Library: http://www.pythonware.com/products/pil/
625 627
 
626 628
 ``IntegerField``
7  docs/releases/1.6.txt
@@ -220,6 +220,13 @@ Minor features
220 220
 * Added ``BCryptSHA256PasswordHasher`` to resolve the password truncation issue
221 221
   with bcrypt.
222 222
 
  223
+* `Pillow`_ is now the preferred image manipulation library to use with Django.
  224
+  `PIL`_ is pending deprecation (support to be removed in Django 1.8).
  225
+  To upgrade, you should **first** uninstall PIL, **then** install Pillow.
  226
+
  227
+.. _`Pillow`: https://pypi.python.org/pypi/Pillow
  228
+.. _`PIL`: https://pypi.python.org/pypi/PIL
  229
+
223 230
 Backwards incompatible changes in 1.6
224 231
 =====================================
225 232
 
20  tests/file_storage/tests.py
@@ -29,16 +29,10 @@
29 29
 from django.test.utils import override_settings
30 30
 from servers.tests import LiveServerBase
31 31
 
32  
-# Try to import PIL in either of the two ways it can end up installed.
33  
-# Checking for the existence of Image is enough for CPython, but
34  
-# for PyPy, you need to check for the underlying modules
35 32
 try:
36  
-    from PIL import Image, _imaging
37  
-except ImportError:
38  
-    try:
39  
-        import Image, _imaging
40  
-    except ImportError:
41  
-        Image = None
  33
+    from django.utils.image import Image
  34
+except ImproperlyConfigured:
  35
+    Image = None
42 36
 
43 37
 
44 38
 class GetStorageClassTests(SimpleTestCase):
@@ -494,7 +488,7 @@ class DimensionClosingBug(unittest.TestCase):
494 488
     """
495 489
     Test that get_image_dimensions() properly closes files (#8817)
496 490
     """
497  
-    @unittest.skipUnless(Image, "PIL not installed")
  491
+    @unittest.skipUnless(Image, "Pillow/PIL not installed")
498 492
     def test_not_closing_of_files(self):
499 493
         """
500 494
         Open files passed into get_image_dimensions() should stay opened.
@@ -505,7 +499,7 @@ def test_not_closing_of_files(self):
505 499
         finally:
506 500
             self.assertTrue(not empty_io.closed)
507 501
 
508  
-    @unittest.skipUnless(Image, "PIL not installed")
  502
+    @unittest.skipUnless(Image, "Pillow/PIL not installed")
509 503
     def test_closing_of_filenames(self):
510 504
         """
511 505
         get_image_dimensions() called with a filename should closed the file.
@@ -542,7 +536,7 @@ class InconsistentGetImageDimensionsBug(unittest.TestCase):
542 536
     Test that get_image_dimensions() works properly after various calls
543 537
     using a file handler (#11158)
544 538
     """
545  
-    @unittest.skipUnless(Image, "PIL not installed")
  539
+    @unittest.skipUnless(Image, "Pillow/PIL not installed")
546 540
     def test_multiple_calls(self):
547 541
         """
548 542
         Multiple calls of get_image_dimensions() should return the same size.
@@ -556,7 +550,7 @@ def test_multiple_calls(self):
556 550
         self.assertEqual(image_pil.size, size_1)
557 551
         self.assertEqual(size_1, size_2)
558 552
 
559  
-    @unittest.skipUnless(Image, "PIL not installed")
  553
+    @unittest.skipUnless(Image, "Pillow/PIL not installed")
560 554
     def test_bug_19457(self):
561 555
         """
562 556
         Regression test for #19457
17  tests/model_fields/models.py
... ...
@@ -1,17 +1,12 @@
1 1
 import os
2 2
 import tempfile
3 3
 
4  
-# Try to import PIL in either of the two ways it can end up installed.
5  
-# Checking for the existence of Image is enough for CPython, but for PyPy,
6  
-# you need to check for the underlying modules.
  4
+from django.core.exceptions import ImproperlyConfigured
7 5
 
8 6
 try:
9  
-    from PIL import Image, _imaging
10  
-except ImportError:
11  
-    try:
12  
-        import Image, _imaging
13  
-    except ImportError:
14  
-        Image = None
  7
+    from django.utils.image import Image
  8
+except ImproperlyConfigured:
  9
+    Image = None
15 10
 
16 11
 from django.core.files.storage import FileSystemStorage
17 12
 from django.db import models
@@ -87,7 +82,7 @@ class VerboseNameField(models.Model):
87 82
     field9 = models.FileField("verbose field9", upload_to="unused")
88 83
     field10 = models.FilePathField("verbose field10")
89 84
     field11 = models.FloatField("verbose field11")
90  
-    # Don't want to depend on PIL in this test
  85
+    # Don't want to depend on Pillow/PIL in this test
91 86
     #field_image = models.ImageField("verbose field")
92 87
     field12 = models.IntegerField("verbose field12")
93 88
     field13 = models.IPAddressField("verbose field13")
@@ -119,7 +114,7 @@ class Document(models.Model):
119 114
 ###############################################################################
120 115
 # ImageField
121 116
 
122  
-# If PIL available, do these tests.
  117
+# If Pillow/PIL available, do these tests.
123 118
 if Image:
124 119
     class TestImageFieldFile(ImageFieldFile):
125 120
         """
8  tests/model_fields/test_imagefield.py
@@ -3,20 +3,24 @@
3 3
 import os
4 4
 import shutil
5 5
 
  6
+from django.core.exceptions import ImproperlyConfigured
6 7
 from django.core.files import File
7 8
 from django.core.files.images import ImageFile
8 9
 from django.test import TestCase
9 10
 from django.utils._os import upath
10 11
 from django.utils.unittest import skipIf
11 12
 
12  
-from .models import Image
  13
+try:
  14
+    from .models import Image
  15
+except ImproperlyConfigured:
  16
+    Image = None
13 17
 
14 18
 if Image:
15 19
     from .models import (Person, PersonWithHeight, PersonWithHeightAndWidth,
16 20
         PersonDimensionsFirst, PersonTwoImages, TestImageFieldFile)
17 21
     from .models import temp_storage_dir
18 22
 else:
19  
-    # PIL not available, create dummy classes (tests will be skipped anyway)
  23
+    # Pillow not available, create dummy classes (tests will be skipped anyway)
20 24
     class Person():
21 25
         pass
22 26
     PersonWithHeight = PersonWithHeightAndWidth = PersonDimensionsFirst = Person
12  tests/model_forms/models.py
@@ -11,6 +11,7 @@
11 11
 import os
12 12
 import tempfile
13 13
 
  14
+from django.core.exceptions import ImproperlyConfigured
14 15
 from django.core.files.storage import FileSystemStorage
15 16
 from django.db import models
16 17
 from django.utils import six
@@ -91,14 +92,7 @@ def __str__(self):
91 92
         return self.description
92 93
 
93 94
 try:
94  
-    # If PIL is available, try testing ImageFields. Checking for the existence
95  
-    # of Image is enough for CPython, but for PyPy, you need to check for the
96  
-    # underlying modules If PIL is not available, ImageField tests are omitted.
97  
-    # Try to import PIL in either of the two ways it can end up installed.
98  
-    try:
99  
-        from PIL import Image, _imaging
100  
-    except ImportError:
101  
-        import Image, _imaging
  95
+    from django.utils.image import Image
102 96
 
103 97
     test_images = True
104 98
 
@@ -137,7 +131,7 @@ def custom_upload_path(self, filename):
137 131
 
138 132
         def __str__(self):
139 133
             return self.description
140  
-except ImportError:
  134
+except ImproperlyConfigured:
141 135
     test_images = False
142 136
 
143 137
 @python_2_unicode_compatible
2  tests/serializers_regress/models.py
@@ -2,7 +2,7 @@
2 2
 A test spanning all the capabilities of all the serializers.
3 3
 
4 4
 This class sets up a model for each model field type
5  
-(except for image types, because of the PIL dependency).
  5
+(except for image types, because of the Pillow/PIL dependency).
6 6
 """
7 7
 
8 8
 from django.db import models

0 notes on commit 33793f7

Please sign in to comment.
Something went wrong with that request. Please try again.