Загрузка файлов

Когда Django обрабатывает загрузку файла, данные файла помещаются в request.FILES (подробнее об объекте request смотрите в документации объекты запроса и ответа). В этом документе объясняется, как файлы хранятся на диске и в памяти и как настроить поведение по умолчанию.

Предупреждение

Существуют риски безопасности, если вы принимаете загруженное содержимое от ненадежных пользователей! Подробности о снижении рисков см. в теме руководства по безопасности Загружаемый пользователем контент.

Основы загрузки файлов

Рассмотрим форму, содержащую FileField:

forms.py
from django import forms


class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=50)
    file = forms.FileField()

Представление, обрабатывающее эту форму, получит данные файла в request.FILES, который представляет собой словарь, содержащий ключ для каждого FileField (или ImageField, или другой FileField) в форме. Таким образом, данные из приведенной выше формы будут доступны как request.FILES['file'].

Обратите внимание, что request.FILES будет содержать данные только в том случае, если метод запроса был POST, по крайней мере одно поле файла было фактически отправлено, а <form> отправивший запрос имеет атрибут enctype="multipart/form-data. В противном случае request.FILES будет пустым.

В большинстве случаев вы будете передавать данные файла из запроса в форму, как описано в Привязка загруженных файлов к форме. Это будет выглядеть примерно так:

views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm

# Imaginary function to handle an uploaded file.
from somewhere import handle_uploaded_file


def upload_file(request):
    if request.method == "POST":
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            handle_uploaded_file(request.FILES["file"])
            return HttpResponseRedirect("/success/url/")
    else:
        form = UploadFileForm()
    return render(request, "upload.html", {"form": form})

Обратите внимание, что мы должны передать request.FILES в конструктор формы; так данные файла привязываются к форме.

Вот обычный способ обработки загруженного файла:

def handle_uploaded_file(f):
    with open("some/file/name.txt", "wb+") as destination:
        for chunk in f.chunks():
            destination.write(chunk)

Зацикливание на UploadedFile.chunks() вместо использования read() гарантирует, что большие файлы не перегружают память вашей системы.

Есть несколько других методов и атрибутов, доступных для объектов UploadedFile; смотрите UploadedFile для полной справки.

Обработка загруженных файлов с помощью модели

Если вы сохраняете файл в Model с FileField, используя ModelForm значительно упрощает этот процесс. Объект файла будет сохранен в расположение, указанное аргументом upload_to соответствующего FileField при вызове form.save():

from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField


def upload_file(request):
    if request.method == "POST":
        form = ModelFormWithFileField(request.POST, request.FILES)
        if form.is_valid():
            # file is saved
            form.save()
            return HttpResponseRedirect("/success/url/")
    else:
        form = ModelFormWithFileField()
    return render(request, "upload.html", {"form": form})

Если вы создаете объект вручную, вы можете назначить файловый объект из request.FILES полю файла в модели:

from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm
from .models import ModelWithFileField


def upload_file(request):
    if request.method == "POST":
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            instance = ModelWithFileField(file_field=request.FILES["file"])
            instance.save()
            return HttpResponseRedirect("/success/url/")
    else:
        form = UploadFileForm()
    return render(request, "upload.html", {"form": form})

Если вы конструируете объект вручную вне запроса, вы можете присвоить File подобный объект FileField:

from django.core.management.base import BaseCommand
from django.core.files.base import ContentFile


class MyCommand(BaseCommand):
    def handle(self, *args, **options):
        content_file = ContentFile(b"Hello world!", name="hello-world.txt")
        instance = ModelWithFileField(file_field=content_file)
        instance.save()

Загрузка нескольких файлов в Django

Если вы хотите загружать несколько файлов с помощью одного поля формы, создайте подкласс виджета поля и установите для него атрибут allow_multiple_selected в значение True.

Для того чтобы все эти файлы проверялись формой (и значение поля включало их все), необходимо также создать подкласс FileField. Пример смотрите ниже.

Многофайловое поле

Вероятно, в будущем в Django будет реализована поддержка нескольких файловых полей.

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_file_clean = super().clean
        if isinstance(data, (list, tuple)):
            result = [single_file_clean(d, initial) for d in data]
        else:
            result = single_file_clean(data, initial)
        return result


class FileFieldForm(forms.Form):
    file_field = MultipleFileField()

Затем переопределите метод post вашего подкласса FormView для обработки нескольких загрузок файлов:

views.py
from django.views.generic.edit import FormView
from .forms import FileFieldForm


class FileFieldFormView(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)
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        files = form.cleaned_data["file_field"]
        for f in files:
            ...  # Do something with each file.
        return super().form_valid()

Предупреждение

Это позволит работать с несколькими файлами только на уровне формы. Следует иметь в виду, что с его помощью нельзя, например, разместить несколько файлов на одном экземпляре модели (в одном поле), даже если пользовательский виджет используется с полем формы, связанным с моделью FileField.

Changed in Django 3.2.19:

В предыдущих версиях не было поддержки атрибута класса allow_multiple_selected, и пользователям рекомендовалось создавать виджет с HTML-атрибутом multiple, заданным через аргумент attrs. Однако это приводило к тому, что валидация поля формы применялась только к последнему присланному файлу, что могло иметь негативные последствия для безопасности.

Обработчики загрузки

Когда пользователь загружает файл, Django передает данные файла обработчику загрузки – небольшому классу, который обрабатывает данные файла по мере их загрузки. Обработчики загрузки изначально определены в настройке FILE_UPLOAD_HANDLERS, которая по умолчанию:

[
    "django.core.files.uploadhandler.MemoryFileUploadHandler",
    "django.core.files.uploadhandler.TemporaryFileUploadHandler",
]

Вместе MemoryFileUploadHandler и TemporaryFileUploadHandler обеспечивают стандартное поведение загрузки файлов Django для чтения небольших файлов в память и больших файлов на диск.

Вы можете написать собственные обработчики, которые настраивают, как Django обрабатывает файлы. Вы можете, например, использовать настраиваемые обработчики для обеспечения соблюдения квот на уровне пользователя, сжатия данных на лету, отображения индикаторов выполнения и даже отправки данных в другое место хранения напрямую, не сохраняя их локально. Смотрите Написание пользовательских обработчиков загрузки для подробностей о том, как вы можете настроить или полностью заменить поведение загрузки.

Где хранятся загруженные данные

Перед сохранением загруженных файлов данные необходимо где-то сохранить.

По умолчанию, если загруженный файл меньше 2,5 мегабайт, Django будет хранить все содержимое загруженного файла в памяти. Это означает, что сохранение файла включает только чтение из памяти и запись на диск и, следовательно, выполняется очень быстро.

Однако, если загруженный файл слишком велик, Django запишет загруженный файл во временный файл, хранящийся во временном каталоге вашей системы. На Unix-подобной платформе это означает, что вы можете ожидать, что Django сгенерирует файл с именем что-то вроде /tmp/tmpzfp6I6.upload. Если загрузка достаточно велика, вы можете наблюдать, как этот файл увеличивается в размере, когда Django передает данные на диск.

Эти особенности - 2,5 мегабайта; /tmp; и т.д. - это «разумные значения по умолчанию», которые можно настроить, как описано в следующем разделе.

Изменение поведения обработчика загрузки

Есть несколько настроек, которые управляют поведением загрузки файлов Django. Смотрите Настройки загрузки файла для подробностей.

Изменение обработчиков загрузки на лету

Иногда определенные представления требуют другого поведения загрузки. В этих случаях вы можете переопределить обработчики загрузки для каждого запроса, изменив request.upload_handlers. По умолчанию этот список будет содержать обработчики загрузки, заданные параметром FILE_UPLOAD_HANDLERS, но вы можете изменить список так же, как любой другой список.

Например, предположим, что вы написали ProgressBarUploadHandler, который обеспечивает обратную связь о ходе загрузки в какой-то виджет AJAX. Вы можете добавить этот обработчик в свои обработчики загрузки следующим образом:

request.upload_handlers.insert(0, ProgressBarUploadHandler(request))

Вы, вероятно, захотите использовать в этом случае list.insert() (вместо append()), потому что обработчик индикатора выполнения должен будет запускаться перед любыми другими обработчиками. Помните, что обработчики загрузки обрабатываются по порядку.

Если вы хотите полностью заменить обработчики загрузки, вы можете назначить новый список:

request.upload_handlers = [ProgressBarUploadHandler(request)]

Примечание

Вы можете изменять обработчики загрузки только перед доступом к request.POST или request.FILES - нет смысла изменять обработчики загрузки после того, как обработка загрузки уже началась. Если вы попытаетесь изменить request.upload_handlers после чтения из request.POST или request.FILES, Django выдаст ошибку.

Таким образом, вы всегда должны изменять обработчики загрузки как можно раньше в вашем представлении.

Кроме того, к request.POST обращается CsrfViewMiddleware, который по умолчанию включен. Это означает, что вам нужно будет использовать csrf_exempt() в вашем представлении, чтобы вы могли изменять обработчики загрузки. Затем вам нужно будет использовать csrf_protect() в функции, которая фактически обрабатывает запрос. Обратите внимание, что это означает, что обработчики могут начать получать загружаемый файл до того, как будут выполнены проверки CSRF. Пример кода:

from django.views.decorators.csrf import csrf_exempt, csrf_protect


@csrf_exempt
def upload_file_view(request):
    request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
    return _upload_file_view(request)


@csrf_protect
def _upload_file_view(request):
    ...  # Process request

Если вы используете представление на основе класса, вам нужно будет использовать csrf_exempt() в его методе dispatch() и csrf_protect() в методе, который фактически обрабатывает запрос. Пример кода:

from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt, csrf_protect


@method_decorator(csrf_exempt, name="dispatch")
class UploadFileView(View):
    def setup(self, request, *args, **kwargs):
        request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
        super().setup(request, *args, **kwargs)

    @method_decorator(csrf_protect)
    def post(self, request, *args, **kwargs):
        ...  # Process request
Вернуться на верх