如何在Django ImageField中验证图像格式

11
我们的项目使用Python 2.7、PIL 1.1.7和Django 1.5.1。有一个ImageField可以处理许多图像格式,包括bmp、gif、ico、pnm、psd、tif和pcx。然而,要求仅允许png或jpg图像。如何实现?
更新:我知道可以验证文件扩展名和http Content-Type头。但是,这两种方法都不可靠。我的问题是是否有一种方法可以检查上传文件内容是否为png/jpg。

我知道你可以在表单设置中添加一些验证。我会告诉你详细信息。 - Zeinab Abbasimazar
请查看此答案和此链接 - Zeinab Abbasimazar
还有这个: django-vimage! - nik_m
3个回答

10
您没有说明是否使用Django表单来上传图像,我假设是的,因为验证是在表单字段中进行的。
您可以创建django.forms.fields.ImageField的子类来扩展to_python的功能。
Django目前在to_python中执行的文件类型检查如下:
Image.open(file).verify()

您的子类可能如下所示:

您的子类可能如下所示。

class DmitryImageField(ImageField):

    def to_python(self, data):
        f = super(DmitryImageField, self).to_python(data)
        if f is None:
            return None

        try:
            from PIL import Image
        except ImportError:
            import Image

        # We need to get a file object for PIL. 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:
            im = Image.open(file)
            if im.format not in ('BMP', 'PNG', 'JPEG'):
                raise ValidationError("Unsupport image type. Please upload bmp, png or jpeg")
        except ImportError:
            # Under PyPy, it is possible to import PIL. However, the underlying
            # _imaging C module isn't available, so an ImportError will be
            # raised. Catch and re-raise.
            raise
        except Exception: # Python Imaging Library doesn't recognize it as an image
            raise ValidationError(self.error_messages['invalid_image'])

        if hasattr(f, 'seek') and callable(f.seek):
            f.seek(0)
        return f

您可能会注意到这是 ImageField.to_python 中的大部分代码,并且可能更喜欢创建一个子类来代替使用 ImageField 而不是对其进行子类化并复制其大部分功能。在这种情况下,请确保在格式检查之前添加 im.verify()
编辑:我应该指出,我没有测试过这个子类。

谢谢!它对我有用(稍作修改后) - Dmitry
2
那个try...except块将捕获raise ValidationError异常,而且你可以更轻松地在clean_field方法中实现。 - nitely
这里是Django的ImageField发生的地方:https://github.com/django/django/blob/2ea3fb3e6386c43f124b542e92b817dbc227c76b/django/forms/fields.py#L634 - user3064538
截至Django 3.1.7版本,ImageField已注释返回值,并包含PIL对象的.image属性。这可用于避免重复大量的超级逻辑。 - Addison Klinke

1
您可能需要使用os来完成这个任务。来自Python文档。
os.path.splitext(path) 将路径名path拆分成一对(root,ext),使得root + ext == path,ext为空或以句点开头,最多包含一个句点。忽略basename上的前导句点;splitext('.cshrc')返回('.cshrc','')。版本2.6中的更改:早期版本可能会在唯一的句点是第一个字符时产生空根。
示例
import os
fileName, fileExtension = os.path.splitext('yourImage.png')

print fileName 
>>> "yourImage"

print fileExtension
>>> ".png"

当您将扩展名从文件名中分离出来后,只需使用简单的字符串比较来验证它是否是正确的格式。

2
谢谢您的回答。然而,我的问题是我是否可以检查上传文件的内容是否为png/jpg格式。检查文件扩展名并不可靠。例如,我可以将bmp文件重命名为png,然后上传。 - Dmitry
哦,所以不仅仅是一个扩展名,你要确保文件内容与扩展名所代表的类型相符。抱歉我误解了问题。 - Chris Hawkes
在大多数情况下,仅检查文件扩展名就足够了! - Mohammed Shareef C

1
你可以使用python-magic,它是libmagic的ctype包装器,file在Linux上使用该库。
从文档中可以看到:
>>> import magic
>>> magic.from_file("testdata/test.pdf")
'PDF document, version 1.2'
>>> magic.from_buffer(open("testdata/test.pdf").read(1024))
'PDF document, version 1.2'
>>> magic.from_file("testdata/test.pdf", mime=True)
'application/pdf'

然而,该方法仅查看 mime 信息。您仍然可以使用正确的 mime 上传非有效的PNG文件或在文件元数据中嵌入未经授权的数据。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接