如何限制FileField
以优雅的方式仅在服务器端接受某种类型的文件(视频,音频,pdf等)?
Answers:
一种非常简单的方法是使用自定义验证器。
在您的应用程序中validators.py
:
def validate_file_extension(value):
import os
from django.core.exceptions import ValidationError
ext = os.path.splitext(value.name)[1] # [0] returns path+filename
valid_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.png', '.xlsx', '.xls']
if not ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.')
然后在您的models.py
:
from .validators import validate_file_extension
...并在表单字段中使用验证器:
class Document(models.Model):
file = models.FileField(upload_to="documents/%Y/%m/%d", validators=[validate_file_extension])
另请参阅:如何限制具有FileFields的ModelForms的文件上载中的文件类型?。
警告
用于保护代码执行环境免受恶意媒体文件的侵害
- 使用Exif库正确验证媒体文件。
- 将媒体文件与应用程序代码执行环境分开
- 如果可能,请使用S3,GCS,Minio或类似的解决方案
- 在客户端上加载媒体文件时,请使用客户端本机方法(例如,如果您在浏览器中非安全地加载媒体文件,则可能会导致执行“精心制作的” JavaScript代码)
Django版本1.11
新增了一个FileExtensionValidator
用于模型字段的文档,文档位于此处:https : //docs.djangoproject.com/en/dev/ref/validators/#fileextensionvalidator。
有关如何验证文件扩展名的示例:
from django.core.validators import FileExtensionValidator
from django.db import models
class MyModel(models.Model):
pdf_file = models.FileField(upload_to='foo/',
validators=[FileExtensionValidator(allowed_extensions=['pdf'])])
请注意,此方法不安全。Django文档的引文:
不要依靠验证文件扩展名来确定文件的类型。无论文件包含什么数据,都可以将其重命名为具有任何扩展名的文件。
还有一个新的validate_image_file_extension
(https://docs.djangoproject.com/en/dev/ref/validators/#validate-image-file-extension)用于验证图像扩展名(使用Pillow)。
allowed_extensions
由于某种原因未添加文件,文件也已上传。使用Django Rest Framework。
有一个Django片段可以做到这一点:
import os
from django import forms
class ExtFileField(forms.FileField):
"""
Same as forms.FileField, but you can specify a file extension whitelist.
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>>
>>> t = ExtFileField(ext_whitelist=(".pdf", ".txt"))
>>>
>>> t.clean(SimpleUploadedFile('filename.pdf', 'Some File Content'))
>>> t.clean(SimpleUploadedFile('filename.txt', 'Some File Content'))
>>>
>>> t.clean(SimpleUploadedFile('filename.exe', 'Some File Content'))
Traceback (most recent call last):
...
ValidationError: [u'Not allowed filetype!']
"""
def __init__(self, *args, **kwargs):
ext_whitelist = kwargs.pop("ext_whitelist")
self.ext_whitelist = [i.lower() for i in ext_whitelist]
super(ExtFileField, self).__init__(*args, **kwargs)
def clean(self, *args, **kwargs):
data = super(ExtFileField, self).clean(*args, **kwargs)
filename = data.name
ext = os.path.splitext(filename)[1]
ext = ext.lower()
if ext not in self.ext_whitelist:
raise forms.ValidationError("Not allowed filetype!")
#-------------------------------------------------------------------------
if __name__ == "__main__":
import doctest, datetime
doctest.testmod()
第一。在应用程序内创建一个名为formatChecker.py的文件,在该文件中,您的模型具有要接受某种文件类型的FileField。
这是您的formatChecker.py:
from django.db.models import FileField
from django.forms import forms
from django.template.defaultfilters import filesizeformat
from django.utils.translation import ugettext_lazy as _
class ContentTypeRestrictedFileField(FileField):
"""
Same as FileField, but you can specify:
* content_types - list containing allowed content_types. Example: ['application/pdf', 'image/jpeg']
* max_upload_size - a number indicating the maximum file size allowed for upload.
2.5MB - 2621440
5MB - 5242880
10MB - 10485760
20MB - 20971520
50MB - 5242880
100MB 104857600
250MB - 214958080
500MB - 429916160
"""
def __init__(self, *args, **kwargs):
self.content_types = kwargs.pop("content_types")
self.max_upload_size = kwargs.pop("max_upload_size")
super(ContentTypeRestrictedFileField, self).__init__(*args, **kwargs)
def clean(self, *args, **kwargs):
data = super(ContentTypeRestrictedFileField, self).clean(*args, **kwargs)
file = data.file
try:
content_type = file.content_type
if content_type in self.content_types:
if file._size > self.max_upload_size:
raise forms.ValidationError(_('Please keep filesize under %s. Current filesize %s') % (filesizeformat(self.max_upload_size), filesizeformat(file._size)))
else:
raise forms.ValidationError(_('Filetype not supported.'))
except AttributeError:
pass
return data
第二。在您的models.py中,添加以下内容:
from formatChecker import ContentTypeRestrictedFileField
然后,使用此“ ContentTypeRestrictedFileField”代替“ FileField”。
例:
class Stuff(models.Model):
title = models.CharField(max_length=245)
handout = ContentTypeRestrictedFileField(upload_to='uploads/', content_types=['video/x-msvideo', 'application/pdf', 'video/mp4', 'audio/mpeg', ],max_upload_size=5242880,blank=True, null=True)
这些就是您只想在FileField中接受某种文件类型时必须要做的事情。
一些人建议使用python-magic来验证文件实际上是否为您期望接收的类型。可以将其合并到validator
接受的答案中:
import os
import magic
from django.core.exceptions import ValidationError
def validate_is_pdf(file):
valid_mime_types = ['application/pdf']
file_mime_type = magic.from_buffer(file.read(1024), mime=True)
if file_mime_type not in valid_mime_types:
raise ValidationError('Unsupported file type.')
valid_file_extensions = ['.pdf']
ext = os.path.splitext(file.name)[1]
if ext.lower() not in valid_file_extensions:
raise ValidationError('Unacceptable file extension.')
此示例仅验证pdf,但是可以将任意数量的mime类型和文件扩展名添加到数组。
假设您保存了以上内容,则validators.py
可以将其合并到模型中,如下所示:
from myapp.validators import validate_is_pdf
class PdfFile(models.Model):
file = models.FileField(upload_to='pdfs/', validators=(validate_is_pdf,))
我认为您最适合使用Dominic Rodger在他的答案中指定的ExtFileField和Daniel Quinn提到的python-magic是最好的方法。如果有人足够聪明,可以更改扩展名,至少您会用标题将其捕获。
另外,我将通过一些额外的行为来扩展本课程。
class ContentTypeRestrictedFileField(forms.FileField):
...
widget = None
...
def __init__(self, *args, **kwargs):
...
self.widget = forms.ClearableFileInput(attrs={'accept':kwargs.pop('accept', None)})
super(ContentTypeRestrictedFileField, self).__init__(*args, **kwargs)
当我们使用param accept =“。pdf,.txt”创建实例时,在默认情况下以文件结构弹出窗口中,我们将看到带有扩展名的文件。
在检查了可接受的答案之后,我决定分享一个基于Django文档的技巧。已经有一个用于验证文件扩展名的验证器。您无需重写自己的自定义函数即可验证是否允许文件扩展名。
https://docs.djangoproject.com/zh-CN/3.0/ref/validators/#fileextensionvalidator
警告
不要依靠验证文件扩展名来确定文件的类型。无论文件包含什么数据,都可以将其重命名为具有任何扩展名的文件。