Загрузка (upload) файлов на сервер. Классы FileField и ImageField

Курс по Django: https://stepik.org/a/183363

Архив проекта: 48_sitewomen.zip

На данный момент мы с вами узнали, как можно создавать свои собственные формы на Django, используя классы Form и ModelForm. Давайте теперь посмотрим, как можно на сервер загружать отдельные файлы. По-английски это называется upload.

Начнем с самого начала. Создадим HTML-форму для загрузки произвольных файлов. Я ее прикреплю к уже существующему маршруту:

http://127.0.0.1:8000/about/

с отображением шаблона about.html. Откроем этот шаблон и после заголовка h1 пропишем тег form следующим образом:

<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <p ><input type="file" name="file_upload"></p>
    <p ><button type="submit">Отправить</button></p>
</form>

Обратите внимание, в форме обязательно должен быть прописан параметр enctype="multipart/form-data". Без него файлы загружаться на сервер не будут. Также должно быть определено специальное поле input с типом file и стандартная кнопка формы для отправки данных. Это минимальный набор элементов для выбора и отправки произвольного файла на сервер.

В качестве обработчика файла у нас сейчас определена функция представления about(). Давайте посмотрим, что будет на нее приходить при отправке файла. Поставим в функции about() точку останова и при отправке файла в словаре FILES появляется ключ file_upload, который ссылается на объект загруженных данных файла. Нам осталось обратиться к этому объекту и сохранить данные в файл на сервер. Согласно документации:

https://docs.djangoproject.com/en/4.2/topics/http/file-uploads/

у этого объекта есть методы read() и chunks(). Первый читает все данные целиком, а второй по частям. Конечно, предпочтительно использовать второй метод, так как файлы могут быть больших объемов. Ниже, как раз, приведена функция, которая сохраняет загруженные данные в файл с помощью метода chunks(). Я ее просто скопирую и вставлю перед функцией представления about():

def handle_uploaded_file(f):
    with open(f"uploads/{f.name}", "wb+") as destination:
        for chunk in f.chunks():
            destination.write(chunk)

И несколько изменю путь загрузки. А, затем, вызовем ее при отправке POST-запроса:

def about(request):
    if request.method == "POST":
        handle_uploaded_file(request.FILES['file_upload'])
 
    return render(request, 'women/about.html', {'title': 'О сайте', 'menu': menu})

Попробуем выполнить загрузку. Выберем файл, нажимаем кнопку «Отправить» и видим ошибку, что каталог uploads, куда мы собираемся поместить файл, не существует. Давайте его добавим в корень нашего проекта. Теперь, при отправке файла никаких ошибок не возникло, и видим файл в директории uploads.

Класс поля FileField

Мы с вами только что сделали простейший вариант загрузки произвольных файлов на сервер. Конечно, у него есть недостатки. Например, при отправке формы без выбранного файла возникает ошибка. То есть, поля формы мы сейчас никак не проверяем на корректность (валидность). Но, как мы уже знаем, это автоматизируется фреймворком Django при использовании классов форм. И давайте воспользуемся формой, не привязанной к модели.

Перейдем в файл women/forms.py и в нем объявим еще один класс:

class UploadFileForm(forms.Form):
    file = forms.FileField(label="Файл")

Затем, мы создадим форму в функции представления about() следующим образом:

def about(request):
    if request.method == "POST":
        form = UploadFileForm(request.POST, request.FILES)
#        handle_uploaded_file(request.FILES['file_upload'])
    else:
        form = UploadFileForm()
 
    return render(request, 'women/about.html', {'title': 'О сайте', 'menu': menu, 'form': form})

И отобразим ее в шаблоне about.html:

<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Отправить</button></p>
</form>

Переходим по адресу:

http://127.0.0.1:8000/about/

и видим форму, похожую на прежнюю. Здесь можно выбрать файл, отправить, но сохраняться на сервере он пока не будет. Мы этот функционал еще не прописали. Добавим в функцию about() проверку полей и вызов функции сохранения файла, получим:

def about(request):
    if request.method == "POST":
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            handle_uploaded_file(form.cleaned_data['file'])
    else:
        form = UploadFileForm()
 
    return render(request, 'women/about.html', {'title': 'О сайте', 'menu': menu, 'form': form})

Обратите внимание, при вызове метода is_valid(), выполняется проверка полей формы и если попытаться отправить форму без файла, то Django обработает эту ситуацию и возвратит сообщение об ошибке обратно в форму. Это очень удобно. Все, что нам остается, это получить объект-файл и сохранить его в каталог uploads. Причем, здесь мы обращаемся уже к словарю cleaned_data с набором проверенных и корректных данных.

По идее у нас с вами сейчас имеется еще один серьезный недостаток при загрузке файлов. Если загружать разные файлы, но с одинаковыми именами, то они будут перезатирать друг друга в папке uploads. Давайте поправим и этот момент. Для этого мы воспользуемся специальным модулем uuid (в файле women/views.py):

import uuid

И в функции handle_uploaded_file() будем генерировать случайные имена для загружаемых файлов:

def handle_uploaded_file(f):
    name = f.name
    ext = ''
 
    if '.' in name:
        ext = name[name.rindex('.'):]
        name = name[:name.rindex('.')]
 
    suffix = str(uuid.uuid4())
    with open(f"uploads/{name}_{suffix}{ext}", "wb+") as destination:
        for chunk in f.chunks():
            destination.write(chunk)

Теперь, вероятность появления файлов с одинаковыми имена практически равна нулю. Для надежности, дополнительно можно файлы загружать в подкаталоги с годом, месяцем и днем. Но это мы уже делать не будем, пусть останется вам самостоятельным заданием.

Очень часто на практике требуется загружать не произвольные файлы, а только графические. Для этого в форме предусмотрено специальное поле ImageField. Оно является расширением класса FileField и позволяет выбирать исключительно графические файлы. Чтобы им воспользоваться нам достаточно его прописать в классе формы:

class UploadFileForm(forms.Form):
    file = forms.ImageField(label="Изображение")

Обработчик остается неизменным.

Итак, на этом занятии мы разобрали общий принцип загрузки произвольных файлов на сервер с помощью обычной HTML-формы и с помощью класса Form, в котором определили поле специального вида FileField. На следующем занятии продолжим эту тему и разберем работу форм загрузки файлов, связанных с моделями.

Курс по Django: https://stepik.org/a/183363

Видео по теме