如何使用魔法来验证 Django 表单清理方法中的文件类型?

Posted

技术标签:

【中文标题】如何使用魔法来验证 Django 表单清理方法中的文件类型?【英文标题】:How does one use magic to verify file type in a Django form clean method? 【发布时间】:2012-01-28 15:55:53 【问题描述】:

我在 Django 中使用 FileField 编写了一个电子邮件表单类。我想通过检查其 mimetype 来检查上传文件的类型。随后,我想将文件类型限制为 pdf、word 和打开的 office 文档。

为此,我已经安装了 python-magic 并希望按照 python-magic 的规范检查文件类型如下:

mime = magic.Magic(mime=True)
file_mime_type = mime.from_file('address/of/file.txt')

但是,最近上传的文件在我的服务器上缺少地址。我也不知道任何类似于“from_file_content”的 mime 对象的方法在给定文件内容的情况下检查 mime 类型。

使用magic验证Django表单中上传文件的文件类型的有效方法是什么?

【问题讨论】:

【参考方案1】:

Stan 用缓冲区描述了很好的变体。不幸的是,这种方法的弱点是将文件读取到内存中。另一种选择是使用临时存储文件:

import tempfile
import magic
with tempfile.NamedTemporaryFile() as tmp:
    for chunk in form.cleaned_data['file'].chunks():
        tmp.write(chunk)
    print(magic.from_file(tmp.name, mime=True))

另外,您可能需要检查文件大小:

if form.cleaned_data['file'].size < ...:
    print(magic.from_buffer(form.cleaned_data['file'].read()))
else:
    # store to disk (the code above)

Additionally:

在命名的临时文件仍处于打开状态时,该名称是否可用于第二次打开文件,因平台而异(在 Unix 上可以这样使用;在 Windows NT 或更高版本上不能)。

所以你可能想像so一样处理它:

import os
tmp = tempfile.NamedTemporaryFile(delete=False)
try:
    for chunk in form.cleaned_data['file'].chunks():
        tmp.write(chunk)
    print(magic.from_file(tmp.name, mime=True))
finally:
    os.unlink(tmp.name)
    tmp.close()

另外,你可能想在read() 之后seek(0)

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

Where uploaded data is stored

【讨论】:

谢谢,当我尝试使用cleaned_data 时,Django 注意到文件/tmp/filename.doc 是未定义的。你知道为什么吗? 以某种方式或另一种方式,您的文件将被加载到内存中。而且我更喜欢避免直接使用临时路径。 @Stan 当服务器因内存不足而崩溃时,您是否遇到过问题?我认为这就是程序员试图避免将文件读入内存的原因,这就是为什么 django UploadedFilechunks() 方法 @AlexeySavanovich : m.from_buffer(request.FILES['my_file_field'].multiple_chunks()) 在这种情况下应该可以工作。 非常感谢!事实上,multiple_chunks() 似乎可以做到这一点,而无需一次上传整个文件进行验证。【参考方案2】:

为什么不在你的观点中尝试这样的事情:

m = magic.Magic()
m.from_buffer(request.FILES['my_file_field'].read())

如果django.forms.Form 真的不行,则使用request.FILES 代替form.cleaned_data

【讨论】:

第二个代码不正确。 multiple_chunks() not 返回块,它返回一个布尔值:文件是否大到可以分割成块。 docs.djangoproject.com/en/1.5/topics/http/file-uploads/… from_buffer 需要一个字符串缓冲区,而不是一个迭代器。 AFAIK 您的新代码将失败并出现 AttributeError,因为迭代器没有 len()。除了手动获取第一个块之外,我在这里没有看到任何好的解决方案。【参考方案3】:
mime = magic.Magic(mime=True)

attachment = form.cleaned_data['attachment']

if hasattr(attachment, 'temporary_file_path'):
    # file is temporary on the disk, so we can get full path of it.
    mime_type = mime.from_file(attachment.temporary_file_path())
else:
    # file is on the memory
    mime_type = mime.from_buffer(attachment.read())

另外,你可能想在read()之后seek(0)

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

来自Django code 的示例。在验证期间对图像字段执行。

【讨论】:

【参考方案4】:

您可以使用django-safe-filefield 包来验证上传的文件扩展名是否匹配它的 MIME 类型。

from safe_filefield.forms import SafeFileField

class MyForm(forms.Form):

    attachment = SafeFileField(
        allowed_extensions=('xls', 'xlsx', 'csv')
    )

【讨论】:

【参考方案5】:

如果您正在处理文件上传并且只关心图片, Django 会为你设置content_type(或者更确切地说是为它自己?):

from django.forms import ModelForm
from django.core.files import File
from django.db import models
class MyPhoto(models.Model):
    photo = models.ImageField(upload_to=photo_upload_to, max_length=1000)
class MyForm(ModelForm):
    class Meta:
        model = MyPhoto
        fields = ['photo']
photo = MyPhoto.objects.first()
photo = File(open('1.jpeg', 'rb'))
form = MyForm(files='photo': photo)
if form.is_valid():
    print(form.instance.photo.file.content_type)

它不依赖于用户提供的内容类型。但 django.db.models.fields.files.FieldFile.file 是一个无证 property.

其实最初content_type是从request设置的,但是当 表单得到验证,值为updated。

关于非图像,request.FILES['name'].read() 对我来说似乎没问题。 首先,这就是Django 所做的。二、默认大于2.5Mb的文件 在disk 上是stored。所以让我指出other 的答案 在这里。


为了好奇,这里是导致更新的堆栈跟踪 content_type:

django.forms.forms.BaseForm.is_valid:self.errors django.forms.forms.BaseForm.errors:self.full_clean() django.forms.forms.BaseForm.full_clean: self._clean_fields() django.forms.forms.BaseForm._clean_fiels:field.clean() django.forms.fields.FileField.clean:super().clean() django.forms.fields.Field.clean:self.to_python() django.forms.fields.ImageField.to_python

【讨论】:

以上是关于如何使用魔法来验证 Django 表单清理方法中的文件类型?的主要内容,如果未能解决你的问题,请参考以下文章

Django:清理和验证相互依赖的表格

Django Modelform - 它没有验证,为什么?

在 Django 中清理表单数据

Django:模板中的 Crispy 表单验证错误

如果空值传递给Django中的表单字段,如何验证表单?

Django Modelform - 它没有验证,为啥?