Добавление картинок к постам

Django — это огромная экосистема из всевозможных модулей, расширяющих возможности базового фреймворка. На сайте PyPI.org размещены десятки тысяч расширений, в названии которых есть слово django, а неопубликованных модулей или тех, что названы как-то иначе — ещё больше.
Пустим в дело богатства экосистемы Django: подключим к нашему проекту управление изображениями.

sorl-thumbnail

В стандартную установку Django встроен инструмент для работы с картинками, но задачи вроде изменения размера изображений ему не по силам. Django умеет только загружать файлы и отдавать их как есть.
Для настоящей соцсети этого мало. Пользователи могут залить RAW-картинку с фотоаппарата размером в 5950×3968 пикселей и весом в 50 Мб, или выложить картинку-мем размером в 120х80. Если опубликовать эти картинки как есть — сайт будет выглядеть неопрятно или долго загружаться.
Дадим пользователям возможность иллюстрировать посты и сделаем так, чтобы загруженные изображения выглядели более-менее одинаково.
Одно из первых по популярности и удобству приложений для работы с графикой — sorl-thumbnail. Для его работы нужна графическая библиотека, sorl-thumbnail умеет работать со многими, мы возьмём библиотеку Pillow.
Когда-то, когда Python еще набирал популярность, в компании Secret Labs AB написали библиотеку PIL (от Python Imaging Library). Это была одна из первых графических библиотек, предназначенных именно для Python. Она быстро стала стандартом в сообществе. Некоторое время спустя компания перестала существовать и развивать библиотеку: последняя версия PIL ориентирована на Python v.2.3. Но потребность в обработке изображений никуда не пропала, и Alex Clark сделал форк (ответвление проекта) под новым названием Pillow (англ. «подушка»). Очевидно, что автор дал библиотеке название, включающее буквы pil, в честь старого проекта, а не потому, что любит поспать. Новая библиотека поддерживает совместимость и для старых проектов.
Для установки Pillow выполните команду в виртуальном окружении проекта:
Скопировать код
(venv) $ pip install Pillow
Возможно, вам понадобится установить дополнительные библиотеки для вашей операционной системы.
Теперь установите приложение sorl-thumbnail:
Скопировать код
(venv) $ pip install sorl-thumbnail
Добавьте приложение в список INSTALLED_APPS, в конец списка:
Скопировать кодPYTHON
INSTALLED_APPS = [ # ... 'sorl.thumbnail', ]
Выполните миграцию, и после этого приложение будет готово к работе.
Теперь вам станут доступны специальные теги в шаблонах:
Скопировать код
<!-- Загрузка тегов библиотеки в шаблон --> {% load thumbnail %} <!-- Пример использования тега для пропорционального уменьшения и обрезки картинки до размера 100x100px с центрированием --> <!-- и вставляет в код изображение которое отобразится пользователю. --> {% thumbnail item.image "100x100" crop="center" as im %} <img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"> {% endthumbnail %}

Настройки проекта

Настроим модель Post так, чтобы к посту можно было добавить заглавную картинку:
Скопировать кодPYHTON
class Post(models.Model): text = models.TextField() pub_date = models.DateTimeField('Дата публикации', auto_now_add=True) author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author_posts') group = models.ForeignKey( Group, on_delete=models.CASCADE, related_name='group_posts', blank=True, null=True ) # поле для картинки image = models.ImageField(upload_to='posts/', blank=True, null=True)
Аргумент upload_to указывает, куда должны загружаться пользовательские файлы.
Теперь добавьте новое поле в форму, связанную с моделью:
Скопировать кодPYTHON
from django import forms from .models import Post class PostForm(forms.ModelForm): class Meta: model = Post fields = ['group', 'text', 'image']
Путь к директории для загрузки изображений указывается относительно адреса в параметре конфигурации MEDIA_ROOT, который должен содержать полный путь к директории и не должен совпадать с директорией STATIC_ROOT. Добавьте следующие строки в файл settings.py:
Скопировать кодPYTHON
# Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Есть еще один нюанс настройки сервера. Мы добавляем возможность загрузки файлов пользователями, а значит, эти файлы должны быть доступны для просмотра в режиме разработки. Поэтому добавьте в файл yatube/urls.py следующий код:
Скопировать кодPYTHON
# эти строки — в начало файла, рядом с импортом других модулей from django.conf import settings from django.conf.urls.static import static # эти строки — в самый конец файла if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Этот код будет работать, когда ваш сайт в режиме отладки. Он позволяет обращаться файлам в директории, указанной в MEDIA_ROOT по имени, через префикс MEDIA_URL.
Не забудьте создать директорию media, добавить её в .gitignore и обновить связанную с постом форму.

Обновление шаблона

Чтобы изображение показывалось на странице сайта, измените шаблон страницы записи. Добавьте вывод картинок в post.html:
Скопировать кодHTML
<div class="card mb-3 mt-1 shadow-sm"> {% load thumbnail %} {% thumbnail post.image "960x339" crop="center" upscale=True as im %} <img class="card-img" src="{{ im.url }}"> {% endthumbnail %} <div class="card-body"> ...
Если в посте нет картинки, то содержимое тега thumbnail будет проигнорировано, так что проверку {% if post.image %}...{% endif %} делать не надо.
В HTML-форме создания и редактирования поста появится поле для загрузки изображения. Форма должна понимать, что из неё на сервер будут передаваться файлы. Обновите шаблон с формой — в тег <form> добавьте атрибут enctype:
Скопировать кодHTML
<form method="post" enctype="multipart/form-data">

Обновление view-функции

Осталось подправить функцию редактирования записи. Django-формы умеют работать с файлами, так что нужно лишь передать дополнительный параметр files=request.FILES or None, и больше ничего! Вам не надо отдельно сохранять файлы, не надо проверять их тип или беспокоиться, что в директории загрузки окажется файл с таким же именем (Django сам переименует файл при необходимости):
Скопировать кодPYHTON
@login_required def post_edit(request, username, post_id): profile = get_object_or_404(User, username=username) post = get_object_or_404(Post, pk=post_id, author=profile) if request.user != profile: return redirect('post', username=username, post_id=post_id) # добавим в form свойство files form = PostForm(request.POST or None, files=request.FILES or None, instance=post) if request.method == 'POST': if form.is_valid(): form.save() return redirect("post", username=request.user.username, post_id=post_id) return render( request, 'post_edit.html', {'form': form, 'post': post}, )

Результат

У вас должна получиться страница просмотра записи с картинкой:
image

Задание

1. Выведите иллюстрации к постам:

  • в шаблон главной страницы
  • в шаблон профайла автора
  • в шаблон страницы группы
Команду {% load thumbnail %} достаточно выполнить один раз в начале файла.

2. Напишите тесты, которые:

  • проверяют страницу конкретной записи с картинкой: на странице есть тег <img>
  • проверяют, что на главной странице, на странице профайла и на странице группы пост с картинкой отображается корректно, с тегом <img>
  • проверяют, что срабатывает защита от загрузки файлов не-графических форматов
Чтобы в тестах проверить загрузку файла на сервер, нужно отправить файл с помощью тестового клиента:
Скопировать кодPYTHON
>>> with open('posts/file.jpg','rb') as img: ... post = self.client.post("<username>/<int:post_id>/edit/", {'author': self.user, 'text': 'post with image', 'image': img})
Подсказка
Для проверки защиты от загрузки «неправильных» файлов достаточно протестировать загрузку на одном «неграфическом» файле: тест покажет, срабатывает ли система защиты.