Как загружать картинки и файлы через Django 3 используя модель и формы


12 апреля 2021


Загрузка изображений и других файлов в Django 3 через формы и модели

Для работы с изображениями и другими файлами во фреймворке Python Django существует готовый механизм реализуемый через модели. Процесс хранения картинок и файлов в базах данных может вызвать путаницу если вы не имели особого опыта работы с ними. Процесс создания возможности загрузки файлов состоит из нескольких шагов, которые мы рассмотрим ниже на примерах.

 

Подготовка тестового проекта

Создадим проект, на котором будут показаны следующие примеры. У вас уже должен быть установлен Django и, по вашему желанию, создана виртуальная среда. В примерах будет использоваться название проекта 'file_project', вы можете выбрать имя на свое усмотрение:

# создаем проект
django-admin startproject file_project
cd file_project
# создадим приложение
python manage.py startapp upload_app

Мы должны добавить наше приложение в конфигурацию проекта через 'settings.py'. В 'INSTALLED_APPS' нужно добавить следующую строку:

# file_project/settings.py

'upload_app.apps.UploadAppConfig',

Добавление приложения Django в INSTALLED_APPS

Выполните миграцию:

python manage.py migrate

Выполнение migrate в Django

 

Создание модели для загрузки файлов

Каждая модель отображает какую-то таблицу в базе данных. В этих таблицах, а именно колонках, хранятся определенные типы данных (строки, числа и т.д.). Один из таких типов данных - файлы. Однако хранить файлы в SQL базах, в большинстве случаев, считается плохой практикой. Одна из причин не делать этого заключается в непригодности SQL баз в поиске по таким типам данных. 

В большинстве случаев хранение файлов организуется в несколько шагов:

  1. Ваше приложение знает полный путь до изображения;
  2. В базу данных добавляется путь до изображения, в виде строки, например '/media/image1.jpg';
  3. Ваше приложение физически сохраняет изображения используя указанный путь.

Для такой реализации в Django есть два типа полей (Fields):

  • FileField - для любых типов данных;
  • ImageField - наследует все методы FileField, добавляет валидацию картинок и методы. Например мы можем получить размер картинки используя image.width или image.height.

Модели создаются в 'models.py' вашего проекта. Добавьте в него следующую модель:

# upload_app/models.py

from django.db import models


class Book(models.Model):
    title = models.CharField(max_length=150)
    cover = models.ImageField(upload_to='images/')
    book = models.FileField(upload_to='books/')

    def __str__(self):
        return self.title

Создание модели с ImageField и FileField в Django

uppload_to - это папка, в которую будут сохранены файлы этого поля. Этот путь относительный и он продолжает путь указанный в MEDIA_ROOT (будет создана ниже). Т.е. если в upload_to указан 'images/', то полный путь до файла будет '/media_root/images'. Параметр upload_to - не обязателен.

В ImageField добавлена проверка, что файл имеет тип изображения. Так же у ImageField есть методы возвращающие высоту и ширину. Ширину и высоту так же можно сохранить в отдельные модели используя параметры height_field и width_field. Часть этих возможностей по работе с изображениями выполняется через библиотеку Pillow и поэтому ее нужно установить:

pip install pillow

Удаление любого объекта удаляет только запись из базы. На файловой системе файл остается.

После создания моделей выполните миграции:

python manage.py makemigrations
python manage.py migrate

Динамический upload_to

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

upload_to='books/%Y-%m-%d/'

Этот способ соответствует методу strftime() из библиотеки datetime.

Так же можно создать путь соответствующий пользователю, который выполняет загрузку:

def user_directory_path(instance, filename):
    # путь, куда будет осуществлена загрузка MEDIA_ROOT/user_<id>/<filename>
    return 'user_{0}/{1}'.format(instance.user.id, filename)

class MyModel(models.Model):
    ...
    upload = models.FileField(upload_to=user_directory_path)

Папка соответствующая пользователю будет создана только в том случае, если модель MyModel будет иметь поле с названием user. Т.е. instance - это и есть сам объект модели. Пример того как это можно сделать:

Сохранение картинки в папку пользователя Djagno

Использование существующих файлов и папок с FilePathField

Во фреймворке Python Django есть так же поле FilePathField. Его основная задача - создание записи в базе на основании существующего файла.

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

class Foo(models.Model):
    # Указывает на абсолютный путь
    audio = models.FilePathField(path='/home/user/')

Доступны поля для рекурсивного поиска и по маске. Path - это абсолютный путь (в отличие от предыдущих примеров).

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

Выбор существующего файла с FilePathField Django

FilePathField не следует использовать в директориях с вашим приложением т.к. приводит к уязвимостям.

 

Переменная MEDIA_ROOT и MEDIA_URL

Для хранения всех загруженных файлов определяется отдельная папка. Эта папка указывается в переменной MEDIA_ROOT, в файле 'settings.py'. Есть еще переменная MEDIA_URL, которая используется в шаблонах и адресах для обращения к файлам в  MEDIA_ROOT.

Эти настройки прописываются в файле 'settings.py'. Для Django 3+ используются следующие значения:

# file_project/settings.py

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

Для Django до 3-ей версии это выглядело так:

# file_project/settings.py

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

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

Определение MEDIA_ROOT и MEDIA_URL в Django

Теперь, например, сохранять файлы вы будете по следующему пути:

'/var/www/file_project/media/images/1.jpg'

А открывать их будет по следующей ссылке:

'http://localhost/file_project/media/images/1.jpg'

В Django так же есть похожая переменная - STATIC_ROOT и STATIC_URL. Эти переменные указывают на папки в которые вы сами загружали файлы не используя Django. Хоть вы можете соединить STATIC_ROOT и MEDIA_ROOT  - это не рекомендуется делать т.к. приведет к уязвимости. Совмещения этих путей стоит избегать.

 

Панель администрирования

Зарегистрируем наши модели что бы они отображались в панели администрирования. Для этого отредактируем файл 'admin.py':

# upload_app/admin.py

from django.contrib import admin
from .models import Book

admin.site.register(Book)

Создание панели администрирования в Django

После этого создайте супер пользователя и запустите сервер:

python manage.py createsuperuser
python manage.py runserver

Создание пользователя администратора в Django

Зайдя по адресу 'http://127.0.0.1:8000/admin/' и авторизовавшись можно увидеть созданную модель и поля для загрузки файлов:

Загрузка файлов и картинок в панели администрирования Django

Если открыть директорию прописанную в "MEDIA_ROOT" можно увидеть загруженные файлы (могут отличаться из-за настроек):

Папка с сохранёнными файлами в Django

 

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

Что бы вывести изображение на странице нужно выполнить следующие шаги:

  1. Создать url связывающий запрос пользователя с какой-то логикой;
  2. Шаблон HTML, который преобразует данные и вернет их пользователю;
  3. Создать функцию или класс, который свяжет url и шаблон.

Реализуем эти пункты

Создание ссылки в urls.py

В вебе есть понятие статических файлов - это любой файл который не изменяясь возвращается пользователю: картинки, видео, файлы css, js и т.д. По умолчанию Django не занимается обработкой таких файлов. Обычно, запросы к статическим файлам обрабатывает другая программа, например веб сервер Nginx.

В рамках разработки, в Django, предусмотрена возможность возвращать пользователю статические файлы. Добавим в файл 'url.py' ссылку по которой будет возвращаться страница с картинкой и возможность обработки загруженных файлов:

# file_project/url.py

from django.contrib import admin
from django.urls import path
from django.conf.urls.static import static
from django.conf import settings
# функция для возврата картинки
from upload_app.views import home_page

urlpatterns = [
    path('admin/', admin.site.urls),
    # Url и функция, которая вернет картинку
    path('', home_page)
]

# включаем возможность обработки картинок
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Включение поддержки загруженных файлов с DEBUG в Django

Возможность возвращать пользователю статические файлы так же связана с настройкой DEBUG в 'settings.py'. Файлы будут нормально обрабатываться, если DEBUG=True.

Вывод изображения на странице

Что бы вывести файл на странице достаточно обратить к нужному значению из базы. То есть для вывода картинки достаточно следующей функции в 'upload_app/views.py':

from django.shortcuts import render
from .models import Book


def home_page(request):
    # получаем все значения модели
    data = Book.objects.all()
    return render(request, 'home_page.html', {'data': data})

В созданной модели у меня есть 3 поля:

  • title - строка;
  • cover - картинка;
  • book - файл.

Что бы вывести их на странице я создам следующий шаблон по пути:

upload_app/templates/home_page.html
{% for b in data %}
<a href="{{ b.book.url }}">{{ b.title }}</a>
<img src="{{ b.cover.url }}">
{% endfor %}

Создание шаблона и вывод файла на странице в Django

После запуска сервера можно будет увидеть следующий результат (при наличии данных в базе):

Вывод файла и картинки в Django

Загрузка документа со страницы и его сохранение

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

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

# upload_app/views.py

from django.shortcuts import render
from django.core.files.storage import FileSystemStorage

def home_page(request):
    # POST - обязательный метод
    if request.method == 'POST' and request.FILES:
        # получаем загруженный файл
        file = request.FILES['myfile1']
        fs = FileSystemStorage()
        # сохраняем на файловой системе
        filename = fs.save(file.name, file)
        # получение адреса по которому лежит файл
        file_url = fs.url(filename)
        return render(request, 'home_page.html', {
            'file_url': file_url
        })
    return render(request, 'home_page.html')

В шаблоне 'upload_app/templates/home_pahe.html' мы должны создать форму с атрибутом enctype="multipart/form-data". Если этого атрибута не будет - 'request.FILES' будет пустым:

<!-- Метод POST и установлен enctype -->
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
  <!-- myfile1 - переменная передающееся в Django -->
  <input type="file" name="myfile1">
  <button type="submit">Загрузка</button>
</form>

<p>Ссылка: <a href="{{ file_url }}">{{ file_url }}</a></p>

Загрузка файла в Django используя request.FILES

Теперь, запустив сервер и, открыв главную страницу, у нас будет возможность загрузки файла в корень MEDIA_ROOT:

Страница вывода и загрузки файла Django

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

Book.objects.create(title='some title', cover=request.FILES['myfile1'], book=request.FILES['myfile2'])

Создание формы

Все шаги, описанные выше, можно сократить использовав существующие возможности Django. Например в Django есть формы, которые можно создать на основе существующей модели. Такие формы упрощают процесс валидации (соответствует ли тип данных Django типу в базе), представления в шаблоне (HTML теги создаются сами) и много другое. 

Формы, обычно, создаются в отдельном файле 'forms.py':

upload_app/forms.py

Для работы формы нужна модель, на основе которой можно определить поля. Поместим в этот файл следующий код:

from .models import Book
from django.forms import ModelForm


class BookForm(ModelForm):

    class Meta:
        # Название модели на основе
        # которой создается форма
        model = Book
        # Включаем все поля с модели в форму
        fields = '__all__'

Создание формы ModelForm Django

Для работы с пользователем, в случае создания объектов, в Django есть отдельный класс -  CreateView. Этот класс сам определит тип запроса ('GET' или 'POST'), проведет валидацию отправленных полей с помощью формы и сохранит значение в базе. Создадим такой класс:

# upload_app/views.py

from django.views.generic import CreateView
from .forms import BookForm

class BookCreate(CreateView):
    # Модель куда выполняется сохранение
    model = Book
    # Класс на основе которого будет валидация полей
    form_class = BookForm
    # Выведем все существующие записи на странице
    extra_context = {'books': Book.objects.all()}
    # Шаблон с помощью которого
    # будут выводиться данные
    template_name = 'book_create.html'
    # На какую страницу будет перенаправление
    # в случае успешного сохранения формы
    success_url = '/book/'

Вывод формы для загрузки файлов с CreateView в Django

В файл маршрутизации добавим маршрут, который свяжет url с новым классом:

# file_project/urls.py

...
from upload_app.views import home_page, BookCreate

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home_page),
    path('book/', BookCreate.as_view())
]
...

Регистрация url в Django

Создадим файл шаблона по пути:

upload_app/templates/book_create.html

В этот файл добавим форму и вывод существующих записей:

<!-- Устанавливаем multipart/form-data -->
<form method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    <!-- Выводим каждое поле формы в отдельном параграфе -->
    {{ form.as_p }}
    <button type="submit">Загрузка</button>
</form>

<!-- Выводим уже созданные записи -->
{% for b in books %}
<p>{{ b.title }}</p>
<img src="{{ b.cover.url }}" width="100px">
<!-- Выводим размер картинки -->
<p>Оригинальный размер картинки  {{ b.cover.width }}x{{ b.cover.height }}</p>
<br/>
<a href="{{ b.book }}">Книга</a>
{% endfor %}

Вывод картинки и ее размеров в Django

После запуска сервера эта страница будет выглядеть так:

Вывод картинки на странице в Django

...

Теги: #python #django


Каналы
Telegram FixMyPc Telegram Лента FixMyPC RSS Rss
Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, приобретённого опыта и лучшие практики в системном администрировании и программировании.