Что случается
При запуске form.is_valid()
поля проверяются и очищаются одно за другим и сохраняются в переменной cleaned_data
. Если вы посмотрите на исходный код Django, вы обнаружите, что ваши поля формы проходят индивидуальную проверку в _clean_fields
методах класса BaseForm
в файле django/forms/forms.py
Проверка выполняется в соответствии с типом виджета (т.е. 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, который возвращает список для каждого ключа, даже несмотря на то, что большинство веб-форм отправляют одиночные пары имя-значение.
Поскольку это поведение зависит только от виджета поля, сейчас я вижу три разных решения.
Решения
- Вы исправляете Django, чтобы иметь правильное поведение, когда
attrs
виджета установлено на multiple
. (Я собирался это сделать, но я действительно не уверен в последствиях.) Я тщательно изучу это и, возможно, отправлю PR.
- Вы создаете свой собственный виджет, дочерний элемент
ClearableFileInput
, который переопределяет метод value_from_datadict
для использования files.getlist(name)
вместо file.get(name)
.
- Вы используете
request.FILES.getlist('your_filed_name')
, как предложено Astik Anand, или любое более простое решение.
Давайте подробнее рассмотрим решение 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
, чтобы переопределить метод to_python
FileField
, который пытается получить доступ к атрибутам 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
. Это только начальная идея, но вам потребуется еще много работы и просмотр виджеты и поля в официальной документации .
25.09.2017