Arhn - архитектура программирования

Django: загрузка нескольких файлов. Список файлов, необходимых в clean_data['file']

Я следовал шаблону документов, чтобы загрузить несколько файлов с одним 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

20.09.2017

  • Можете ли вы добавить код к вопросу? 22.09.2017
  • В примере показан доступ к списку файлов в представлении через вызов request.FILES.getlist('file_field'), а не через поля формы. Я подозреваю, что проблема именно в этом, но не могу знать наверняка, не видя кода. 23.09.2017
  • Да, какой-нибудь пример шаблона кода, который вы используете, был бы замечательным, чтобы попытаться обнаружить возможную ошибку или какую-то конфигурацию, которой может не хватать. 24.09.2017
  • Я предложил редактирование с некоторым кодом, который был отклонен. Итак, вот оно... # form.py из класса импорта форм django MyForm(forms.Form): my_field = forms.FileField( widget=ClearableFileInput( attrs={'multiple': True})) В файле views.py , я хочу получить доступ к файлам в виде списка через переменную form.cleaned_data['my_field'] # views.py из .forms import MyForm def post_view(request): form = MyForm( request.POST, request.FILES) if form .is_valid(): files = form.cleaned_data['my_field'] # Сделайте что-нибудь 26.09.2017
  • О боже ... ПОЧЕМУ, на Земле, добавление примера кода в качестве примера предназначено для автора сообщения и не имеет смысла в качестве редактирования? 26.09.2017
  • @Dunatotatos Я добавил код из документации. 26.09.2017
  • Идеально! Спасибо :) 26.09.2017

Ответы:


1

Что случается

При запуске form.is_valid() поля проверяются и очищаются одно за другим и сохраняются в переменной cleaned_data. Если вы посмотрите на исходный код Django, вы обнаружите, что ваши поля формы проходят индивидуальную проверку в _clean_fields методах класса BaseForm в файле django/forms/forms.py

Проверка выполняется в соответствии с типом виджета (т.е. forms.ClearableFileInput в случае интересующего вас поля). Немного углубившись, вы увидите, что cleaned_data заполнено files.get(name), где files — это список обновленных файлов, а name — это имя поля, которое в настоящее время проверяется.

Тип filesMultiValueDict. Если вы посмотрите на код в 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, который возвращает список для каждого ключа, даже несмотря на то, что большинство веб-форм отправляют одиночные пары имя-значение.

Поскольку это поведение зависит только от виджета поля, сейчас я вижу три разных решения.

Решения

  1. Вы исправляете Django, чтобы иметь правильное поведение, когда attrs виджета установлено на multiple. (Я собирался это сделать, но я действительно не уверен в последствиях.) Я тщательно изучу это и, возможно, отправлю PR.
  2. Вы создаете свой собственный виджет, дочерний элемент ClearableFileInput, который переопределяет метод value_from_datadict для использования files.getlist(name) вместо file.get(name).
  3. Вы используете 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
  • Вау, отличный ответ. Спасибо 26.09.2017
  • self.error_message['противоречие'] должно быть self.error_messages['противоречие']. Спасибо за ваш ответ! 30.03.2018

  • 2

    Я предполагаю, что у вас есть:

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

    и вы пытаетесь получить files, используя: cleaned_data['files'], и вы получаете только 1 файл вместо 2.

    Причина:

    Здесь происходит следующее: Когда вы пытаетесь сделать что-то подобное

    file in self.cleaned_data['files]:, 
    

    думая об этом, вы можете перебрать список объектов uploadedFile и передать каждый из них функции обработчика.

    Но cleaned_data['files'] для вас не список, это всего лишь ОДИН единственный экземпляр загружаемого файла.

    Когда вы перебираете файловый объект, вы на самом деле читаете его. Итак, в конечном итоге вы передаете функции обработчика не объект файла, а его содержимое (в виде строки байтов).

    Решение

    Вам нужно получить список файлов, а затем выполнить с ними то, что вы хотите, как показано ниже.

    files = request.FILES.getlist('files')
    
    for f in files:
        ...  # Do something with each file considering f as file object
    
    24.09.2017
  • Спасибо за предоставленный обходной путь. Да, это работает, но в моих глазах это обходной путь. Форма может иметь префикс. Если я использую request.FILES.getlist('files') в коде формы, префикс не используется... Мне нравится разделение проблем, которое я получаю из хорошей библиотеки форм django. Насколько я знаю, это единственная часть, где мне нужно получить прямой доступ к объекту запросов. Я хотел бы избежать этого. 25.09.2017

  • 3

    Вы можете использовать эту библиотеку: https://github.com/Chive/django-multiupload

    Джанго мультизагрузка

    Мертвое простое поле загрузки нескольких файлов для форм django с использованием множественного атрибута HTML5.

    25.10.2017
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..