import pickle import unittest from django.core.exceptions import ValidationError from django.core.files.uploadedfile import SimpleUploadedFile from django.core.validators import validate_image_file_extension from django.forms import FileField, FileInput from django.test import SimpleTestCase try: from PIL import Image # NOQA except ImportError: HAS_PILLOW = False else: HAS_PILLOW = True class FileFieldTest(SimpleTestCase): def test_filefield_1(self): f = FileField() with self.assertRaisesMessage(ValidationError, "'This field is required.'"): f.clean("") with self.assertRaisesMessage(ValidationError, "'This field is required.'"): f.clean("", "") self.assertEqual("files/test1.pdf", f.clean("", "files/test1.pdf")) with self.assertRaisesMessage(ValidationError, "'This field is required.'"): f.clean(None) with self.assertRaisesMessage(ValidationError, "'This field is required.'"): f.clean(None, "") self.assertEqual("files/test2.pdf", f.clean(None, "files/test2.pdf")) no_file_msg = "'No file was submitted. Check the encoding type on the form.'" file = SimpleUploadedFile(None, b"") file._name = "" with self.assertRaisesMessage(ValidationError, no_file_msg): f.clean(file) with self.assertRaisesMessage(ValidationError, no_file_msg): f.clean(file, "") self.assertEqual("files/test3.pdf", f.clean(None, "files/test3.pdf")) with self.assertRaisesMessage(ValidationError, no_file_msg): f.clean("some content that is not a file") with self.assertRaisesMessage( ValidationError, "'The submitted file is empty.'" ): f.clean(SimpleUploadedFile("name", None)) with self.assertRaisesMessage( ValidationError, "'The submitted file is empty.'" ): f.clean(SimpleUploadedFile("name", b"")) self.assertEqual( SimpleUploadedFile, type(f.clean(SimpleUploadedFile("name", b"Some File Content"))), ) self.assertIsInstance( f.clean( SimpleUploadedFile( "我隻氣墊船裝滿晒鱔.txt", "मेरी मँडराने वाली नाव सर्पमीनों से भरी ह".encode(), ) ), SimpleUploadedFile, ) self.assertIsInstance( f.clean( SimpleUploadedFile("name", b"Some File Content"), "files/test4.pdf" ), SimpleUploadedFile, ) def test_filefield_2(self): f = FileField(max_length=5) with self.assertRaisesMessage( ValidationError, "'Ensure this filename has at most 5 characters (it has 18).'", ): f.clean(SimpleUploadedFile("test_maxlength.txt", b"hello world")) self.assertEqual("files/test1.pdf", f.clean("", "files/test1.pdf")) self.assertEqual("files/test2.pdf", f.clean(None, "files/test2.pdf")) self.assertIsInstance( f.clean(SimpleUploadedFile("name", b"Some File Content")), SimpleUploadedFile, ) def test_filefield_3(self): f = FileField(allow_empty_file=True) self.assertIsInstance( f.clean(SimpleUploadedFile("name", b"")), SimpleUploadedFile ) def test_filefield_changed(self): """ The value of data will more than likely come from request.FILES. The value of initial data will likely be a filename stored in the database. Since its value is of no use to a FileField it is ignored. """ f = FileField() # No file was uploaded and no initial data. self.assertFalse(f.has_changed("", None)) # A file was uploaded and no initial data. self.assertTrue( f.has_changed("", {"filename": "resume.txt", "content": "My resume"}) ) # A file was not uploaded, but there is initial data self.assertFalse(f.has_changed("resume.txt", None)) # A file was uploaded and there is initial data (file identity is not dealt # with here) self.assertTrue( f.has_changed( "resume.txt", {"filename": "resume.txt", "content": "My resume"} ) ) def test_disabled_has_changed(self): f = FileField(disabled=True) self.assertIs(f.has_changed("x", "y"), False) def test_file_picklable(self): self.assertIsInstance(pickle.loads(pickle.dumps(FileField())), FileField) class MultipleFileInput(FileInput): allow_multiple_selected = True class MultipleFileField(FileField): def __init__(self, *args, **kwargs): kwargs.setdefault("widget", MultipleFileInput()) super().__init__(*args, **kwargs) def clean(self, data, initial=None): single_file_clean = super().clean if isinstance(data, (list, tuple)): result = [single_file_clean(d, initial) for d in data] else: result = single_file_clean(data, initial) return result class MultipleFileFieldTest(SimpleTestCase): def test_file_multiple(self): f = MultipleFileField() files = [ SimpleUploadedFile("name1", b"Content 1"), SimpleUploadedFile("name2", b"Content 2"), ] self.assertEqual(f.clean(files), files) def test_file_multiple_empty(self): f = MultipleFileField() files = [ SimpleUploadedFile("empty", b""), SimpleUploadedFile("nonempty", b"Some Content"), ] msg = "'The submitted file is empty.'" with self.assertRaisesMessage(ValidationError, msg): f.clean(files) with self.assertRaisesMessage(ValidationError, msg): f.clean(files[::-1]) @unittest.skipUnless(HAS_PILLOW, "Pillow not installed") def test_file_multiple_validation(self): f = MultipleFileField(validators=[validate_image_file_extension]) good_files = [ SimpleUploadedFile("image1.jpg", b"fake JPEG"), SimpleUploadedFile("image2.png", b"faux image"), SimpleUploadedFile("image3.bmp", b"fraudulent bitmap"), ] self.assertEqual(f.clean(good_files), good_files) evil_files = [ SimpleUploadedFile("image1.sh", b"#!/bin/bash -c 'echo pwned!'\n"), SimpleUploadedFile("image2.png", b"faux image"), SimpleUploadedFile("image3.jpg", b"fake JPEG"), ] evil_rotations = ( evil_files[i:] + evil_files[:i] # Rotate by i. for i in range(len(evil_files)) ) msg = "File extension “sh” is not allowed. Allowed extensions are: " for rotated_evil_files in evil_rotations: with self.assertRaisesMessage(ValidationError, msg): f.clean(rotated_evil_files)