【问题标题】:ImageField not validatingImageField 未验证
【发布时间】:2019-12-14 19:33:20
【问题描述】:

为什么 Django 的 ImageField 没有在这里抛出验证错误?

    # field in model
    image_mobile = ImageField(
        upload_to='static/images/',
        blank=True,
        null=True
    )

    # in test
    from django.core.files.uploadedfile import SimpleUploadedFile

    attachment = SimpleUploadedFile("file.mp4", b"file_content", content_type="text/plain")
    obj.image_mobile = attachment
    obj.save()
    self.assertEqual(obj.image_mobile, '')

输出这个:

AssertionError: <ImageFieldFile: static/images/file_7wanB5P.mp4> != ''

来自文档:

从 FileField 继承所有属性和方法,但也验证上传的对象是有效的图像。

【问题讨论】:

  • Django 不会急切地验证。您应该调用obj.full_clean() 来运行所有验证。这主要是出于性能原因。
  • @WillemVanOnsem 但是当你调用save()时它不应该验证它吗?
  • 不,这正是重点。出于性能原因,验证器不使用.save() 运行。但是,ModelForm 将调用清理函数(当然,除非您覆盖它)。
  • @WillemVanOnsem full_clean() 什么都不做。仍然没有验证错误。

标签: python django


【解决方案1】:

看起来这取决于 PIL 库。 Django 使用它来获取图像尺寸,它似乎是 ImageField 的唯一验证。 它获取文件的前 1024 个字节并读取元数据,并且可能 mp4 和 jpeg 在那里有类似的数据。所以这不是最可靠的检查方式

 try:
    # Most of the time Pillow only needs a small chunk to parse the image
    # and get the dimensions, but with some TIFF files Pillow needs to
    # parse the whole file.
    chunk_size = 1024
    while 1:
        data = file.read(chunk_size)
        if not data:
            break
        try:
            p.feed(data)
        except zlib.error as e:
            # ignore zlib complaining on truncated stream, just feed more
            # data to parser (ticket #19457).
            if e.args[0].startswith("Error -5"):
                pass
            else:
                raise
        except struct.error:
            # Ignore PIL failing on a too short buffer when reads return
            # less bytes than expected. Skip and feed more data to the
            # parser (ticket #24544).
            pass
        except RuntimeError:
            # e.g. "RuntimeError: could not create decoder object" for
            # WebP files. A different chunk_size may work.
            pass
        if p.image:
            return p.image.size
        chunk_size *= 2
    return (None, None)
finally:
    if close:
        file.close()
    else:
        file.seek(file_pos)

【讨论】:

    【解决方案2】:
    def to_python(self, data):
            """
            Check that the file-upload field data contains a valid image (GIF, JPG,
            PNG, etc. -- whatever Pillow supports).
            """
            f = super().to_python(data)
            if f is None:
                return None
    
            from PIL import Image
    
            # We need to get a file object for Pillow. We might have a path or we might
            # have to read the data into memory.
            if hasattr(data, 'temporary_file_path'):
                file = data.temporary_file_path()
            else:
                if hasattr(data, 'read'):
                    file = BytesIO(data.read())
                else:
                    file = BytesIO(data['content'])
    
            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.verify()
    
                # Annotating so subclasses can reuse it for their own validation
                f.image = image
                # Pillow doesn't detect the MIME type of all formats. In those
                # cases, content_type will be None.
                f.content_type = Image.MIME.get(image.format)
            except Exception as exc:
                # Pillow doesn't recognize it as an image.
                raise ValidationError(
                    self.error_messages['invalid_image'],
                    code='invalid_image',
                ) from exc
            if hasattr(f, 'seek') and callable(f.seek):
                f.seek(0)
            return f
    

    如果您查看文档,这是运行的代码

    https://pillow.readthedocs.io/en/5.1.x/reference/Image.html?highlight=verify#PIL.Image.Image.verify

    Image.verify()

    验证文件的内容。对于从文件中读取的数据,此方法会尝试确定文件是否损坏,而无需实际解码图像数据。如果此方法发现任何问题,它会引发适当的异常。如果使用此方法后需要加载图片,必须重新打开图片文件。

    所以基本上它是在尝试确定文件是否损坏。也许它正在验证您的mp4 文件,这就是为什么没有例外。如果您想要更严格的验证,您需要使用magic 或其他一些库来确定文件类型。请参阅下面的 SO 线程以获取相同的内容

    How does one use magic to verify file type in a Django form clean method?

    Django: file field validation in model using python-magic

    Django: Validate file type of uploaded file

    https://timmyomahony.com/blog/upload-and-validate-image-from-url-in-django/

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-26
      • 1970-01-01
      • 2011-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多