Автосоздание превью(thumbnail) для тизера из изображений в теле статьи

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

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

Когда-то мной был реализован схожий функционал для CMS Drupal  в виде модуля. С большим количеством настроек под разные хотелки. Городить же на Django нечто настолько же объемное отдельной батарейкой считаю излишеством.  Вполне можно обойтись созданием пары-тройки новых методов у модели.

Дополнительные библиотеки

Мы не будем заниматься манипуляциями, типо уменьшения,  непосредственно с изображением. Это лишняя работа, когда есть масса отлаженных и проверенных библиотек для подобных вещей. Например Easy Thumbnails. Её то мы и будем использовать в примере.

Так же нам понадобится Pillow. Куда ж без этой батарейки, если мы собираемся работать с картинками.

Поле модели для картинки-превью

Выше мы уже определились с генератором thumbnail. Этим займется Easy Thumbnails. Посему и поле для превью сделаем на его основе.

Наша тестовая модель будет выглядеть вот так:

# models.py
import re
from django.conf import settings
from django.db import models
from easy_thumbnails.fields import ThumbnailerImageField
from PIL import Image
  
class Article(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    teaserimage = ThumbnailerImageField(upload_to='images', blank=True)

Дополнительные методы модели

Парсим  body статьи на предмет изображений.

# models.py
def get_teaserimage(self):     
        '''                                                     
        Парсим текст поля body статьи на предмет наличия в оном изображений.
        Первое найденное изображение назначаем, как картинку для тизера.
        '''
        # Определяемся с размерами изображения в body, которое станет нашим превью.
        max_width = getattr(settings, 'TEASERIMAGE_MAX_WIDTH', 400)
        patern = re.compile(r'<img [^="">]*src="([^"]+)')
        images = patern.findall(self.body)
        for img in images:
            img = img.replace(settings.MEDIA_URL, '') 
            img_path = settings.MEDIA_ROOT + '/' + img 
            try:
                image = Image.open(img_path)
            except FileNotFoundError:
                continue
            # Проверяем, что изображение нужных·
            # размеров(проверка по ширине картинки).
            # Первое подходящее изображение из найденных и будем использовать.
            if image.size[0] >= max_width:
                return img 

Переопределяем метод save() модели под наши нужды.

# models.py
def save(self, *args, **kwargs):
        '''
        При каждом сохранении статьи определяем  по-новой картинку для тизера.       
        '''
        img = self.get_teaserimage()
        if img is not None:
            self.teaserimage = img
        super(Article, self).save(*args, **kwargs)

Теперь при создании новой статьи и изменении уже существующей полю teaserimage нашей модели будет присваиваться значением первая, найденная в теле статьи, картинка, соответствующая заданным размерам. Вся дальнейшая работа с этим полем в шаблоне или где-либо еще не будет отличаться от того, как если бы мы собственноручно загрузили отдельное фото для данного поля.