1
0
mirror of https://github.com/django/django.git synced 2024-12-22 09:05:43 +00:00

Fixed #35139 -- Prevented file read after ImageField is saved to storage.

This commit is contained in:
John Parton 2024-01-24 16:22:07 -06:00 committed by Sarah Boyce
parent 4971a9afe5
commit 9c5fe93349
5 changed files with 65 additions and 2 deletions

View File

@ -90,10 +90,13 @@ class FieldFile(File, AltersData):
# to further manipulate the underlying file, as well as update the
# associated model instance.
def _set_instance_attribute(self, name, content):
setattr(self.instance, self.field.attname, name)
def save(self, name, content, save=True):
name = self.field.generate_filename(self.instance, name)
self.name = self.storage.save(name, content, max_length=self.field.max_length)
setattr(self.instance, self.field.attname, self.name)
self._set_instance_attribute(self.name, content)
self._committed = True
# Save the object because it has changed, unless save is False
@ -391,6 +394,12 @@ class ImageFileDescriptor(FileDescriptor):
class ImageFieldFile(ImageFile, FieldFile):
def _set_instance_attribute(self, name, content):
setattr(self.instance, self.field.attname, content)
# Update the name in case generate_filename() or storage.save() changed
# it, but bypass the descriptor to avoid re-reading the file.
self.instance.__dict__[self.field.attname] = self.name
def delete(self, save=True):
# Clear the image dimensions cache
if hasattr(self, "_dimensions_cache"):

View File

@ -437,6 +437,11 @@ Miscellaneous
:class:`~django.core.exceptions.FieldError` when saving a file without a
``name``.
* ``ImageField.update_dimension_fields(force=True)`` is no longer called after
saving the image to storage. If your storage backend resizes images, the
``width_field`` and ``height_field`` will not match the width and height of
the image.
.. _deprecated-features-5.1:
Features deprecated in 5.1

View File

@ -13,6 +13,8 @@ from django.db.models.functions import Lower
from django.utils.functional import SimpleLazyObject
from django.utils.translation import gettext_lazy as _
from .storage import NoReadFileSystemStorage
try:
from PIL import Image
except ImportError:
@ -373,6 +375,21 @@ if Image:
width_field="headshot_width",
)
class PersonNoReadImage(models.Model):
"""
Model that defines an ImageField with a storage backend that does not
support reading.
"""
mugshot = models.ImageField(
upload_to="tests",
storage=NoReadFileSystemStorage(),
width_field="mugshot_width",
height_field="mugshot_height",
)
mugshot_width = models.IntegerField()
mugshot_height = models.IntegerField()
class CustomJSONDecoder(json.JSONDecoder):
def __init__(self, object_hook=None, *args, **kwargs):

View File

@ -0,0 +1,6 @@
from django.core.files.storage.filesystem import FileSystemStorage
class NoReadFileSystemStorage(FileSystemStorage):
def open(self, *args, **kwargs):
raise AssertionError("This storage class does not support reading.")

View File

@ -18,6 +18,7 @@ if Image:
from .models import (
Person,
PersonDimensionsFirst,
PersonNoReadImage,
PersonTwoImages,
PersonWithHeight,
PersonWithHeightAndWidth,
@ -30,7 +31,7 @@ else:
pass
PersonWithHeight = PersonWithHeightAndWidth = PersonDimensionsFirst = Person
PersonTwoImages = Person
PersonTwoImages = PersonNoReadImage = Person
class ImageFieldTestMixin(SerializeMixin):
@ -469,3 +470,28 @@ class TwoImageFieldTests(ImageFieldTestMixin, TestCase):
# Dimensions were recalculated, and hence file should have opened.
self.assertIs(p.mugshot.was_opened, True)
self.assertIs(p.headshot.was_opened, True)
@skipIf(Image is None, "Pillow is required to test ImageField")
class NoReadTests(ImageFieldTestMixin, TestCase):
def test_width_height_correct_name_mangling_correct(self):
instance1 = PersonNoReadImage()
instance1.mugshot.save("mug", self.file1)
self.assertEqual(instance1.mugshot_width, 4)
self.assertEqual(instance1.mugshot_height, 8)
instance1.save()
self.assertEqual(instance1.mugshot_width, 4)
self.assertEqual(instance1.mugshot_height, 8)
instance2 = PersonNoReadImage()
instance2.mugshot.save("mug", self.file1)
instance2.save()
self.assertNotEqual(instance1.mugshot.name, instance2.mugshot.name)
self.assertEqual(instance1.mugshot_width, instance2.mugshot_width)
self.assertEqual(instance1.mugshot_height, instance2.mugshot_height)