如何使用 FileFields 限制 ModelForms 文件上传的文件类型?

Posted

技术标签:

【中文标题】如何使用 FileFields 限制 ModelForms 文件上传的文件类型?【英文标题】:How to limit file types on file uploads for ModelForms with FileFields? 【发布时间】:2011-09-21 14:18:53 【问题描述】:

我的目标是将 Django ModelForm 上的 FileField 限制为 PDF 和 Word 文档。我用谷歌搜索的答案都是关于创建一个单独的文件处理程序,但我不确定如何在 ModelForm 的上下文中这样做。 settings.py 中是否有我可以用来限制上传文件类型的设置?

【问题讨论】:

【参考方案1】:

创建一个验证方法,如:

def validate_file_extension(value):
    if not value.name.endswith('.pdf'):
        raise ValidationError(u'Error message')

并将其包含在 FileField 验证器中,如下所示:

actual_file = models.FileField(upload_to='uploaded_files', validators=[validate_file_extension])

此外,您应该在 setting.py 上创建一个列表并对其进行迭代,而不是手动设置模型允许的扩展。

编辑

过滤多个文件:

def validate_file_extension(value):
  import os
  ext = os.path.splitext(value.name)[1]
  valid_extensions = ['.pdf','.doc','.docx']
  if not ext in valid_extensions:
    raise ValidationError(u'File not supported!')

【讨论】:

如何在setting.py中创建一个列表并对其进行迭代? 这个答案是一个巨大的安全漏洞。用户可以在文件名下添加 anything 并且可能具有有效的扩展名。 在模型/表单上使用 `clean_[your field]' 方法不是最“django-esque”的方式吗?为什么人们更喜欢这种方法而不是 Brandon 帖子中建议的方法?【参考方案2】:

使用文件名的扩展名进行验证不是一种一致的方式。例如,我可以将图片.jpg 重命名为图片.pdf,并且验证不会引发错误。

更好的方法是检查文件的 content_type。

验证方法

def validate_file_extension(value):
    if value.file.content_type != 'application/pdf':
        raise ValidationError(u'Error message')

用法

actual_file = models.FileField(upload_to='uploaded_files', validators=[validate_file_extension])

【讨论】:

content_type-field 也是如此,因为它取自请求,用户也可以选择。两种方法 - extension 和 content_type - 都打开了简单的攻击向量。唯一半确定的方法是在上传后检查文件 mimetype 本身 - 即使这样也几乎没有保存。【参考方案3】:

一种更简单的方法是在您的表单中如下所示

file = forms.FileField(widget=forms.FileInput(attrs='accept':'application/pdf'))

【讨论】:

这对前端的可用性非常有用,但实际上并不能控制上传的内容。【参考方案4】:

为了更通用的用途,我写了一个小类ExtensionValidator,它扩展了Django 的内置RegexValidator。它接受单个或多个扩展名,以及可选的自定义错误消息。

class ExtensionValidator(RegexValidator):
    def __init__(self, extensions, message=None):
        if not hasattr(extensions, '__iter__'):
            extensions = [extensions]
        regex = '\.(%s)$' % '|'.join(extensions)
        if message is None:
            message = 'File type not supported. Accepted types are: %s.' % ', '.join(extensions)
        super(ExtensionValidator, self).__init__(regex, message)

    def __call__(self, value):
        super(ExtensionValidator, self).__call__(value.name)

现在您可以定义一个与字段内联的验证器,例如:

my_file = models.FileField('My file', validators=[ExtensionValidator(['pdf', 'doc', 'docx'])])

【讨论】:

这太棒了——我唯一不能工作的部分是if message is None: message = 'File type not supported. Accepted types are: %s.' % ', '.join(extensions)——该功能有效,但我没有收到错误消息。 Prolly 我做错了什么! 我被建议不要使用自定义类,所以我不再追求这个了。不过感谢您的评论。【参考方案5】:

我在这些方面使用了一些东西(注意,“pip install filemagic”是必需的......):

import magic
def validate_mime_type(value):
    supported_types=['application/pdf',]
    with magic.Magic(flags=magic.MAGIC_MIME_TYPE) as m:
        mime_type=m.id_buffer(value.file.read(1024))
        value.file.seek(0)
    if mime_type not in supported_types:
        raise ValidationError(u'Unsupported file type.')

您可能还可以将前面的示例合并到其中 - 例如还检查扩展/上传类型(作为主要检查可能比魔术更快。)这仍然不是万无一失的 - 但它更好,因为它依赖更多关于数据in文件,而不是浏览器提供的标题。

注意:这是一个验证器函数,您希望将其添加到 FileField 模型的验证器列表中。

【讨论】:

我有一个问题,因为在我的网站上,有些人有时会尝试上传高达 200MB 的文件,而我的服务器没有太多内存,所以当它不能时它会抛出 500分配内存。我们是一家小型初创公司,并且正在尽可能避免花费更多的钱,所以我想知道是否有一种方法可以检查文件,同时将其分成单独的部分,或者无需在服务器中重新分配整个文件。 TY :)【参考方案6】:

从 1.11 开始,Django 有一个 FileExtensionValidator 用于此目的:

class SomeDocument(Model):
    document = models.FileFiled(validators=[
        FileExtensionValidator(allowed_extensions=['pdf', 'doc'])])

正如@savp 提到的,您还需要自定义小部件,以便用户首先无法选择不合适的文件:

class SomeDocumentForm(ModelForm):
    class Meta:
        model = SomeDocument
        widgets = 'document': FileInput(attrs='accept': 'application/pdf,application/msword')
        fields = '__all__'

您可能需要摆弄accept 才能准确确定您的目的需要哪些 MIME 类型。

正如其他人所提到的,这一切都不会阻止某人将 badstuff.exe 重命名为 innocent.pdf 并通过您的表单上传 - 您仍然需要安全地处理上传的文件。获得内容后,python-magic 库之类的东西可以帮助您确定实际的文件类型。

【讨论】:

【参考方案7】:

我发现检查文件类型的最佳方法是检查其内容类型。我还要补充一点,进行类型检查的最佳位置之一是表单验证。我将有一个表单和验证如下:

class UploadFileForm(forms.Form):
    file = forms.FileField()

    def clean_file(self):
        data = self.cleaned_data['file']

        # check if the content type is what we expect
        content_type = data.content_type
        if content_type == 'application/pdf':
            return data
        else:
            raise ValidationError(_('Invalid content type'))

以下文档链接可能会有所帮助: https://docs.djangoproject.com/en/3.1/ref/files/uploads/ 和 https://docs.djangoproject.com/en/3.1/ref/forms/validation/

【讨论】:

【参考方案8】:

我通过在 ModelForm 上使用 clean_[your_field] 方法来处理这个问题。您可以在 settings.py 中设置可接受的文件扩展名列表以在您的 clean 方法中进行检查,但是 settings.py 没有内置任何内容来限制上传类型。

例如,Django-Filebrowser 采用在 settings.py 中创建可接受文件扩展名列表的方法。

希望对你有所帮助。

【讨论】:

请记住,此解决方案几乎不会阻止某人将 .doc 或 .pdf 添加到文件末尾以上传文件。检查扩展名 - 通常 - 并不是保证文件属于特定类型的好方法。

以上是关于如何使用 FileFields 限制 ModelForms 文件上传的文件类型?的主要内容,如果未能解决你的问题,请参考以下文章

重新排序模型表单字段

在SQLAlchemy ORM中动态变更表名

:用户账户)

Django表单在单个HTML中多次使用,避免重复的id

Django“提交的文件是空的”

Codeigniter - 只能在 autoload.php 中加载模型