会发生什么
当您运行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 来覆盖FileField 的to_python 方法,该方法试图访问self.name 和self.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} 与小部件名称是多余的,以及许多类似的事情。同样,我很确定我错过了FileField 或ClearableFileInput 中的一些重要方法。这只是一个初步的想法,但您需要做更多的工作,并查看官方文档中的widgets 和fields。