Django:上传多个文件。 clean_data['file'] 中需要的文件列表

Posted

技术标签:

【中文标题】Django:上传多个文件。 clean_data[\'file\'] 中需要的文件列表【英文标题】:Django: Uploading multiple files. List of files needed in cleaned_data['file']Django:上传多个文件。 clean_data['file'] 中需要的文件列表 【发布时间】:2018-03-01 07:26:42 【问题描述】:

我按照文档的模式,上传了几个文件,一个forms.FileField

https://docs.djangoproject.com/en/1.11/topics/http/file-uploads/#uploading-multiple-files

很遗憾,cleaned_data['file'] 确实包含一个文件,而不是两个文件。

需要做什么才能将所有上传的文件放在cleaned_data['file'] 上?

这是文档中的代码:

forms.py

from django import forms

class FileFieldForm(forms.Form):
    file_field = forms.FileField(widget=forms.ClearableFileInput(attrs='multiple': True))

views.py

from django.views.generic.edit import FormView
from .forms import FileFieldForm

class FileFieldView(FormView):
    form_class = FileFieldForm
    template_name = 'upload.html'  # Replace with your template.
    success_url = '...'  # Replace with your URL or reverse().

    def post(self, request, *args, **kwargs):
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        files = request.FILES.getlist('file_field')
        if form.is_valid():
            for f in files:
                ...  # Do something with each file.
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

更新

有一个拉取请求来解决这个问题:https://github.com/django/django/pull/9011

【问题讨论】:

你能在问题中添加一些代码吗? 该示例显示了通过 request.FILES.getlist('file_field') 调用访问视图中的文件列表,而不是通过表单字段。我怀疑这就是问题所在,但如果没有看到一些代码就无法确定。 是的,您正在使用的代码模式的一些示例非常适合尝试发现可能的错误或可能缺少的某些配置。 天哪...为什么,在地球上,添加示例代码作为示例是“旨在解决帖子的作者并且没有任何意义作为编辑”? @Dunatotatos 我添加了文档中的代码。 【参考方案1】:

会发生什么

当您运行form.is_valid() 时,这些字段会被逐个验证和清理,并存储在cleaned_data 变量中。如果您查看 Django 源代码,您会发现您的表单字段在文件 django/forms/forms.py 中的类 BaseForm_clean_fields 方法中经过单独验证

根据小部件类型进行验证(ie forms.ClearableFileInput 在您感兴趣的字段的情况下)。再深入一点,您会发现cleaned_data 填充有files.get(name),其中files 是更新文件的列表,name 是当前正在验证的字段的名称。

files 的类型是MultiValueDict。如果您查看django/utils/datastructures.py 中的代码,您会在第 48 行发现一些有趣的东西。我将文档字符串复制到这里:

为处理多个值而定制的字典子类 相同的键。

>>> d = MultiValueDict('name': ['Adrian', 'Simon'], 'position': ['Developer'])
>>> d['name']
'Simon'
>>> d.getlist('name')
['Adrian', 'Simon']
>>> d.getlist('doesnotexist')
[]
>>> d.getlist('doesnotexist', ['Adrian', 'Simon'])
['Adrian', 'Simon']
>>> d.get('lastname', 'nonexistent')
'nonexistent'
>>> d.setlist('lastname', ['Holovaty', 'Willison'])

这个类的存在是为了解决cgi.parse_qs提出的恼人问题, 它为每个键返回一个列表,即使大多数 Web 表单提交 单个名称-值对。

由于此行为仅取决于字段的小部件,因此我现在可以看到三种不同的解决方案。

解决方案

    当小部件的attrs 设置为multiple 时,您修补Django 以获得正确的行为。 (我正要这样做,但我真的不确定后果。)我会深入研究,可能会提交 PR。 您创建自己的小部件,ClearableFileInput 的子级,它覆盖 value_from_datadict 方法以使用 files.getlist(name) 而不是 file.get(name)。 您按照Astik Anand 的建议使用request.FILES.getlist('your_filed_name'),或任何更简单的解决方案。

让我们仔细看看解决方案 2。 以下是一些基于ClearableFileInput 创建您自己的小部件的说明。不幸的是,仅仅让它工作是不够的,因为数据是通过该字段拥有的清理过程发送的。您还必须创建自己的FileField

# widgets.py
from django.forms.widgets import ClearableFileInput
from django.forms.widgets import CheckboxInput

FILE_INPUT_CONTRADICTION = object()

class ClearableMultipleFilesInput(ClearableFileInput):
    def value_from_datadict(self, data, files, name):
        upload = files.getlist(name) # files.get(name) in Django source

        if not self.is_required and CheckboxInput().value_from_datadict(
                data, files, self.clear_checkbox_name(name)):

            if upload:
                # If the user contradicts themselves (uploads a new file AND
                # checks the "clear" checkbox), we return a unique marker
                # objects that FileField will turn into a ValidationError.
                return FILE_INPUT_CONTRADICTION
            # False signals to clear any existing value, as opposed to just None
            return False
        return upload

这部分基本上是从ClearableFileInput的方法中逐字提取的,除了value_from_datadict的第一行是upload = files.get(name)

如前所述,您还必须创建自己的Field 来覆盖FileFieldto_python 方法,该方法试图访问self.nameself.size 属性。

# fields.py
from django.forms.fields import FileField
from .widgets import ClearableMultipleFilesInput
from .widgets import FILE_INPUT_CONTRADICTION

class MultipleFilesField(FileField):
    widget = ClearableMultipleFilesInput

    def clean(self, data, initial=None):
        # If the widget got contradictory inputs, we raise a validation error
        if data is FILE_INPUT_CONTRADICTION:
            raise ValidationError(self.error_message['contradiction'], code='contradiction')
        # False means the field value should be cleared; further validation is
        # not needed.
        if data is False:
            if not self.required:
                return False
            # If the field is required, clearing is not possible (the widg    et
            # shouldn't return False data in that case anyway). False is not
            # in self.empty_value; if a False value makes it this far
            # it should be validated from here on out as None (so it will be
            # caught by the required check).
            data = None
        if not data and initial:
            return initial
        return data

下面是如何在您的表单中使用它:

# forms.py
from .widgets import ClearableMultipleFilesInput
from .fields import MultipleFilesField

your_field = MultipleFilesField(
    widget=ClearableMultipleFilesInput(
        attrs='multiple': True))

而且它有效!

>>> print(form.cleaned_data['your_field']
[<TemporaryUploadedFile: file1.pdf (application/pdf)>, <TemporaryUploadedFile: file2.pdf (application/pdf)>, <TemporaryUploadedFile: file3.pdf (application/pdf)>]

当然,这个方案不能直接使用,需要很多改进。在这里,我们基本上删除了在FileField 字段中所做的所有检查,我们没有设置最大文件数,attrs='multiple': True 与小部件名称是多余的,以及许多类似的事情。同样,我很确定我错过了FileFieldClearableFileInput 中的一些重要方法。这只是一个初步的想法,但您需要做更多的工作,并查看官方文档中的widgets 和fields。

【讨论】:

哇,很好的答案。谢谢 self.error_message['contradiction'] 应该是 self.error_messages['contradiction']。谢谢你的回答!【参考方案2】:

我假设你有:

class FileFieldForm(forms.Form):
     files = forms.FileField(widget=forms.ClearableFileInput(attrs='multiple': True))

并且您尝试使用 cleaned_data['files'] 获取 files 并且您只获得 1 个文件而不是 2 个。

原因:

这里发生的事情是,当你尝试做这样的事情时

file in self.cleaned_data['files]:, 

考虑到这一点,您可以遍历一组uploadedFile 对象并将每个对象传递给处理函数。

但是cleaned_data['files'] 不是给你的列表,它只是一个uploadfile 的单个实例。

当你迭代一个文件对象时,你实际上是在读取它。所以你最终传递给处理函数的不是文件对象,而是它的内容(作为字节字符串)。

解决办法

您需要获取文件列表,然后对它们执行您想要的操作,如下所示。

files = request.FILES.getlist('files')

for f in files:
    ...  # Do something with each file considering f as file object

【讨论】:

感谢您提供此解决方法。是的,这行得通,但在我看来这是一种解决方法。表单可以有前缀。如果我在表单代码中使用request.FILES.getlist('files'),则不使用前缀......我喜欢从漂亮的 django 表单库中获得的“conerns 分离”。 AFAIK 这是我需要直接访问请求对象的唯一部分。我想避免这种情况。【参考方案3】:

你可以使用这个库:https://github.com/Chive/django-multiupload

Django 多重上传

使用 django 表单的死简单的插入式多文件上传字段 HTML5 的多重属性。

【讨论】:

以上是关于Django:上传多个文件。 clean_data['file'] 中需要的文件列表的主要内容,如果未能解决你的问题,请参考以下文章

clean_data 和普通数据有啥区别? - django

django 一次性上传多个文件, 批量上传

Django - 导入导出 - 上传多个文件

在 django 中保存多个上传的文件

Django接收多个文件上传

在 django 中使用相同的输入名称上传多个文件