23👍
What happens
When your run form.is_valid()
, the fields are validated and cleaned one after one, and stored in the cleaned_data
variable. If you look at the Django source code, you’ll find that your form fields go through an individual validation in the _clean_fields
methods of the class BaseForm
in the file django/forms/forms.py
The validation is made according to the widget type (ie forms.ClearableFileInput
in the case of the field you are interested in). Going a bit deeper shows you that the cleaned_data
is filled with files.get(name)
where files
is the list of the updated files, and name
is the name of the field currently being validated.
The type of files
is MultiValueDict
. If you look at the code in django/utils/datastructures.py
, you’ll find some interesting stuff around the line 48. I copy the docstring here :
A subclass of dictionary customized to handle multiple values for the
same key.
>>> 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'])
This class exists to solve the irritating problem raised by cgi.parse_qs,
which returns a list for every key, even though most Web forms submit
single name-value pairs.
As this behavior depends only on the widget of the field, I can see three different solutions from now.
The solutions
- You patch Django to have a correct behavior when the
attrs
of the widget is set tomultiple
. (I was about to do it, but I’m really not sure about the consequences.) I’ll study that in depth and may submit a PR. - You create your own Widget, a children of
ClearableFileInput
, which override thevalue_from_datadict
method to usefiles.getlist(name)
instead offile.get(name)
. - You use
request.FILES.getlist('your_filed_name')
as suggested by Astik Anand, or any easier solution.
Let’s take a closer look at the solution 2.
Here are some instructions to create your own widget based on ClearableFileInput
. Unfortunately, it is not enough to make it work, as the data are sent through a cleaning process owned by the field. You must create your own FileField
as well.
# 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
This part is basically taken word by word from the methods of ClearableFileInput
, except the first line of value_from_datadict
which was upload = files.get(name)
.
As mentioned before, you also have to create your own Field
to override the to_python
method of FileField
which tries to access a self.name
and self.size
attributes.
# 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
And here is how to use it in your form:
# forms.py
from .widgets import ClearableMultipleFilesInput
from .fields import MultipleFilesField
your_field = MultipleFilesField(
widget=ClearableMultipleFilesInput(
attrs={'multiple': True}))
And it works!
>>> print(form.cleaned_data['your_field']
[<TemporaryUploadedFile: file1.pdf (application/pdf)>, <TemporaryUploadedFile: file2.pdf (application/pdf)>, <TemporaryUploadedFile: file3.pdf (application/pdf)>]
Of course, this solution cannot be used directly and needs a lot of improvements. Here, we basically erase all the checking made in the FileField
field, we do not set a maximum number of files, the attrs={'multiple': True}
is redundant with the widget name, and many similar things. As well, I am pretty sure I missed some important methods in the FileField
or ClearableFileInput
. This is only a starting idea, but you’ll need much more work, and a look at the widgets and fields on the official documentation.
8👍
I assume that you have:
class FileFieldForm(forms.Form):
files = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
and you are trying to get files
using : cleaned_data['files']
and you are getting only 1 file instead of 2.
The Reason:
What is happening here is, When you try to do something like this
file in self.cleaned_data['files]:,
thinking that, you can iterate over a list of uploadedFile objects and pass each to the handler function.
But cleaned_data['files']
is not a list for you, it’s just ONE single instance of uploadedfile.
When you iterate over a file object, you’re actually reading it. So what you pass eventually to the handler function is not the file object but its content (as a bytes string).
The solution
You need to get a list of files and then, perform something what you want on them as below.
files = request.FILES.getlist('files')
for f in files:
... # Do something with each file considering f as file object
- AttributeError: 'NoneType' object has no attribute 'attname' (Django)
- What are the valid values for a django URL field?
1👍
You can use this library: https://github.com/Chive/django-multiupload
Django Multiupload
Dead simple drop-in multi file upload field for django forms using
HTML5’s multiple attribute.
- Verbose_name for a model's method
- Accessing django project in LAN systems
- Where do I modify the admin view code for a 3rd party app?
- Python gettext error: Can't convert '__proxy__' object to str implicitly