Загрузка нескольких файлов в Django: пошаговая инструкция
Настраиваем форму, которая принимает сразу несколько файлов, сохраняет их в модель и валидирует по размеру и типу. Django 5.x, около 30–40 минут работы.

В конце у вас будет рабочая форма: пользователь выбирает несколько файлов за один раз, Django их принимает, проверяет размер и расширение, сохраняет на диск и создаёт по одной записи в базе на каждый файл. Работать будем на Django 5.x и Python 3.10+. На всё уйдёт 30–40 минут, если проект уже создан.
Главная ловушка новичка тут одна: обычное поле request.FILES['file'] вернёт только один файл, даже если в форме выбрано пять. Чтобы получить все, нужен атрибут multiple у input и метод getlist(). Ниже — весь путь без пропусков.
Что нужно иметь до начала
- Установленный Django 5.x: проверьте командой
python -m django --version. - Созданный проект и приложение. Если приложения нет —
python manage.py startapp uploads. - В
settings.pyзаданыMEDIA_URLиMEDIA_ROOT— без них файлы некуда сохранять. - Установленная библиотека
Pillow, если планируете хранить изображения черезImageField(pip install Pillow). Для обычных файлов черезFileFieldона не нужна.
Шаг 1. Настройте медиа-каталог
Откройте settings.py и добавьте в конец, если этих строк ещё нет:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
Проверка: перезапустите сервер (python manage.py runserver) — он не должен ругаться на импорт. Каталог media/ Django создаст сам при первой загрузке.
Отдача файлов в разработке
В urls.py проекта на время отладки подключите раздачу медиа. В проде этим занимается Nginx или объектное хранилище, но локально нужно так:
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from uploads import views
urlpatterns = [
path("admin/", admin.site.urls),
path("upload/", views.upload_files, name="upload_files"),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Проверка: после шага 5 картинка будет открываться по адресу вида /media/attachments/имя.jpg.
Шаг 2. Опишите модель
Каждый загруженный файл — отдельная запись. Так проще удалять и считать файлы по одному.
# uploads/models.py
from django.db import models
class Attachment(models.Model):
file = models.FileField(upload_to="attachments/")
uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.file.name
Аргумент upload_to задаёт подкаталог внутри MEDIA_ROOT. Примените миграции:
python manage.py makemigrations
python manage.py migrate
Проверка: в выводе появится строка вида Applying uploads.0001_initial... OK.
Шаг 3. Соберите форму с множественным выбором
С Django 4.2 стандартный ClearableFileInput с атрибутом multiple использовать в форме напрямую нельзя — фреймворк выбрасывает ошибку валидации. Официально рекомендованный способ — свой виджет и своё поле, которые умеют работать со списком файлов.
# uploads/forms.py
from django import forms
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_clean = super().clean
if isinstance(data, (list, tuple)):
return [single_clean(item, initial) for item in data]
return single_clean(data, initial)
class UploadForm(forms.Form):
files = MultipleFileField()
Этот приём описан в документации Django в разделе про загрузку файлов — см. ссылку в конце. Проверка: форма импортируется без ошибок в python manage.py shell.
Шаг 4. Обработайте POST во вью
Ключевой момент — request.FILES.getlist("files"). Метод get() вернул бы один файл, getlist() — все.
# uploads/views.py
from django.shortcuts import redirect, render
from .forms import UploadForm
from .models import Attachment
def upload_files(request):
if request.method == "POST":
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
for uploaded in request.FILES.getlist("files"):
Attachment.objects.create(file=uploaded)
return redirect("upload_files")
else:
form = UploadForm()
attachments = Attachment.objects.order_by("-uploaded_at")
return render(
request,
"uploads/upload.html",
{"form": form, "attachments": attachments},
)
Проверка: после отправки формы страница перезагрузится, а в списке ниже появятся загруженные файлы.
Шаг 5. Шаблон с формой
Атрибут enctype="multipart/form-data" обязателен — без него файлы вообще не дойдут до сервера. Атрибут multiple уже добавлен виджетом из шага 3.
<!-- uploads/templates/uploads/upload.html -->
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Загрузить</button>
</form>
<ul>
{% for item in attachments %}
<li><a href="{{ item.file.url }}">{{ item.file.name }}</a></li>
{% endfor %}
</ul>
Проверка: откройте /upload/, диалог выбора файлов должен разрешать выделение нескольких сразу (Ctrl или Shift при клике).
Шаг 6. Добавьте валидацию размера и типа
Без проверок пользователь загрузит гигабайтный архив или исполняемый файл. Проверяйте в методе clean_files формы — там доступ ко всему списку.
# дополнение к UploadForm в uploads/forms.py
MAX_SIZE = 5 * 1024 * 1024 # 5 МБ на файл
ALLOWED_EXTENSIONS = {".pdf", ".jpg", ".jpeg", ".png"}
class UploadForm(forms.Form):
files = MultipleFileField()
def clean_files(self):
files = self.cleaned_data["files"]
for uploaded in files:
if uploaded.size > MAX_SIZE:
raise forms.ValidationError(
f"Файл {uploaded.name} больше 5 МБ."
)
ext = "." + uploaded.name.rsplit(".", 1)[-1].lower()
if ext not in ALLOWED_EXTENSIONS:
raise forms.ValidationError(
f"Тип файла {ext} не разрешён."
)
return files
Проверка расширения — не гарантия безопасности: расширение легко подделать. Для картинок дополнительно помогает ImageField, который прогоняет файл через Pillow. Проверка: загрузите файл больше 5 МБ — форма покажет текст ошибки и ничего не сохранит.
Множественная загрузка ломается на мелочах: забытыйenctype,get()вместоgetlist()и попытка отдать голыйmultipleстандартному виджету. Уберите эти три — и всё работает.
Если не получилось
- Сохраняется только один файл. Во вью стоит
request.FILES.get("files")вместоgetlist("files"). Замените метод. - Форма валидна, но
request.FILESпустой. В теге<form>нетenctype="multipart/form-data". Браузер отправил файлы как обычный текст. - Ошибка
ClearableFileInput doesn't support uploading multiple files. Вы повесилиmultipleна стандартный виджет. ИспользуйтеMultipleFileFieldиз шага 3. - Файлы загрузились, но по ссылке 404. В
urls.pyне подключёнstatic(settings.MEDIA_URL, ...)илиDEBUG=False. В проде отдачу медиа настраивает веб-сервер, а не Django. SuspiciousFileOperationпри сохранении. Имя файла содержит путь или спецсимволы. Django очищает имя сам, но проверьте, чтоupload_toзадан относительным путём без...
Что делать дальше
Форма работает — теперь настройте продакшн. В проде DEBUG=False, и раздача медиа через Django отключается: пропишите отдачу /media/ в конфиге Nginx или подключите объектное хранилище через django-storages. Ограничьте максимальный размер запроса на уровне веб-сервера (в Nginx это client_max_body_size), иначе большой файл упрётся в дефолтный лимит ещё до Django. Хранение файлов на S3-совместимом хранилище и генерация превью для картинок — отдельные темы, здесь мы их не разворачиваем.
Проектная работа: От проблемы до защиты: продукт, этапы, вопросы комиссии
Спросить за 15 ₽Источники: Django docs — Uploading multiple files (File Uploads), Django docs — Managing files (FileField, upload_to)
Частые вопросы
А если у меня Django 3.2 или 4.0 — код из шага 3 подойдёт?
Почему сохраняется только один файл, хотя я выбрал несколько?
Как ограничить общее число файлов за одну загрузку?
Проверка расширения защищает от вредоносных файлов?
Файл больше нескольких мегабайт не загружается даже с валидацией на 5 МБ. Почему?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.