1. import pickle
    
  2. import unittest
    
  3. 
    
  4. from django.core.exceptions import ValidationError
    
  5. from django.core.files.uploadedfile import SimpleUploadedFile
    
  6. from django.core.validators import validate_image_file_extension
    
  7. from django.forms import FileField, FileInput
    
  8. from django.test import SimpleTestCase
    
  9. 
    
  10. try:
    
  11.     from PIL import Image  # NOQA
    
  12. except ImportError:
    
  13.     HAS_PILLOW = False
    
  14. else:
    
  15.     HAS_PILLOW = True
    
  16. 
    
  17. 
    
  18. class FileFieldTest(SimpleTestCase):
    
  19.     def test_filefield_1(self):
    
  20.         f = FileField()
    
  21.         with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
    
  22.             f.clean("")
    
  23.         with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
    
  24.             f.clean("", "")
    
  25.         self.assertEqual("files/test1.pdf", f.clean("", "files/test1.pdf"))
    
  26.         with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
    
  27.             f.clean(None)
    
  28.         with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
    
  29.             f.clean(None, "")
    
  30.         self.assertEqual("files/test2.pdf", f.clean(None, "files/test2.pdf"))
    
  31.         no_file_msg = "'No file was submitted. Check the encoding type on the form.'"
    
  32.         file = SimpleUploadedFile(None, b"")
    
  33.         file._name = ""
    
  34.         with self.assertRaisesMessage(ValidationError, no_file_msg):
    
  35.             f.clean(file)
    
  36.         with self.assertRaisesMessage(ValidationError, no_file_msg):
    
  37.             f.clean(file, "")
    
  38.         self.assertEqual("files/test3.pdf", f.clean(None, "files/test3.pdf"))
    
  39.         with self.assertRaisesMessage(ValidationError, no_file_msg):
    
  40.             f.clean("some content that is not a file")
    
  41.         with self.assertRaisesMessage(
    
  42.             ValidationError, "'The submitted file is empty.'"
    
  43.         ):
    
  44.             f.clean(SimpleUploadedFile("name", None))
    
  45.         with self.assertRaisesMessage(
    
  46.             ValidationError, "'The submitted file is empty.'"
    
  47.         ):
    
  48.             f.clean(SimpleUploadedFile("name", b""))
    
  49.         self.assertEqual(
    
  50.             SimpleUploadedFile,
    
  51.             type(f.clean(SimpleUploadedFile("name", b"Some File Content"))),
    
  52.         )
    
  53.         self.assertIsInstance(
    
  54.             f.clean(
    
  55.                 SimpleUploadedFile(
    
  56.                     "我隻氣墊船裝滿晒鱔.txt", "मेरी मँडराने वाली नाव सर्पमीनों से भरी ह".encode()
    
  57.                 )
    
  58.             ),
    
  59.             SimpleUploadedFile,
    
  60.         )
    
  61.         self.assertIsInstance(
    
  62.             f.clean(
    
  63.                 SimpleUploadedFile("name", b"Some File Content"), "files/test4.pdf"
    
  64.             ),
    
  65.             SimpleUploadedFile,
    
  66.         )
    
  67. 
    
  68.     def test_filefield_2(self):
    
  69.         f = FileField(max_length=5)
    
  70.         with self.assertRaisesMessage(
    
  71.             ValidationError,
    
  72.             "'Ensure this filename has at most 5 characters (it has 18).'",
    
  73.         ):
    
  74.             f.clean(SimpleUploadedFile("test_maxlength.txt", b"hello world"))
    
  75.         self.assertEqual("files/test1.pdf", f.clean("", "files/test1.pdf"))
    
  76.         self.assertEqual("files/test2.pdf", f.clean(None, "files/test2.pdf"))
    
  77.         self.assertIsInstance(
    
  78.             f.clean(SimpleUploadedFile("name", b"Some File Content")),
    
  79.             SimpleUploadedFile,
    
  80.         )
    
  81. 
    
  82.     def test_filefield_3(self):
    
  83.         f = FileField(allow_empty_file=True)
    
  84.         self.assertIsInstance(
    
  85.             f.clean(SimpleUploadedFile("name", b"")), SimpleUploadedFile
    
  86.         )
    
  87. 
    
  88.     def test_filefield_changed(self):
    
  89.         """
    
  90.         The value of data will more than likely come from request.FILES. The
    
  91.         value of initial data will likely be a filename stored in the database.
    
  92.         Since its value is of no use to a FileField it is ignored.
    
  93.         """
    
  94.         f = FileField()
    
  95. 
    
  96.         # No file was uploaded and no initial data.
    
  97.         self.assertFalse(f.has_changed("", None))
    
  98. 
    
  99.         # A file was uploaded and no initial data.
    
  100.         self.assertTrue(
    
  101.             f.has_changed("", {"filename": "resume.txt", "content": "My resume"})
    
  102.         )
    
  103. 
    
  104.         # A file was not uploaded, but there is initial data
    
  105.         self.assertFalse(f.has_changed("resume.txt", None))
    
  106. 
    
  107.         # A file was uploaded and there is initial data (file identity is not dealt
    
  108.         # with here)
    
  109.         self.assertTrue(
    
  110.             f.has_changed(
    
  111.                 "resume.txt", {"filename": "resume.txt", "content": "My resume"}
    
  112.             )
    
  113.         )
    
  114. 
    
  115.     def test_disabled_has_changed(self):
    
  116.         f = FileField(disabled=True)
    
  117.         self.assertIs(f.has_changed("x", "y"), False)
    
  118. 
    
  119.     def test_file_picklable(self):
    
  120.         self.assertIsInstance(pickle.loads(pickle.dumps(FileField())), FileField)
    
  121. 
    
  122. 
    
  123. class MultipleFileInput(FileInput):
    
  124.     allow_multiple_selected = True
    
  125. 
    
  126. 
    
  127. class MultipleFileField(FileField):
    
  128.     def __init__(self, *args, **kwargs):
    
  129.         kwargs.setdefault("widget", MultipleFileInput())
    
  130.         super().__init__(*args, **kwargs)
    
  131. 
    
  132.     def clean(self, data, initial=None):
    
  133.         single_file_clean = super().clean
    
  134.         if isinstance(data, (list, tuple)):
    
  135.             result = [single_file_clean(d, initial) for d in data]
    
  136.         else:
    
  137.             result = single_file_clean(data, initial)
    
  138.         return result
    
  139. 
    
  140. 
    
  141. class MultipleFileFieldTest(SimpleTestCase):
    
  142.     def test_file_multiple(self):
    
  143.         f = MultipleFileField()
    
  144.         files = [
    
  145.             SimpleUploadedFile("name1", b"Content 1"),
    
  146.             SimpleUploadedFile("name2", b"Content 2"),
    
  147.         ]
    
  148.         self.assertEqual(f.clean(files), files)
    
  149. 
    
  150.     def test_file_multiple_empty(self):
    
  151.         f = MultipleFileField()
    
  152.         files = [
    
  153.             SimpleUploadedFile("empty", b""),
    
  154.             SimpleUploadedFile("nonempty", b"Some Content"),
    
  155.         ]
    
  156.         msg = "'The submitted file is empty.'"
    
  157.         with self.assertRaisesMessage(ValidationError, msg):
    
  158.             f.clean(files)
    
  159.         with self.assertRaisesMessage(ValidationError, msg):
    
  160.             f.clean(files[::-1])
    
  161. 
    
  162.     @unittest.skipUnless(HAS_PILLOW, "Pillow not installed")
    
  163.     def test_file_multiple_validation(self):
    
  164.         f = MultipleFileField(validators=[validate_image_file_extension])
    
  165. 
    
  166.         good_files = [
    
  167.             SimpleUploadedFile("image1.jpg", b"fake JPEG"),
    
  168.             SimpleUploadedFile("image2.png", b"faux image"),
    
  169.             SimpleUploadedFile("image3.bmp", b"fraudulent bitmap"),
    
  170.         ]
    
  171.         self.assertEqual(f.clean(good_files), good_files)
    
  172. 
    
  173.         evil_files = [
    
  174.             SimpleUploadedFile("image1.sh", b"#!/bin/bash -c 'echo pwned!'\n"),
    
  175.             SimpleUploadedFile("image2.png", b"faux image"),
    
  176.             SimpleUploadedFile("image3.jpg", b"fake JPEG"),
    
  177.         ]
    
  178. 
    
  179.         evil_rotations = (
    
  180.             evil_files[i:] + evil_files[:i]  # Rotate by i.
    
  181.             for i in range(len(evil_files))
    
  182.         )
    
  183.         msg = "File extension “sh” is not allowed. Allowed extensions are: "
    
  184.         for rotated_evil_files in evil_rotations:
    
  185.             with self.assertRaisesMessage(ValidationError, msg):
    
  186.                 f.clean(rotated_evil_files)