Django: upload multiple files. List of files needed in cleaned_data ['file']

I followed the document template to upload multiple files with one forms.FileField :

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

Unfortunately, cleaned_data['file'] contains one file, not both files.

What needs to be done to get all uploaded files to cleaned_data['file'] ?

Here is the code from the docs:

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) 

Update

To solve this problem, there is a transfer request: https://github.com/django/django/pull/9011

+5
source share
3 answers

What will happen

When you run form.is_valid() , the fields are checked and cleared one by one and stored in the variable cleaned_data . If you look at the source code for Django, you will see that the form fields are individually verified in the _clean_fields methods of the _clean_fields class in the django/forms/forms.py

The check is performed according to the type of widgets (i.e. forms.ClearableFileInput in case of the field you are interested in). After going a little deeper, you will see that cleaned_data populated with files.get(name) , where files is a list of updated files, and name is the name of the field being checked.

The type of files is MultiValueDict . If you look at the code in django/utils/datastructures.py , you will find interesting stuff around line 48. I will copy the docstring here:

A subclass of a dictionary configured 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 annoyance problem caused by cgi.parse_qs, which returns a list for each key, even if most web forms submit single name-value pairs.

Since this behavior depends only on the field widget, now I see three different solutions.

Solutions

  • You set Django to behave properly when the attrs for the widget are set to multiple . (I was going to do it, but I'm really not sure about the consequences.) I will study this in depth and can give PR.
  • You create your own widget, the ClearableFileInput , which override the value_from_datadict method to use files.getlist(name) instead of file.get(name) .
  • You are using request.FILES.getlist('your_filed_name') as suggested by Astik Anand , or any easier solution.

Let's take a closer look at solution 2. Here are some instructions for creating your own widget based on ClearableFileInput . Unfortunately, this is not enough to make it work, because the data is sent through the cleaning process belonging to this field. You must also create your own 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 

This part is mainly performed by the phrase from the ClearableFileInput methods, except for the first line value_from_datadict , which was upload = files.get(name) .

As mentioned earlier, you also need to create your own Field to override the to_python FileField method, which tries to access the self.name and 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 

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 delete all checks made in the FileField field, we do not set the maximum number of files, attrs={'multiple': True} redundant with the widget name and many similar things. Also, I'm sure I skipped some important methods in FileField or ClearableFileInput . This is only a starting idea, but you will need a lot more work and see widgets and fields in the official documentation.

+8
source

I assume you have:

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

and you try to get files using: cleaned_data['files'] and you only get 1 file instead of 2.

Cause:

What happens here when you try to do something like this

 file in self.cleaned_data['files]:, 

thinking that you can iterate over the list of uploadedFile objects and pass them to each handler function.

But cleaned_data['files'] not a list for you, it is just one instance of the downloaded file.

When you iterate over a file object, you really read it. So, what you ultimately pass to the handler functions is not a file object, but its contents (as a string of bytes).

Decision

You need to get a list of files and then do something you want as shown below.

 files = request.FILES.getlist('files') for f in files: ... # Do something with each file considering f as file object 
+3
source

You can use this library: https://github.com/Chive/django-multiupload

Django multiupload

Dead simple paged field to upload multiple files for django forms using HTML5.

0
source

All Articles