Как инициализировать Django FileField с несколькими файлами?
У меня есть модель, которая сохраняет несколько загруженных файлов для документа. Я смог использовать виджет FileField
на форме, чтобы обеспечить загрузку нескольких файлов. Однако всякий раз, когда я обновлял объект документа, виджет FileField
не заполнялся исходными данными. В конце концов, мне удалось выяснить, что нужно установить начальные данные и URL-адрес на начальных данных, чтобы виджет отображался должным образом. Но это просто не работает со списком объектов File
. Как я могу заставить это работать правильно?
По сути, мне пришлось написать свой собственный виджет. Я обновил свои требования к виджету так, чтобы мне нужно было передавать только список объектов файлов, чтобы виджет мог отображать их так, как захочет.
widgets.py
class ClearableMultipleFileInput(ClearableFileInput):
"""When setting the initial value of this widget, provide a list of the File instances."""
template_name = "documents/clearable_multiple_file_input.html"
def is_initial(self, value):
return bool(value)
Мне нужно было также создать тег шаблона для обработки имени файла объекта file. Я не хотел отображать весь путь, меня интересовало только имя файла.
templatetags/document.py
from pathlib import Path
from django import template
register = template.Library()
@register.filter(is_safe=False)
def file_name(value):
return Path(value).name
И, наконец, мне пришлось переделать шаблон. Я хотел предоставить ссылку для каждого файла в переданном значении. Я поставил условие, что если файлов больше 25, то нужно просто показать текущее количество файлов. Вы можете изменить шаблон по своему усмотрению. Возможно, мне следует добавить аргумент, который можно передать в виджет, но сейчас у меня нет времени. Я скопировал файл шаблона из Django и изменил его соответствующим образом.
clearable_multiple_file_input.html
{% load document %}
{% if widget.is_initial %}
{{ widget.initial_text }}:<br>
{% if widget.value|length <= 25 %}
{% for f in widget.value %}
<a href="{{ f.url }}">{{ f.name|file_name }}</a><br>
{% endfor %}
{% else %}
{{ widget.value|length }} files<br>
{% endif %}
{% if not widget.required %}
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"
{% if widget.attrs.disabled %} disabled{% endif %}>
<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label>
{% endif %}<br>
{{ widget.input_text }}:
{% endif %}
<input type="{{ widget.type }}" name="{{ widget.name }}" multiple{% include "django/forms/widgets/attrs.html" %}>
И чтобы действительно использовать новый виджет: models.py
class DocumentFile(models.Model):
file = models.FileField()
document = models.ForeignKey(Document, on_delete=models.CASCADE, related_name='files')
forms.py
class DocumentForm(forms.Form):
extra_files = forms.FileField(required=False, widget=ClearableMultipleFileInput())
class Meta:
model = Document
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['extra_files'].initial = [f.file for f in self.instance.files.all()]
views.py
def file_exists_in_document(file, document):
files = document.files.all()
for f in files:
if Path(f.file.name).name == file.name and f.file.size == file.size:
return True
return False
def delete_file_documents(document):
for f in document.files.all():
f.file.delete()
document.files.all().delete()
class DocumentFormView(UpdateView):
model = Document
form_class = DocumentForm
def form_valid(self, form):
document = self.object.document
# The cleaned data will return False if the "clear" checkbox is checked
if form.cleaned_data.get('extra_files') is False:
delete_file_documents(document)
else:
files = self.request.FILES.getlist(f"{self.get_prefix()}-extra_files")
for f in files:
if not file_exists_in_document(f, document):
DocumentFile.objects.create(file=f, document=document)
return super().form_valid(form)
Возможно, здесь не помешала бы некоторая чистка, но вы получаете гораздо большее понимание виджета файлов. В будущих обновлениях, возможно, вы захотите иметь отдельный флажок для каждого файла, чтобы убрать его из виджета ввода файлов. Поскольку мы используем только несколько файлов, не стоит просить пользователя повторно загружать другие файлы, когда он очищает виджет.