Django База [2023]: Оптимизация изображений, загружаемых в Django, авто-чистка медиа 🌇 #42
Django

Django База [2023]: Оптимизация изображений, загружаемых в Django, авто-чистка медиа 🌇 #42

Razilator

Django предоставляет множество инструментов для обработки медиа-файлов, но часто возникают проблемы с удалением неиспользуемых файлов, которые могут занимать много места на сервере. В этой статье мы рассмотрим модуль django-cleanup, который может автоматически удалять неиспользуемые медиа-файлы, а оптимизацию изображений мы напишем через функционал Pillow.

Установка и настройка django-cleanup

Для начала, нужно установить модуль django-cleanup, устанавливаем через терминал: pip install django-cleanup

Для использования django-cleanup нужно добавить его в список INSTALLED_APPS в файле settings.py:

backend/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sites',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sitemaps',
    'modules.blog.apps.BlogConfig',
    'modules.system.apps.SystemConfig',
    'mptt',
    'debug_toolbar',
    'taggit',
    'captcha',
    'django_ckeditor_5',
    'django_cleanup',
]

Теперь все неиспользуемые медиа-файлы будут автоматически удалены при вызове метода delete() у объекта модели.

Ресайзинг и оптимизация изображения в Django

В нашем модуле services, в файле utils.py, который мы создавали ранее в этом уроке, создадим функционал для оптимизации изображения (перед этим у нас должна быть установлена библиотека Pillow):

modules/services/utils.py
from PIL import Image, ImageOps


def image_compress(image_path, height, width):
    """
    Оптимизация изображений
    """
    img = Image.open(image_path)
    if img.mode != 'RGB':
        img = img.convert('RGB')
    if img.height > height or img.width > width:
        output_size = (height, width)
        img.thumbnail(output_size)
    img = ImageOps.exif_transpose(img)
    img.save(image_path, format='JPEG', quality=90, optimize=True)

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

  • Image.open(image_path) - открывает изображение, указанное в переменной image_path при помощи библиотеки PIL (Python Imaging Library).
  • if img.mode != 'RGB': img = img.convert('RGB') - проверяет, что изображение имеет формат RGB. Если это не так, то изображение преобразуется в RGB.
  • if img.height > height or img.width > width: - проверяет, превышает ли высота или ширина изображения указанные значения. Если да, то изображение будет пропорционально уменьшено до размеров height и width с сохранением соотношения сторон.
  • output_size = (height, width) img.thumbnail(output_size) - уменьшает размер изображения, если оно превышает указанные размеры, сохраняя при этом соотношение сторон.
  • img = ImageOps.exif_transpose(img) - определяет ориентацию изображения и преобразует его в соответствии с EXIF-данными, чтобы изображение отображалось правильно.
  • img.save(image_path, format='JPEG', quality=90, optimize=True) - сохраняет оптимизированное изображение в указанном пути image_path в формате JPEG с качеством 90 и включенной оптимизацией.

Далее добавим функционал в модель Article, в нашем приложении blog, в файле models.py:

modules/blog/models.py
from django.db import models

from modules.services.utils import unique_slugify, image_compress

class Article(models.Model):
    """
    Модель постов для сайта
    """
    
    title = models.CharField(verbose_name='Заголовок', max_length=255)
    slug = models.SlugField(verbose_name='Альт.название', max_length=255, blank=True, unique=True)
    
    # Другие поля...
    
    # Другие методы...

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__thumbnail = self.thumbnail if self.pk else None

    def save(self, *args, **kwargs):
        """
        Сохранение полей модели при их отсутствии заполнения
        """
        if not self.slug:
            self.slug = unique_slugify(self, self.title)
        super().save(*args, **kwargs)

        if self.__thumbnail != self.thumbnail and self.thumbnail:
            image_compress(self.thumbnail.path, width=500, height=500)

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

Также мы оптимизируем изображение с помощью функции image_comporess(), в которую передаем путь изображения, длину и высоту в пикселях.

Проверка работы функционала

У статьи выбираем очистить изображение
У статьи выбираем очистить изображение
При сохранении у нас удаляется изображение как со статьи, так и из хранилища media
При сохранении у нас удаляется изображение как со статьи, так и из хранилища media
Проверяем оптимизацию изображения, загрузим это оригинальное изображение с размером 222кб
Проверяем оптимизацию изображения, загрузим это оригинальное изображение с размером 222кб
Загрузим его в статью, и пройдя оптимизацию мы уменьшим его размер до указанных 500x500, а также получим размер в 28.5 кб
Загрузим его в статью, и пройдя оптимизацию мы уменьшим его размер до указанных 500x500, а также получим размер в 28.5 кб
;