Files
2018-lpon-site/agent-reports/20260707_2109_migration_tbimage_to_imagemetadata.md
T

28 KiB
Raw Blame History

План миграции: TbImage → ImageMetadata (Filer Extension)

Дата: 2026-07-07
Время: 21:09
Автор: Copilot CLI
Статус: В планировании


Обзор

Переконфигурирование архитектуры работы с изображениями:

  • Текущее состояние: Модели ссылаются на TbImage, которая ссылается на FilerImageField
  • Целевое состояние: Модели ссылаются прямо на filer.Image, дополнительные метаданные хранятся в переименованной TbImage (→ ImageMetadata)

Фаза 1: Подготовка и анализ

Задача 1.1: Аудит использования TbImage

  • Найти все FK на TbImage в моделях
  • Найти все M2M связи на TbImage (см. TbOffer.k_offer_to_image)
  • Найти все места в админке, которые работают с TbImage
  • Найти все места в парсерах/сигналах, которые создают/обновляют TbImage
  • Документировать каждое найденное место

Задача 1.2: Анализ filer.Image структуры

  • Проверить какие даты есть в filer.Image модели (modified_at, uploaded_at или их эквиваленты)
  • Проверить есть ли related_name у FilerImageField в TbImage или он будет auto-generated
  • Убедиться что filer работает с сигналами при изменении файла

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


Фаза 2: Модификация моделей

Задача 2.1: Переименование и упрощение TbImageMetadata

Архитектура: МИНИМАЛИЗМ + JSON для гибкости

  • Переименовать класс TbImage → TbImageMetadata

  • Добавить/проверить related_name='metadata' на FilerImageField

    image = FilerImageField(
        ...,
        related_name='metadata'  # ← для доступа: filer_image.metadata
    )
    
  • ОСТАВИТЬ в TbImageMetadata:

    • image (OneToOneField → filer.Image)
    • i_img_sort (IntegerField, db_index=True) ← ТОЛЬКО это индексируется!
    • j_img_metadata (JSONField, null=True, blank=True) ← всё остальное сюда
  • УДАЛИТЬ ВСЕ остальные поля:

    • l_img_source (будет в JSON если нужно)
    • l_img_reality (дефолт по контексту: TbItem→abstract, TbOffer админка→real, иное→abstract)
    • s_img_src_url (в JSON если нужно)
    • f_img_confidence_score (в JSON если нужно, парсеры его заполнят)
    • t_img_created (используем filer.File.uploaded_at)
    • t_img_updated (используем filer.File.modified_at)
  • Обновить Meta.verbose_name на "Метаданные изображения"

  • Обновить Meta.ordering — просто ('i_img_sort',)

ПОЧЕМУ?

  • Парсеров еще нет, не знаем какие поля реально нужны
  • Справочные поля (источник, тип, достоверность) не нужны для индексации/фильтрации
  • JSON дает гибкость: позже когда парсеры появятся, просто добавим новые ключи или выносим в таблицу если нужен индекс
  • YAGNI: не создаем сложность пока она не нужна

Задача 2.2: Обновление FK на изображения во всех моделях

  • TbArticle: k_article_to_image — ForeignKey(TbImage) → ForeignKey(filer.Image)
  • TbItem: добавить k_item_to_image — ForeignKey(filer.Image, blank=True, null=True)
    • Обложка альбома/релиза (всегда l_img_reality = 'abstract')
    • Дефолтное значение при создании ImageMetadata: l_img_reality = 'abstract'
  • TbOffer: M2M k_offer_to_image — на filer.Image вместо TbImage
    • Может переиспользовать картинку из TbItem или иметь свою
    • В админке: l_img_reality может быть 'real' (реальная фото товара) или 'abstract' (из каталога)
    • При создании через админку: дефолт l_img_reality = 'real' (человек загружает реальные фото)
    • При создании через парсер: l_img_reality = 'abstract' (парсер берет с Discogs и т.д.)
  • [Проверить другие модели] — все остальные модели с FK на картинки
  • Для каждого изменения:
    • Обновить саму модель
    • Создать Django миграцию
    • Обновить админку (инлайны)

Задача 2.3: Создание миграции данных

  • Миграция для переименования таблицы (TbImage → ImageMetadata или оставить таблицу, но переименовать модель?)
  • Миграция для удаления полей t_img_created, t_img_updated из ImageMetadata
  • Обработка сирот (ImageMetadata без соответствующего filer.Image) — удалить или связать

Важно: Порядок миграций критичен! Сначала добавляем новые FK/M2M, потом удаляем старые.


Фаза 3: Сигналы и автоматизация

Задача 3.1: Signal на создание filer.Image → auto-create TbImageMetadata

# В apps.py
@receiver(post_save, sender=filer.models.Image)
def auto_create_image_metadata(sender, instance, created, **kwargs):
    """При создании filer.Image автоматически создавать TbImageMetadata"""
    if created:
        TbImageMetadata.objects.get_or_create(
            image=instance,
            defaults={
                'i_img_sort': 0,
                # j_img_metadata оставляем пустым, заполняется по мере необходимости
            }
        )

Задача 3.2: Обратный сигнал — синхронизация дат (опционально)

# В apps.py — ЕСЛИ нужно обновлять modified_at в filer при изменении i_img_sort
@receiver(post_save, sender=TbImageMetadata)
def sync_image_modification_date(sender, instance, **kwargs):
    """При обновлении i_img_sort обновляем modified_at в filer.File"""
    # Обновляем filer.File.modified_at через SQL update (без повторного save()!)
    from filer.models import File
    File.objects.filter(id=instance.image.id).update(modified_at=now())

ПРИМЕЧАНИЕ: Сигнал опционален. Если не нужно обновлять дату каждый раз при изменении сортировки — можно пропустить.

Задача 3.3: Сигнал на удаление

  • При удалении ImageMetadata → не удаляем filer.Image (метаданные удаляются, файл остаётся)
  • При удалении filer.Image → каскадно удаляем ImageMetadata (on_delete=models.CASCADE)

Фаза 4: Админка и UI

Задача 4.1: Создание ImageMetadataAdminMixin

class ImageMetadataAdminMixin:
    """Миксин для админок, содержащих связь с filer.Image"""
    # Автоматически добавлять инлайн ImageMetadata при наличии FK на Image

Задача 4.2: Создание TbImageMetadataInline

  • StackedInline для TbImageMetadata
  • Поля: i_img_sort, j_img_metadata
  • j_img_metadata как JSON editor (админка Django поддерживает JSONField)
  • Всё просто и минималистично

Задача 4.3: Обновление админок моделей

  • TbArticleAdmin — добавить TbImageMetadataAdminMixin
  • TbItemAdmin — добавить мixin для обложки
    • Поле k_item_to_image с инлайном TbImageMetadata
    • i_img_sort редактируемо
    • j_img_metadata может содержать замечания ("абстрактная", "с Discogs" и т.д.) если нужно
  • TbOfferAdmin — добавить мixin для картинок товара
    • M2M к filer.Image с инлайном TbImageMetadata
    • i_img_sort редактируемо (для сортировки фото товара)
    • j_img_metadata редактируемо (админ может добавить замечания)
  • [Все остальные администраторы с изображениями]

Фаза 5: Парсеры и интеграция

Задача 5.1: Подготовка к парсерам (FUTURE-PROOF)

  • Найти все парсеры (их еще нет, но логика готова)
  • Парсеры смогут заполнять j_img_metadata с любыми данными:
    {
      "source": "discogs",
      "reality": "abstract",
      "confidence": 0.95,
      "original_url": "https://...",
      "notes": "..."
    }
    
  • i_img_sort может устанавливаться парсером если нужна специальная сортировка
  • Дефолтная логика (без парсера):
    • TbItem → картинка? → j_img_metadata = {"reality": "abstract"}
    • TbOffer + админка → картинка? → j_img_metadata пустой или {}
    • На фронте: если не указано → apply defaults

Задача 5.2: Миграция существующих данных (ТЫ ДЕЛАЕШЬ)

  • Перенести все TbImage в TbImageMetadata (переименование таблицы)
  • Заполнить i_img_sort из старого поля (если было)
  • j_img_metadata заполнить на основе старых полей если они важны
    {
      "old_l_img_source": "...",
      "old_l_img_reality": "...",
      "old_confidence": "..."
    }
    
  • Или просто пусто, парсеры потом заполнят правильно

Фаза 6: Тестирование и валидация

Задача 6.1: Unit тесты

  • Тест создания filer.Image → автоматическое создание ImageMetadata
  • Тест обновления ImageMetadata → синхронизация дат в filer.Image
  • Тест удаления ImageMetadata → filer.Image остаётся
  • Тест удаления filer.Image → каскадное удаление ImageMetadata

Задача 6.2: Интеграционные тесты

  • Admin interface — создание/редактирование с инлайном
  • Парсеры — создание корректных записей
  • Шаблоны — корректный доступ к метаданным через image.metadata.i_img_sort

Задача 6.3: Миграция данных (если база содержит данные)

  • Запустить миграции
  • Проверить целостность данных
  • Проверить что все ImageMetadata связаны с filer.Image
  • Проверить обратные связи работают

Фаза 7: Финализация

Задача 7.1: Удаление старого кода

  • Удалить все ссылки на старый TbImage (если полностью переименовали на ImageMetadata)
  • Очистить импорты
  • Обновить комментарии и документацию

Задача 7.2: Документирование

  • Обновить ER-диаграмму в моделях
  • Описать новую архитектуру в README или docs
  • Примеры использования для разработчиков

Задача 7.3: Развёртывание

  • Code review
  • Тестирование на staging
  • Развёртывание на production с миграциями
  • Мониторинг логов на ошибки

Потенциальные ловушки

⚠️ Определение источника создания ImageMetadata (админка vs парсер)
Решение: добавить context или flag в сигнал, или использовать user_id в сигнале (если user_id = None → парсер, иначе админка)

⚠️ TbItem.l_img_reality всегда должна быть 'abstract'
Решение: signal принудительно устанавливает и делает read-only в админке

⚠️ TbOffer может иметь как свои картинки, так и наследовать из TbItem
Решение: M2M на filer.Image, админ может выбирать любые картинки и устанавливать l_img_reality на месте


Оценка сложности

Фаза Сложность Время (ч) Приоритет
1 Низкая 0.5 Высокий
2 Средняя 2-3 Высокий
3 Средняя 1-2 Высокий
4 Низкая 1 Высокий
5 Средняя 1-2 Высокий
6 Средняя 1-2 Средний
7 Низкая 1 Средний

Итого: ~8-13 часов


Уточнения из ответов

1. Модель называется: TbImageMetadata (переименуем TbImage)
2. Про performa & filer:

  • filer имеет две таблицы: File + Image (Image extends File через OneToOne)
  • modified_at находится в таблице File (базовая для всех файлов)
  • TbImageMetadata расширяет Image через OneToOne = это ОК, перформанс не критичен
  • Риск цикличности НИЗКИЙ: обновляем ТОЛЬКО дату, не весь объект

3. M2M на TbOffer:

  • k_offer_to_image это M2M (Django автоматически создает промежуточную таблицу)
  • ОК, оставляем M2M

4. Парсеры:

  • Создаются ПОЗЖЕ (в отдельном спринте)
  • Сейчас делаем инфраструктуру для них
  • Парсеры будут работать с filer напрямую, заполнять ImageMetadata вручную (или через свои сигналы позже)

5. Цикличность:

  • Филер обновляет modified_at при изменении файла — ОК, автоматическое
  • Мы обновляем modified_at при изменении метаданных — ТОЛЬКО дату, не вызовет cascade
  • Цикличности НЕ будет если правильно реализовать сигнал (через update(), не save())

6. Определение источника сигнала (админка vs парсер):

  • Решение: Передавать через using параметр или context manager в signal'е
  • Для парсеров: Парсеры могут создавать ImageMetadata вручную (без сигналов) или отключать сигналы
  • Для админки: Сигналы работают стандартно

7. Миграцию делаешь сам — ясно!


ФИНАЛЬНЫЙ ПЛАН: ПОРЯДОК РЕАЛИЗАЦИИ

ЭТАП 1: ПОДГОТОВКА И МОДЕЛИ (БЕЗ МИГРАЦИЙ)

Шаг 1.1: Обновить модель TbImage → TbImageMetadata

  • Переименовать класс с TbImage на TbImageMetadata
  • Добавить related_name='metadata' на FilerImageField ← ВСТРЕЧНЫЙ ИНДЕКС! Упростит миграции
  • ПОКА ОСТАВИТЬ t_img_created, t_img_updated (удалим после миграции данных)
  • НЕ создавать миграцию на этом этапе

Шаг 1.2: Обновить все FK на картинки в моделях

  • TbArticle: k_article_to_image ForeignKey(TbImage) → ForeignKey(filer.Image)
  • TbItem: добавить новое поле k_item_to_image = ForeignKey(filer.Image, blank=True, null=True)
  • TbOffer: k_offer_to_image M2M(TbImage) → M2M(filer.Image)
  • Все остальные модели с картинками
  • НЕ создавать миграции на этом этапе! Только обновляем код

Шаг 1.3: Обновить админку (без инлайнов пока)

  • Обновить все FieldChoices в админке на новые FK
  • НЕ добавлять ImageMetadataInline пока!
  • НЕ добавлять миксин пока!
  • Просто чтобы админка не ломалась с новыми FK

РЕЗУЛЬТАТ: Код скомпилирован, но БД не трогали. Всё готово к миграциям.


ЭТАП 2: МИГРАЦИИ (ТЫ ДЕЛАЕШЬ САМИ)

Шаг 2.1: Создать миграции Django

  • Миграция на переименование класса TbImage → TbImageMetadata (если нужно переименовать таблицу, то custom миграция)
  • Миграция на удаление старых FK из TbArticle, TbOffer и т.д.
  • Миграция на добавление новых FK/M2M на filer.Image
  • Миграция на добавление новых полей (TbItem.k_item_to_image и т.д.)

Шаг 2.2: Миграция данных

  • Перенести существующие картинки из TbImage в filer.Image (если нужно)
  • Привязать существующие записи к новым FK
  • Или: Просто очистить TbImage и начать с нуля (если картинок мало)

Шаг 2.3: Удалить t_img_created и t_img_updated ПОСЛЕ успешной миграции

  • Отдельная миграция на удаление этих двух полей
  • Применяется ПОСЛЕ того как все данные перенесены и связи обновлены
  • ПОРЯДОК ВАЖЕН: сначала новые FK/M2M, потом удаляем старые поля

РЕЗУЛЬТАТ: БД в новом состоянии, приложение рабочее (но без автоматизации через сигналы).


ЭТАП 3: СИГНАЛЫ И АДМИНКА (АВТОМАТИЗАЦИЯ)

Шаг 3.1: Создать Signal для auto-create TbImageMetadata

Когда: post_save filer.Image, created=True
Делает: автоматически создавать TbImageMetadata с дефолтами
Дефолты: i_img_sort=0, l_img_source='manual', l_img_reality зависит от контекста

Шаг 3.2: Создать Signal для синхронизации дат

Когда: post_save TbImageMetadata
Делает: обновляет filer.File.modified_at через SQL update (БЕЗ save()!)
Важно: modified_at из File, а не Image!

Шаг 3.3: Создать миксин для админки

ImageMetadataAdminMixin: автоматически добавляет инлайн для TbImageMetadata
Применяется к: TbArticleAdmin, TbOfferAdmin, и всем остальным

Шаг 3.4: Обновить админку с инлайнами

  • TbArticleAdmin добавить миксин
  • TbItemAdmin добавить миксин + читать-только l_img_reality для обложек
  • TbOfferAdmin добавить миксин + M2M выбор картинок + l_img_reality редактируемо
  • Остальные модели с картинками

РЕЗУЛЬТАТ: Всё работает автоматически. Юзер создает/редактирует картинки — метаданные появляются и синхронизируются.


ЭТАП 4: ПОДГОТОВКА К ПАРСЕРАМ (FUTURE-PROOF)

Шаг 4.1: Документировать как парсерам использовать новую архитектуру

  • Парсеры создают filer.Image напрямую
  • TbImageMetadata автоматически создается через сигнал
  • Парсеры могут переопределять значения через direct update

Шаг 4.2: Создать helper для парсеров (optional)

Функция: create_image_from_url(url, l_img_source, l_img_reality, ...)
Делает: загружает в filer.Image + заполняет TbImageMetadata правильно
Это обычно нужно потом, но архитектура к этому готова

Шаг 4.3: Отключение сигналов для парсеров (если нужно)

Если парсер хочет создавать без сигналов:
- Парсер может установить какой-то флаг (например, signals.disconnect())
- Или парсер создает все сам без сигналов
- ИЛИ передавать context через signal'ы (позже если понадобится)

РЕЗУЛЬТАТ: Архитектура готова для будущих парсеров. Они смогут работать либо через сигналы, либо вручную.


УТОЧНЕНИЕ: КАРТИНКИ ТОЛЬКО В TBIMAGE

Важный факт: Все картинки сейчас в filer привязаны ТОЛЬКО к TbImage, больше никуда не привязаны.

Это упрощает миграции!

  • Нет данных в других моделях которые нужно конвертировать
  • related_name='metadata' на FilerImageField даст встречный доступ: filer_image.metadata
  • Миграция будет проще: просто переименовать таблицу, добавить новые FK структуры, готово

Шаги миграции будут:

  1. Переименовать TbImage → TbImageMetadata (в таблице)
  2. Добавить новые поля для TbItem.k_item_to_image
  3. Очистить/переинициализировать существующие данные если нужно
  4. ПОСЛЕ всего этого — удалить t_img_created, t_img_updated

ОТВЕТ НА ВОПРОС: КОГДА УДАЛЯТЬ t_img_created И t_img_updated?

Правильный порядок:

  1. Этап 1 (код): Оставить поля в коде модели (пока!)
  2. Этап 2 (миграции) — ПЕРВЫЕ миграции:
    • Переименование TbImage → TbImageMetadata
    • Добавление новых FK структур
    • Миграция данных
  3. Этап 2 (миграции) — ПОСЛЕДНЯЯ миграция:
    • Удалить t_img_created и t_img_updated из БД
  4. ПОТОМ, Этап 1 (код): Удалить эти поля из кода модели

ПОЧЕМУ такой порядок?

  • Сначала применяем миграции НОВЫХ структур — убеждаемся что всё работает
  • ПОТОМ удаляем старые поля — когда новая структура стабильна
  • Если что-то пошло не так — есть откат через старые поля

ПРАКТИЧЕСКИ:

  • Этап 1: TbImageMetadata всё ещё имеет t_img_created, t_img_updated в коде
  • Ты создаёшь первую миграцию (переименование, новые FK)
  • Ты создаёшь вторую миграцию (удаление дат из БД)
  • Применяешь обе
  • ПОТОМ я удаляю эти поля из самого кода модели (простое удаление из файла)
  1. ЭТАП 1 (2-3 часа) — Обновить код моделей и админки, БД не трогаем
  2. ЭТАП 2 (30 мин - 2 часа) — Написать миграции и применить их (ты делаешь!)
  3. ЭТАП 3 (2-3 часа) — Написать сигналы, админка миксины, протестировать
  4. ЭТАП 4 (30 мин) — Документировать для будущих парсеров

ОБЩЕЕ ВРЕМЯ: ~7-9 часов


НОВАЯ АРХИТЕКТУРА: МИНИМАЛИЗМ + JSON

TbImageMetadata содержит:

  • image (OneToOne → filer.Image)
  • i_img_sort (IntegerField, db_index=True) ← ТОЛЬКО поле с индексом!
  • j_img_metadata (JSONField, null=True) ← Всё остальное сюда

Почему?

  • Парсеров еще нет → не знаем какие поля реально нужны
  • Справочные данные не используются для фильтрации/индексации
  • JSON гибко хранит всё: источник, тип, достоверность, URL и т.д.
  • Если потом парсеры потребуют индексы → просто выносим поле из JSON
  • Дефолтная логика: TbItem → abstract, TbOffer админка → любое значение

Пример j_img_metadata:

{
  "source": "parser",
  "reality": "abstract",
  "confidence": 0.95,
  "original_url": "https://discogs.com/...",
  "notes": "Обложка из каталога"
}

ИТОГОВЫЙ ПОРЯДОК РЕАЛИЗАЦИИ (ОБНОВЛЕНО)

🔑 Этап 1 БЕЗ МИГРАЦИЙ — сначала весь код готовый, потом БД меняем
🔑 Этап 2 ТЫ ДЕЛАЕШЬ — я не трогаю, ты контролируешь миграции
🔑 Архитектура МИНИМАЛИСТИЧНА — только i_img_sort индексируется, всё остальное в JSON
🔑 Гибкость МАКСИМАЛЬНАЯ — JSON позволяет добавлять любые данные без миграций БД
🔑 Парсеры готовы — при создании просто заполняют j_img_metadata как нужно
🔑 Дефолтная логика — на фронте если метаданных нет → применяем defaults по контексту


Готов начинать с ЭТАПА 1? Или еще вопросы/уточнения?