Django 文件上传大小限制

Posted

技术标签:

【中文标题】Django 文件上传大小限制【英文标题】:Django File upload size limit 【发布时间】:2011-01-29 04:02:40 【问题描述】:

我的 django 应用中有一个表单,用户可以在其中上传文件。 如何设置上传文件大小的限制,以便如果用户上传的文件大于我的限制,表单将无效并引发错误?

【问题讨论】:

类似问题的答案:***.com/questions/2894914/… @DaveGallagher:使用上传处理程序不会向用户显示漂亮的错误消息,它只会断开连接。 Max image size on file upload的可能重复 【参考方案1】:

此代码可能会有所帮助:

# Add to your settings file
CONTENT_TYPES = ['image', 'video']
# 2.5MB - 2621440
# 5MB - 5242880
# 10MB - 10485760
# 20MB - 20971520
# 50MB - 5242880
# 100MB 104857600
# 250MB - 214958080
# 500MB - 429916160
MAX_UPLOAD_SIZE = "5242880"

#Add to a form containing a FileField and change the field names accordingly.
from django.template.defaultfilters import filesizeformat
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
def clean_content(self):
    content = self.cleaned_data['content']
    content_type = content.content_type.split('/')[0]
    if content_type in settings.CONTENT_TYPES:
        if content._size > settings.MAX_UPLOAD_SIZE:
            raise forms.ValidationError(_('Please keep filesize under %s. Current filesize %s') % (filesizeformat(settings.MAX_UPLOAD_SIZE), filesizeformat(content._size)))
    else:
        raise forms.ValidationError(_('File type is not supported'))
    return content

取自:Django Snippets - Validate by file content type and size

【讨论】:

你知道为什么使用这样的值,看起来像 10 *(2 的幂)吗? @AnthonyLozano 实际上,MiB 总是总是 1048576 字节,它不是十的幂。至于MB,它是模棱两可的,如果你遵循IEC标准,它可能意味着1000000字节,如果你使用Windows等,它可能意味着1048576字节。您链接到的***文章就是证据。 很确定你在“5242880”的末尾丢失了一个 0。应该是“52428800” 在 Django 1.10 中使用 content.size(无下划线) 不要将MAX_UPLOAD_SIZE 设置为字符串。它应该是一个数字——此代码将允许任何大小的上传,因为无法达到第一个 ValidationError。【参考方案2】:

我相信 django 表单只有在文件完全上传后才会收到文件。这就是为什么如果有人上传 2Gb 文件,你最好使用网络服务器即时检查大小。

请参阅此mail thread 了解更多信息。

【讨论】:

我同意你的观点,但就我而言,我需要限制在 Django 中。 在撰写本文时(2 年前),django 会简单地使用大量文件上传进行 DoS。现在情况有所不同,根据限制的目的,它可以采取任何一种方式【参考方案3】:

你可以使用这个 sn -p formatChecker。它的作用是

它允许您指定允许上传的文件格式。

并允许您设置要上传文件的文件大小限制。

首先。在应用程序中创建一个名为 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", 0)

        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

那么不要使用“FileField”,而是使用这个“ContentTypeRestrictedFileField”。

例子:

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)

您可以将“max_upload_size”的值更改为您想要的文件大小限制。您还可以将“content_types”列表中的值更改为您想要接受的文件类型。

【讨论】:

多么被低估的答案啊!比经过验证的更完整且略胜一筹。 给出错误 __init__() 在创建数据库时得到了一个意外的关键字参数 content_types 第 23 行,在 init self.content_types = kwargs.pop("content_types") KeyError: 'content_types' - 一直给我这个错误 这确实应该内置到 Django 中。 遇到这种情况的人应该记住文档kwargs.pop应该有一个默认值。改成这个self.content_types = kwargs.pop("content_types", []) self.max_upload_size = kwargs.pop("max_upload_size", [])【参考方案4】:

只是对包含在此线程中的 sn-p 的简短说明:

看看这个sn-p: http://www.djangosnippets.org/snippets/1303/

它非常有用,但是它包含一些小错误。更健壮的代码应如下所示:

# Add to your settings file
CONTENT_TYPES = ['image', 'video']
# 2.5MB - 2621440
# 5MB - 5242880
# 10MB - 10485760
# 20MB - 20971520
# 50MB - 5242880
# 100MB - 104857600
# 250MB - 214958080
# 500MB - 429916160
MAX_UPLOAD_SIZE = "5242880"

#Add to a form containing a FileField and change the field names accordingly.
from django.template.defaultfilters import filesizeformat
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
def clean_content(self):
    if content != None:
        content = self.cleaned_data['content']
        content_type = content.content_type.split('/')[0]
        if content_type in settings.CONTENT_TYPES:
            if content._size > int(settings.MAX_UPLOAD_SIZE):
                raise forms.ValidationError(_(u'Please keep filesize under %s. Current filesize %s') % (filesizeformat(settings.MAX_UPLOAD_SIZE), filesizeformat(content._size)))
        else:
            raise forms.ValidationError(_(u'File type is not supported'))
        return content

只有一些改进:

首先,我检测文件字段是否为空(无) - 没有它,Django 将在 Web 浏览器中抛出异常。

接下来是 int(settings.MAX_UPLOAD_SIZE) 中的类型转换,因为该设置值是一个字符串。字符串不能用于与数字进行比较。

最后但同样重要的是,ValidationError 函数中的 unicode 'u' 前缀。

非常感谢这个sn-p!

【讨论】:

我正在使用类似的方法,只是使用 python-magic 而不是读取 django content_type 字段,但我遇到了一个问题。我接受 pdf 文件(mime 类型“应用程序/pdf”)。问题是有时 mime 类型似乎是“应用程序/八位字节流”,即使对于 pdf 文件也是如此。我不想将该 mime 类型添加到我接受的类型列表中,否则其他文档类型也会被接受(例如 excel)。有人知道如何解决这个问题吗? 尝试使用文件扩展名检查 '.pdf' 而不是 content_type 检查。这同时更简单、更健壮。【参考方案5】:

如果有人正在寻找@angelo 解决方案的FileField 变体形式,那么就是这里

from django import forms
from django.template.defaultfilters import filesizeformat
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError

class RestrictedFileField(forms.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(RestrictedFileField, self).__init__(*args, **kwargs)

    def clean(self, data, initial=None):
        file = super(RestrictedFileField, self).clean(data, initial)

        try:
            content_type = file.content_type
            if content_type in self.content_types:
                if file._size > self.max_upload_size:
                    raise ValidationError(_('Please keep filesize under %s. Current filesize %s') % (
                        filesizeformat(self.max_upload_size), filesizeformat(file._size)))
            else:
                raise ValidationError(_('Filetype not supported.'))
        except AttributeError:
            pass

        return data

然后创建一个表单为

class ImageUploadForm(forms.Form):
    """Image upload form."""
    db_image = RestrictedFileField(content_types=['image/png', 'image/jpeg'],
                                   max_upload_size=5242880)

【讨论】:

【参考方案6】:

另一种解决方案是使用验证器

from django.core.exceptions import ValidationError

def file_size(value): # add this to some file where you can import it from
    limit = 2 * 1024 * 1024
    if value.size > limit:
        raise ValidationError('File too large. Size should not exceed 2 MiB.')

然后在带有文件字段的表单中,您有类似这样的内容

image = forms.FileField(required=False, validators=[file_size])

【讨论】:

这是我的最爱,因为其他人访问私有变量 _size 而这个没有。 小心!仅当您使用表单保存数据时才会调用验证,否则您必须手动调用验证,例如。 'instance.full_clean()' 保存到数据库之前。 @Hemant_Negi 我相信这个问题确实表明文件是通过表单接收的,所以不用担心。 这对我不起作用,验证器没有被调用 @ifedapoolarewaju 如果用户必须上传多个文件,这是否可行?【参考方案7】:

服务器端

我最喜欢的服务器端检查文件是否太大的方法是使用验证器ifedapo olarewaju's answer。

客户端

仅进行服务器端验证的问题在于,验证仅在上传完成后进行。想象一下,上传一个巨大的文件,等待很长时间,结果却被告知文件太大。如果浏览器能提前告诉我文件太大不是更好吗?

嗯,这个客户端有一种方法,使用HTML5 File API

这是所需的 javascript(取决于 JQuery):

$("form").submit(function() 
  if (window.File && window.FileReader && window.FileList && window.Blob) 
    var file = $('#id_file')[0].files[0];

    if (file && file.size > 2 * 1024 * 1024) 
      alert("File " + file.name + " of type " + file.type + " is too big");
      return false;
    
  
);

当然,您仍然需要服务器端验证,以防止恶意输入和未启用 Javascript 的用户。

【讨论】:

感谢您的补充,双向进行是有意义的。以上对我有用。【参考方案8】:
from django.forms.utils import ErrorList

class Mymodelform(forms.ModelForm):
    class Meta:
        model = Mymodel
        fields = '__all__'

    def clean(self):image = self.cleaned_data.get('image')
        # 5MB - 5242880
        if org_image._size > 5242880:            
            self._errors["image"] = ErrorList([u"Image too heavy."])

【讨论】:

【参考方案9】:

另一个不用硬编码最大文件大小的验证器的优雅解决方案是使用基于类的验证器:

from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator
from django.utils.translation import ugettext as _

class MaxSizeValidator(MaxValueValidator):
message = _('The file exceed the maximum size of %(limit_value)s MB.')

def __call__(self, value):
    # get the file size as cleaned value
    cleaned = self.clean(value.size)
    params = 'limit_value': self.limit_value, 'show_value': cleaned, 'value': value
    if self.compare(cleaned, self.limit_value * 1024 * 1024): # convert limit_value from MB to Bytes
        raise ValidationError(self.message, code=self.code, params=params)

然后,在您的模型中,例如:

image = models.ImageField(verbose_name='Image', upload_to='images/', validators=[MaxSizeValidator(1)])

编辑:here 是MaxValueValidator 的源代码,以了解有关此作品的更多详细信息。

【讨论】:

【参考方案10】:

您可以扩展 Django 的 MaxValueValidator 并覆盖它的 clean() 以返回文件大小:

from django.core.validators import MaxValueValidator
from django.utils.deconstruct import deconstructible
from django.utils.translation import ugettext_lazy as _


@deconstructible
class MaxKibFileSizeValidator(MaxValueValidator):
    message = _('File size %(show_value)d KiB exceeds maximum file size of %(limit_value)d KiB.')

    def clean(self, filefield) -> float:
        return filefield.file.size / 1024

【讨论】:

【参考方案11】:

我要感谢所有为此问题提供各种不同解决方案的人。我有额外的要求,我想(a)在提交之前在 JavaScript 中进行文件长度验证,(b)在forms.py 中进行第二道服务器内验证,(c)保留所有硬编码位,包括 end -forms.py 中的用户消息,(d) 我希望我的views.py 具有尽可能少的文件相关代码,并且 (d) 将文件信息上传到我的数据库,因为这些是我只想服务的小文件登录用户并在删除 Meal 模型项时立即删除(即,仅将它们放入 /media/ 是不够的)。

首先是模型:

class Meal(models.Model) :
    title = models.CharField(max_length=200)
    text = models.TextField()

    # Picture (you need content type to serve it properly)
    picture = models.BinaryField(null=True, editable=True)
    content_type = models.CharField(max_length=256, null=True, help_text='The MIMEType of the file')

    # Shows up in the admin list
    def __str__(self):
        return self.title

然后,您需要一个表单,它既可以进行服务器内验证,又可以进行从 InMemoryUploadedFilebytes 的预保存转换,并获取 Content-Type 以供以后使用。

class CreateForm(forms.ModelForm):
    max_upload_limit = 2 * 1024 * 1024
    max_upload_limit_text = str(max_upload_limit) # A more natural size would be nice
    upload_field_name = 'picture'
    # Call this 'picture' so it gets copied from the form to the in-memory model
    picture = forms.FileField(required=False, label='File to Upload <= '+max_upload_limit_text)

    class Meta:
        model = Meal
        fields = ['title', 'text', 'picture']

    def clean(self) :  # Reject if the file is too large
        cleaned_data = super().clean()
        pic = cleaned_data.get('picture')
        if pic is None : return
        if len(pic) > self.max_upload_limit:
            self.add_error('picture', "File must be < "+self.max_upload_limit_text+" bytes")

    def save(self, commit=True) : # Convert uploaded files to bytes
        instance = super(CreateForm, self).save(commit=False)
        f = instance.picture   # Make a copy
        if isinstance(f, InMemoryUploadedFile):
            bytearr = f.read();
            instance.content_type = f.content_type
            instance.picture = bytearr  # Overwrite with the actual image data

        if commit:
            instance.save()
        return instance

在模板中,添加以下代码(改编自上一个答案):

<script>
$("#upload_form").submit(function() 
  if (window.File && window.FileReader && window.FileList && window.Blob) 
      var file = $('#id_ form.upload_field_name ')[0].files[0];
      if (file && file.size >  form.max_upload_limit  ) 
          alert("File " + file.name + " of type " + file.type + " must be <  form.max_upload_limit_text ");
      return false;
    
  
);
</script>

这是处理创建和更新的视图代码:

class MealFormView(LoginRequiredMixin, View):
    template = 'meal_form.html'
    success_url = reverse_lazy('meals')
    def get(self, request, pk=None) :
        if not pk :
            form = CreateForm()
        else:
            meal = get_object_or_404(Meal, id=pk, owner=self.request.user)
            form = CreateForm(instance=meal)
        ctx =  'form': form 
        return render(request, self.template, ctx)

    def post(self, request, pk=None) :
        if not pk:
            form = CreateForm(request.POST, request.FILES or None)
        else:
            meal = get_object_or_404(Meal, id=pk, owner=self.request.user)
            form = CreateForm(request.POST, request.FILES or None, instance=meal)

        if not form.is_valid() :
            ctx = 'form' : form
            return render(request, self.template, ctx)

        form.save()
        return redirect(self.success_url)

这是一个非常简单的视图,可确保传入 request.FILES 在创建实例期间。您几乎可以使用通用的 CreateView,如果它 (a) 使用我的表单并 (b) 在创建模型实例时传递 request.files。

为了完成这项工作,我有以下简单的视图来流式传输文件:

def stream_file(request, pk) :
    meal = get_object_or_404(Meal, id=pk)
    response = HttpResponse()
    response['Content-Type'] = meal.content_type
    response['Content-Length'] = len(meal.picture)
    response.write(meal.picture)
    return response

这不会强制用户登录,但我省略了,因为这个答案已经太长了。

【讨论】:

不要使用二进制字段来存储图像数据 我同意,但是...我的示例是针对一个简单案例和作为学生演示项目的,因此不值得设置基于磁盘的 blob 存储。 Django 中文件的默认方法适用于博客或其他“公共”内容——因为它在查看文件时没有任何访问控制。我的应用程序是面向教育的,您必须保护学生头像图像之类的隐私。默认的 Django 方法不包括将图像保密的能力 - 我上面的方法有。【参考方案12】:

就我而言,django 限制了上传文件的大小。 添加以下设置将取消限制。

# allow upload big file
DATA_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 15  # 15M
FILE_UPLOAD_MAX_MEMORY_SIZE = DATA_UPLOAD_MAX_MEMORY_SIZE

【讨论】:

每当我尝试在我的 Django 应用程序中上传大图像时,它都不会将它们传送到服务器。非常感谢您的帮助。 FILE_UPLOAD_MAX_MEMORY_SIZE 需要相同吗?文档说它将开始流式传输到文件系统,从这个限制开始......所以......只有DATA_UPLOAD_MAX_MEMORY_SIZE确实限制了最大上传文件大小。 另外,我更喜欢这个答案-设置中的oneliner,完成...如果您不需要广泛的不同限制,这应该是首选解决方案。 @weaming 你拯救了我的一天!除此之外,我在 nginx 配置中添加了client_max_body_size 25M;

以上是关于Django 文件上传大小限制的主要内容,如果未能解决你的问题,请参考以下文章

如何限制lighttpd 上传文件的大小

上传文件失败,具体原因:上传的文件超过大小限制,请上传小于 1024k的文件?

修改 apache 上传文件大小限制

关于FileUpload控件上传文件大小限制问题

SpringBoot上传文件大小限制的配置

如何解决IIS7上传文件大小限制