From 8b7347220f3d86b46f5f87270c6cdcb9960895fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petras=20Zdanavi=C4=8Dius?= Date: Sat, 26 Jul 2014 15:25:44 +0300 Subject: [PATCH] Fixed #23103 -- Annotated ImageField file with image and content_type attributes. Thanks Jeremy Dunck for the suggestion and Nick Sanford for review. --- django/forms/fields.py | 7 ++++- docs/ref/forms/fields.txt | 9 ++++++ docs/releases/1.8.txt | 6 ++++ .../tests/filepath_test_files/1x1.png | Bin 0 -> 150 bytes tests/forms_tests/tests/test_fields.py | 27 +++++++++++++++++- 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 tests/forms_tests/tests/filepath_test_files/1x1.png diff --git a/django/forms/fields.py b/django/forms/fields.py index 9070963607..6e44ccfbb7 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -659,8 +659,13 @@ class ImageField(FileField): try: # load() could spot a truncated JPEG, but it loads the entire # image in memory, which is a DoS vector. See #3848 and #18520. + image = Image.open(file) # verify() must be called immediately after the constructor. - Image.open(file).verify() + image.verify() + + # Annotating so subclasses can reuse it for their own validation + f.image = image + f.content_type = Image.MIME[image.format] except Exception: # Pillow doesn't recognize it as an image. six.reraise(ValidationError, ValidationError( diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index a886536597..e92013a832 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -649,7 +649,16 @@ For each field, we describe the default widget used if you don't specify When you use an ``ImageField`` on a form, you must also remember to :ref:`bind the file data to the form `. + .. versionchanged:: 1.8 + + After the field has been cleaned and validated, the ``UploadedFile`` + object will have an additional ``image`` attribute containing the Pillow + `Image`_ instance used to check if the file was a valid image. + ``UploadedFile.content_type`` is also updated with the image's content + type as determined by Pillow. + .. _Pillow: http://pillow.readthedocs.org/en/latest/ +.. _Image: https://pillow.readthedocs.org/en/latest/reference/Image.html ``IntegerField`` ~~~~~~~~~~~~~~~~ diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index d4943fd0da..15dca75e1d 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -160,6 +160,12 @@ Forms :attr:`~django.forms.extras.widgets.SelectDateWidget.empty_label` argument, which will override the top list choice label when :class:`~django.forms.DateField` is not required. +* After an :class:`~django.forms.ImageField` has been cleaned and validated, the + ``UploadedFile`` object will have an additional ``image`` attribute containing + the Pillow ``Image`` instance used to check if the file was a valid image. It + will also update ``UploadedFile.content_type`` with the image's content type + as determined by Pillow. + Internationalization ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/forms_tests/tests/filepath_test_files/1x1.png b/tests/forms_tests/tests/filepath_test_files/1x1.png new file mode 100644 index 0000000000000000000000000000000000000000..5e2f43a2e1939077f96a7ae3ac5d3e3c5f45b7a3 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2ryoCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#9=jBmh4_d44F7;avY8S|xv6<2KrRD=b5UwyNotBhd1gt5g1e`0K#E=} nJ5Zd*)5S4_<9hOs|Nre7SpPBoyK^S;IY@)2tDnm{r-UW|8YU)o literal 0 HcmV?d00001 diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py index 2482fe0682..0b82b3940b 100644 --- a/tests/forms_tests/tests/test_fields.py +++ b/tests/forms_tests/tests/test_fields.py @@ -31,12 +31,18 @@ import pickle import re import os from decimal import Decimal +from unittest import skipIf + +try: + from PIL import Image +except ImportError: + Image = None from django.core.files.uploadedfile import SimpleUploadedFile from django.forms import ( BooleanField, CharField, ChoiceField, ComboField, DateField, DateTimeField, DecimalField, EmailField, Field, FileField, FilePathField, FloatField, - Form, forms, HiddenInput, IntegerField, MultipleChoiceField, + Form, forms, HiddenInput, ImageField, IntegerField, MultipleChoiceField, NullBooleanField, NumberInput, PasswordInput, RadioSelect, RegexField, SplitDateTimeField, TextInput, Textarea, TimeField, TypedChoiceField, TypedMultipleChoiceField, URLField, ValidationError, Widget, @@ -741,6 +747,24 @@ class FieldsTests(SimpleTestCase): # with here) self.assertTrue(f._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'})) + # ImageField ################################################################## + + @skipIf(Image is None, "Pillow is required to test ImageField") + def test_imagefield_annotate_with_image_after_clean(self): + f = ImageField() + + img_path = os.path.dirname(upath(__file__)) + '/filepath_test_files/1x1.png' + with open(img_path, 'rb') as img_file: + img_data = img_file.read() + + img_file = SimpleUploadedFile('1x1.png', img_data) + img_file.content_type = 'text/plain' + + uploaded_file = f.clean(img_file) + + self.assertEqual('PNG', uploaded_file.image.format) + self.assertEqual('image/png', uploaded_file.content_type) + # URLField ################################################################## def test_urlfield_1(self): @@ -1262,6 +1286,7 @@ class FieldsTests(SimpleTestCase): f.choices.sort() expected = [ ('/tests/forms_tests/tests/filepath_test_files/.dot-file', '.dot-file'), + ('/tests/forms_tests/tests/filepath_test_files/1x1.png', '1x1.png'), ('/tests/forms_tests/tests/filepath_test_files/directory', 'directory'), ('/tests/forms_tests/tests/filepath_test_files/fake-image.jpg', 'fake-image.jpg'), ('/tests/forms_tests/tests/filepath_test_files/real-text-file.txt', 'real-text-file.txt'),