minor: переименование
This commit is contained in:
@@ -0,0 +1,595 @@
|
||||
# Паттерн создания справочников: автоматизация статей, синонимов и валидации
|
||||
|
||||
**Дата:** 2026-07-15
|
||||
**Статус:** Актуально ✓
|
||||
|
||||
## Обзор
|
||||
|
||||
Система содержит 5 **справочных сущностей** (Reference Entities) которые описывают каталог:
|
||||
|
||||
| Модель | Таблица | Назначение | Связь с TbArticle |
|
||||
|-------------|--------------|------------------------------------------|-------------------|
|
||||
| Лейбл | TbLabel | Издатель, лейбл, дистрибьютор | 1:1 OneToOne |
|
||||
| Исполнитель | TbArtist | Артист, группа, бренд | 1:1 OneToOne |
|
||||
| Товар | TbItem | Альбом, релиз, носитель, аксессуар | 1:1 OneToOne |
|
||||
| Стиль | TbMusicStyle | Музыкальный жанр | 1:1 OneToOne |
|
||||
| Продавец | TbSeller | Магазин, продавец, издатель-дистрибьютор | 1:1 OneToOne |
|
||||
|
||||
Все 5 моделей используют **единый паттерн**:
|
||||
1. Автоматическое создание связанной статьи (если нет)
|
||||
2. Управление синонимами в метаданных
|
||||
3. Валидация дубликатов через админку
|
||||
4. Подготовка к парсингу с очередью конфликтов
|
||||
|
||||
## Архитектура: TbArticle как связующее звено
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ TbArticle │
|
||||
│ (текст, SEO, картинка, слаг — для всех справочников) │
|
||||
│ │
|
||||
│ Поля: │
|
||||
│ • s_article_title: "Label: Sony" │
|
||||
│ • l_article_type: "label" │
|
||||
│ • slug: "label-sony" (URL-идентификатор) │
|
||||
│ • k_article_to_image: ссылка на картинку (логотип, фото) │
|
||||
│ • seo_title, seo_description: для поисковиков и llm │
|
||||
│ • s_article_teaser_html, s_article_content_html: текст │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
▲ ▲ ▲ ▲
|
||||
│ │ │ │
|
||||
1:1 1:1 1:1 1:1
|
||||
│ │ │ │
|
||||
┌────────┴────┐ ┌────────┴─────┐ ┌────────┴────┐ ┌──┴──────────┐
|
||||
│ TbLabel │ │ TbArtist │ │ TbItem │ │ TbMusicStyle│
|
||||
│ │ │ │ │ │ │ │
|
||||
│ s_label │ │ s_artist │ │ s_item │ │ s_style_name│
|
||||
│ j_metadata │ │ j_metadata │ │ j_metadata │ │ j_metadata │
|
||||
│ (синонимы) │ │ (синонимы) │ │ (синонимы) │ │ (синонимы) │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘ └─────────────┘
|
||||
|
||||
TbSeller аналогично, без синонимов (используется редко)
|
||||
```
|
||||
|
||||
## Жизненный цикл справочника (на примере TbLabel)
|
||||
|
||||
### Фаза 1: Создание в админке
|
||||
|
||||
```python
|
||||
# Админ создает новый лейбл через Django Admin форму
|
||||
# Заполняет: s_label="Sony Music", j_label_metadata={"SYN_EN": [...]}
|
||||
|
||||
# При сохранении форма вызывает label.save()
|
||||
```
|
||||
|
||||
### Фаза 2: Валидация синонимов (админка)
|
||||
|
||||
```python
|
||||
# Функция validate_for_duplicates() проверяет:
|
||||
|
||||
# 1. EXACT_MATCH: s_label == s_label в других записях?
|
||||
# Результат: "Sony Music" уже есть в базе
|
||||
# Действие: Форма показывает ошибку, блокирует сохранение
|
||||
|
||||
# 2. FIND_IN_SYNONYM: s_label найден в синонимах других записей?
|
||||
# Результат: "Sony Music" найден в синонимах записи TbLabel(pk=5)
|
||||
# Действие: Админ видит список дубликатов, решает что делать
|
||||
|
||||
# 3. EXACT_SYNONYM_MATCH: синонимы разных лейблов совпадают?
|
||||
# Результат: {"SYN_EN": ["Sony", "Sony Music"]} уже есть в записи TbLabel(pk=3)
|
||||
# Действие: Ошибка, нужно исправить синонимы
|
||||
#
|
||||
# 4. PARTIAL_MATCH: частичное совпадение синонимов
|
||||
# Не критичные совпадения. s_label и синонимы из метаданных разбиваются по словам, ищутся совпадения слов.
|
||||
# Админ видит предупреждение. Для коротких слов (Inc, Ltd т.п.) может много ложных срабатываний. Админу показывается
|
||||
# список слов-совпадений, он с чем они совпали, и решает что делать.
|
||||
```
|
||||
|
||||
### Фаза 3: Автоматическое создание статьи
|
||||
|
||||
```python
|
||||
# Если админ одобрил - вызывается label.save()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Шаг 1: Валидация синонимов
|
||||
validate_and_raise_for_duplicates(self, 's_label', 'j_label_metadata')
|
||||
|
||||
# Шаг 2: Обновление синонимов метаданных
|
||||
update_synonyms_in_metadata(self, 's_label', 'j_label_metadata')
|
||||
|
||||
# Шаг 3: Создание или получение связанной статьи
|
||||
article = create_or_get_related_article(
|
||||
self,
|
||||
TbArticle.ArticleType.LABEL, # тип статьи
|
||||
's_label', # основное поле
|
||||
'j_label_metadata', # метаданные
|
||||
'k_label_to_article' # FK на статью
|
||||
)
|
||||
self.k_label_to_article = article
|
||||
|
||||
# Шаг 4: Сохранение
|
||||
super().save(*args, **kwargs)
|
||||
```
|
||||
|
||||
**Что делает `create_or_get_related_article()`:**
|
||||
|
||||
```python
|
||||
def create_or_get_related_article(
|
||||
instance, # TbLabel, TbArtist и т.д.
|
||||
article_type, # TbArticle.ArticleType.LABEL
|
||||
main_field_name, # 's_label'
|
||||
metadata_field_name, # 'j_label_metadata'
|
||||
fk_field_name # 'k_label_to_article'
|
||||
):
|
||||
# 1. Генерируем название для статьи
|
||||
article_title = f"Label: {instance.s_label}"
|
||||
|
||||
# 2. Ищем существующую статью
|
||||
article = TbArticle.objects.filter(
|
||||
s_article_title=article_title,
|
||||
l_article_type=article_type
|
||||
).first()
|
||||
|
||||
if article:
|
||||
return article # Используем существующую
|
||||
|
||||
# 3. Создаём новую статью
|
||||
article = TbArticle.objects.create(
|
||||
s_article_title=article_title,
|
||||
l_article_type=article_type,
|
||||
b_article_published=True,
|
||||
# slug генерируется автоматически в TbArticle.save()
|
||||
)
|
||||
|
||||
return article
|
||||
```
|
||||
|
||||
## Синонимы: управление и валидация
|
||||
|
||||
### Как формируются синонимы
|
||||
|
||||
Синонимы хранятся в JSONField `j_*_metadata` в формате:
|
||||
|
||||
```json
|
||||
{
|
||||
"SYN_EN": [
|
||||
"The Beatles",
|
||||
"Beatles",
|
||||
"Beatles, The",
|
||||
"The Fab Four"
|
||||
],
|
||||
"other_data": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### Автоматическое обновление синонимов при сохранении
|
||||
|
||||
**Функция `update_synonyms_in_metadata()`:**
|
||||
|
||||
```python
|
||||
def update_synonyms_in_metadata(instance, main_field_name, metadata_field_name):
|
||||
"""
|
||||
При каждом сохранении:
|
||||
1. Если это новая запись: добавляем s_label в синонимы
|
||||
2. Если s_label изменился: добавляем оба (старый и новый) в синонимы
|
||||
3. Админ может редактировать синонимы через форму (приоритет админу)
|
||||
"""
|
||||
|
||||
current_value = getattr(instance, main_field_name) # "Sony Music"
|
||||
original_value = instance._original_value # "Sony"
|
||||
|
||||
# Получаем или создаём словарь метаданных
|
||||
metadata = getattr(instance, metadata_field_name) or {}
|
||||
synonyms = metadata.get(KEY_SYNONYM_EN, [])
|
||||
|
||||
# Добавляем новые синонимы
|
||||
if original_value and original_value != current_value:
|
||||
# Изменилось название - добавляем оба
|
||||
synonyms.extend([original_value, current_value])
|
||||
else:
|
||||
# Новая запись - добавляем текущее
|
||||
synonyms.append(current_value)
|
||||
|
||||
# Удаляем дубликаты и короткие слова
|
||||
synonyms = [s for s in set(synonyms) if len(s) >= MIN_SYNONYM_WORD_LENGTH]
|
||||
|
||||
metadata[KEY_SYNONYM_EN] = sorted(synonyms)
|
||||
setattr(instance, metadata_field_name, metadata)
|
||||
```
|
||||
|
||||
### Валидация синонимов при сохранении
|
||||
|
||||
```python
|
||||
# Проверяем: все ли синонимы уникальны?
|
||||
|
||||
validate_for_duplicates(
|
||||
model_class=TbLabel,
|
||||
instance_pk=label.id,
|
||||
main_field_value=label.s_label,
|
||||
metadata_dict=label.j_label_metadata,
|
||||
main_field_name='s_label',
|
||||
metadata_field_name='j_label_metadata',
|
||||
)
|
||||
|
||||
# Если найдены дубликаты (FIND_IN_SYNONYM, EXACT_SYNONYM_MATCH):
|
||||
# - В админке: показываем форму с ошибкой + список дубликатов
|
||||
# - В парсере (будущее): создаём запись в ValidatorQueue для админа
|
||||
```
|
||||
|
||||
## Админка: как это видит пользователь
|
||||
|
||||
### Создание нового лейбла
|
||||
|
||||
```
|
||||
Админ заполняет форму:
|
||||
┌────────────────────────────┐
|
||||
│ Название лейбла* │
|
||||
│ [ Sony Music ] │
|
||||
│ │
|
||||
│ Метаданные (JSON) │
|
||||
│ { "SYN_EN": [...] } │
|
||||
│ │
|
||||
│ [ Сохранить ] │
|
||||
└────────────────────────────┘
|
||||
|
||||
При сохранении:
|
||||
1. Проверяются синонимы (EXACT_MATCH, FIND_IN_SYNONYM)
|
||||
2. Если конфликт → показывается ошибка:
|
||||
"Найдено совпадение! Существующие записи: [List]"
|
||||
|
||||
3. Если ОК → статья создаётся автоматически
|
||||
"Лейбл создан. Связанная статья: Label: Sony Music"
|
||||
```
|
||||
|
||||
### Обновление существующего лейбла
|
||||
|
||||
```
|
||||
Админ меняет название (например, опечатка):
|
||||
[ Sony Musik ] → [ Sony Music ]
|
||||
|
||||
1. Синонимы обновляются: добавляются оба варианта
|
||||
2. Статья остаётся той же (slug не меняется)
|
||||
3. Сохраняется историчность: "был Sony Musik, теперь Sony Music"
|
||||
```
|
||||
|
||||
### Ошибки при сохранении
|
||||
|
||||
```
|
||||
Сценарий: Админ пытается создать дубликат
|
||||
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ ОШИБКА: TbLabel.save() │
|
||||
│ Найдено совпадение в синонимах! │
|
||||
│ Разрешите на уровне админки. │
|
||||
│ PK конфликтующих записей: [1, 5, 12] │
|
||||
│ │
|
||||
│ Подробно: "Sony Music" найдено в │
|
||||
│ синонимах TbLabel(pk=5) │
|
||||
└─────────────────────────────────────────────┘
|
||||
|
||||
Решение: Админ либо меняет название, либо
|
||||
объединяет с существующей записью вручную.
|
||||
```
|
||||
|
||||
## Применение ко всем 5 моделям
|
||||
|
||||
### TbLabel (Лейблы)
|
||||
|
||||
```python
|
||||
class TbLabel(models.Model):
|
||||
s_label: str (уникальный) # "Sony Music", "Мелодия"
|
||||
j_label_metadata: JSON # {"SYN_EN": [...]}
|
||||
k_label_to_article: FK → TbArticle # автоматическое создание
|
||||
|
||||
# ArticleType: LABEL
|
||||
# Формат названия: "Label: Sony Music"
|
||||
```
|
||||
|
||||
### TbArtist (Исполнители)
|
||||
|
||||
```python
|
||||
class TbArtist(models.Model):
|
||||
s_artist: str (уникальный) # "The Beatles", "David Bowie"
|
||||
j_artist_metadata: JSON # {"SYN_EN": [...]}
|
||||
k_artist_to_article: FK → TbArticle # автоматическое создание
|
||||
|
||||
# ArticleType: ARTIST
|
||||
# Формат названия: "Artist: The Beatles"
|
||||
```
|
||||
|
||||
### TbMusicStyle (Стили)
|
||||
|
||||
```python
|
||||
class TbMusicStyle(models.Model):
|
||||
s_style_name: str (уникальный) # "Rock", "Jazz"
|
||||
j_style_metadata: JSON # {"SYN_EN": [...]}
|
||||
k_style_to_article: FK → TbArticle # автоматическое создание
|
||||
|
||||
# ArticleType: STYLE
|
||||
# Формат названия: "Style: Rock"
|
||||
```
|
||||
|
||||
### TbItem (Товары/Релизы)
|
||||
|
||||
```python
|
||||
class TbItem(models.Model):
|
||||
s_item: str (уникальный) # "Abbey Road (LP)"
|
||||
j_item_metadata: JSON # {"SYN_EN": [...]}
|
||||
k_item_to_article: FK → TbArticle # автоматическое создание
|
||||
|
||||
# ArticleType: ITEM
|
||||
# Формат названия: "Item: Abbey Road (LP)"
|
||||
```
|
||||
|
||||
### TbSeller (Продавцы)
|
||||
|
||||
```python
|
||||
class TbSeller(models.Model):
|
||||
s_seller: str (уникальный) # "Клюква Records", "Amazon"
|
||||
j_seller_metadata: JSON # (синонимы не используются)
|
||||
k_seller_to_article: FK → TbArticle # автоматическое создание
|
||||
|
||||
# ArticleType: SELLER
|
||||
# Формат названия: "Seller: Клюква Records"
|
||||
# НЕ имеет синонимов (редко меняется, используется редко)
|
||||
```
|
||||
|
||||
## Парсинг (будущее): интеграция с очередью конфликтов
|
||||
|
||||
### Сценарий: парсер вытащил данные из Discogs
|
||||
|
||||
```python
|
||||
# Парсер получил из Discogs:
|
||||
incoming_data = {
|
||||
's_artist': 'The Beatles',
|
||||
'j_artist_metadata': {
|
||||
'SYN_EN': ['Beatles', 'The Fab Four'],
|
||||
'DISCOGS_ID': 123456,
|
||||
'COUNTRY': 'UK',
|
||||
'FORMED': '1960',
|
||||
}
|
||||
}
|
||||
|
||||
# Парсер пытается создать TbArtist(**incoming_data)
|
||||
artist = TbArtist(**incoming_data)
|
||||
artist.save() # Вызывает валидацию
|
||||
|
||||
# Если есть конфликт (FIND_IN_SYNONYM, EXACT_SYNONYM_MATCH):
|
||||
# - Парсер ловит ValidationError
|
||||
# - Создаёт запись в ValidatorQueue
|
||||
# - Админ получает уведомление про конфликт
|
||||
# - Админ решает: создать новую / объединить / пропустить
|
||||
```
|
||||
|
||||
### Структура ValidatorQueue для справочников
|
||||
|
||||
```python
|
||||
ValidatorQueue(
|
||||
k_source=excel_file_source,
|
||||
l_model_type='TbArtist',
|
||||
l_conflict_type='FIND_IN_SYNONYM', # тип конфликта
|
||||
model_pk=None, # это новая запись (или PK существующей)
|
||||
|
||||
j_incoming_data={
|
||||
's_artist': 'The Beatles',
|
||||
'j_artist_metadata': {'SYN_EN': [...], 'DISCOGS_ID': ...}
|
||||
},
|
||||
|
||||
j_duplicates=[
|
||||
{
|
||||
'pk': 15,
|
||||
's_artist': 'Beatles',
|
||||
'matched_value': 'The Beatles',
|
||||
'match_type': 'FIND_IN_SYNONYM',
|
||||
'reason': 'Найдено в синонимах'
|
||||
}
|
||||
],
|
||||
|
||||
l_status='pending', # Ждёт решения админа
|
||||
s_admin_decision=None, # Админ ещё не решил
|
||||
)
|
||||
```
|
||||
|
||||
### Решения админа
|
||||
|
||||
```
|
||||
1. "create_new" — создать новую запись (с дубликатом)
|
||||
Когда: Если это действительно разные сущности (например,
|
||||
The Beatles (группа) vs The Beatles (альбом))
|
||||
|
||||
2. "merge" — объединить с существующей
|
||||
Когда: Если это одна и та же сущность, только разные названия
|
||||
Действие: Добавить синонимы в существующую запись
|
||||
|
||||
3. "skip" — пропустить
|
||||
Когда: Данные некорректны или дубликаты действительно
|
||||
Действие: Ничего не создавать, просто пропустить
|
||||
```
|
||||
|
||||
## Метаданные для парсинга: проверить, можно что-то вытащить из внешних источников
|
||||
|
||||
### Из Discogs (для всех справочников)
|
||||
|
||||
```json
|
||||
{
|
||||
"DISCOGS_ID": 123456, // ID записи в Discogs
|
||||
"DISCOGS_URL": "https://...", // Ссылка на Discogs
|
||||
"DISCOGS_UPDATED": "2024-01-15",// Дата обновления на Discogs
|
||||
"SYN_EN": [ // Синонимы на английском
|
||||
"Alternative name",
|
||||
"Another variant"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Для TbArtist (исполнители) — из Discogs, MusicBrainz
|
||||
|
||||
```json
|
||||
{
|
||||
"ARTIST_TYPE": "Group", // Solo, Group, Orchestra, Character
|
||||
"FORMED": "1960", // Год образования
|
||||
"DISBANDED": null, // Год распада (если есть)
|
||||
"COUNTRY": "GB", // Страна (ISO 3166-1)
|
||||
"AREA": "United Kingdom", // Регион
|
||||
"MEMBERS": [ // Участники (для групп)
|
||||
{"name": "John Lennon", "role": "Vocals"},
|
||||
{"name": "Paul McCartney", "role": "Bass"},
|
||||
],
|
||||
"WEBSITE": "https://beatles.com", // Официальный сайт
|
||||
"WIKIPEDIA": "https://en.wikipedia.org/...", // Статья
|
||||
"MUSICBRAINZ_ID": "uuid-here", // MusicBrainz ID
|
||||
"BIOGRAPHY": "Краткая биография...", // Текст для статьи
|
||||
"IMG_URL": "https://...", // Ссылка на изображение
|
||||
"YANDEX_MUSIC_ID": 123456, // ID на Яндекс.Музыке
|
||||
"SPOTIFY_ID": "spotify-id", // ID на Spotify
|
||||
"SYN_EN": [...],
|
||||
"SYN_RU": [...] // Синонимы на русском
|
||||
}
|
||||
```
|
||||
|
||||
### Для TbLabel (лейблы) — из Discogs, MusicBrainz
|
||||
|
||||
```json
|
||||
{
|
||||
"LABEL_TYPE": "Production", // Production, Reissue, Distributor
|
||||
"FOUNDED": "1945", // Год основания
|
||||
"COUNTRY": "USA", // Страна
|
||||
"HEADQUARTERS": "New York, NY", // Штаб-квартира
|
||||
"PARENT_LABEL": 456789, // ID материнского лейбла (Discogs)
|
||||
"WEBSITE": "https://sony.com", // Официальный сайт
|
||||
"WIKIPEDIA": "https://...", // Статья
|
||||
"CONTACT_EMAIL": "info@...", // Email контакта
|
||||
"CONTACT_PHONE": "+1-234-567", // Телефон
|
||||
"COMPANY_SIZE": "Large", // Small, Medium, Large, Mega
|
||||
"DISCOGS_CATALOG_PREFIX": "SR", // Префикс каталога
|
||||
"IMG_LOGO": "https://...", // Логотип
|
||||
"SYN_EN": [...],
|
||||
"SYN_RU": [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Для TbItem (релизы/альбомы) — из Discogs, MusicBrainz
|
||||
|
||||
```json
|
||||
{
|
||||
"ITEM_TYPE": "Album", // Album, Single, EP, Compilation
|
||||
"RELEASE_DATE": "1969-09-26", // Дата выхода
|
||||
"FORMAT": "Vinyl LP", // Vinyl LP, CD, Cassette, Digital
|
||||
"COUNTRY": "GB", // Страна выхода
|
||||
"BARCODE": "093624892340", // Штрихкод
|
||||
"DISCOGS_ID": 987654, // ID мастер-релиза
|
||||
"MUSICBRAINZ_ID": "uuid", // MusicBrainz ID
|
||||
"GENRES": ["Rock", "Pop"], // Жанры (для синхронизации с TbMusicStyle)
|
||||
"CATALOG_NUMBER": "SR-123", // Каталожный номер издателя
|
||||
"LANGUAGE": "English", // Язык вокала/текста
|
||||
"TRACKS_COUNT": 14, // Количество треков
|
||||
"DURATION": "42:30", // Длительность альбома
|
||||
"RATING": 4.5, // Рейтинг на Discogs (1-5)
|
||||
"NOTES": "Limited edition...", // Примечания
|
||||
"IMG_COVER": "https://...", // Обложка альбома
|
||||
"YANDEX_MUSIC_ID": 123456, // ID на Яндекс.Музыке
|
||||
"SPOTIFY_ID": "spotify-id", // ID на Spotify
|
||||
"SYN_EN": [...],
|
||||
"SYN_RU": [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Для TbMusicStyle (жанры/стили) — из Discogs, MusicBrainz, Wikipedia
|
||||
|
||||
```json
|
||||
{
|
||||
"STYLE_TYPE": "Primary", // Primary, Sub-genre
|
||||
"DESCRIPTION": "Rock music is...", // Описание жанра
|
||||
"ORIGIN_COUNTRY": "USA", // Страна происхождения
|
||||
"ORIGIN_YEAR": "1950s", // Период возникновения
|
||||
"PARENT_GENRE": "Rock", // Основной жанр (для иерархии)
|
||||
"RELATED_STYLES": [ // Связанные жанры
|
||||
"Hard Rock",
|
||||
"Progressive Rock"
|
||||
],
|
||||
"NOTABLE_ARTISTS": [ // Известные исполнители
|
||||
"The Beatles",
|
||||
"Rolling Stones"
|
||||
],
|
||||
"WIKIPEDIA": "https://...", // Статья в Википедии
|
||||
"IMG_ILLUSTRATION": "https://...", // Иллюстрация для жанра
|
||||
"SYN_EN": [...],
|
||||
"SYN_RU": [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Для TbSeller (продавцы) — из веб-скрейпинга, API или Excel (для excel и ручного ввода заполнять руками)
|
||||
|
||||
```json
|
||||
{
|
||||
"SELLER_TYPE": "Store", // Store, Label, Distributor, Marketplace
|
||||
"FOUNDED": "2005", // Год основания
|
||||
"COUNTRY": "RU", // Страна
|
||||
"CITY": "Moscow", // Город
|
||||
"ADDRESS": "ul. Pushkina, 42", // Адрес
|
||||
"WEBSITE": "https://example.com", // Сайт
|
||||
"EMAIL": "sales@example.com", // Email
|
||||
"PHONE": "+7-495-123-45-67", // Телефон
|
||||
"SOCIAL_VK": "https://vk.com/...", // VKontakte
|
||||
"SOCIAL_INSTAGRAM": "https://...", // Instagram
|
||||
"SHIPPING_COUNTRIES": [ // Страны доставки
|
||||
"RU", "BY", "KZ"
|
||||
],
|
||||
"PAYMENT_METHODS": [ // Методы оплаты
|
||||
"Card", "Bank transfer", "Cash"
|
||||
],
|
||||
"RETURNS_POLICY": "7 days", // Политика возврата
|
||||
"RATING": 4.8, // Рейтинг на платформе
|
||||
"REVIEWS_COUNT": 342, // Количество отзывов
|
||||
"IMG_LOGO": "https://...", // Логотип магазина
|
||||
}
|
||||
```
|
||||
|
||||
## Жизненный цикл в нескольких слов
|
||||
|
||||
```
|
||||
Админка Парсер (будущее)
|
||||
│ │
|
||||
├─ Заполняет форму ├─ Читает Discogs/Excel
|
||||
│ (название, синонимы) │ (вытаскивает данные)
|
||||
│ │
|
||||
├─ Нажимает "Сохранить" ├─ Вызывает Model.save()
|
||||
│ │
|
||||
├─ Валидация синонимов ├─ Валидация синонимов
|
||||
│ (FIND_IN_SYNONYM?) │ (конфликты?)
|
||||
│ │
|
||||
├─ Ошибка? → показать админу ├─ Конфликт? → ValidatorQueue
|
||||
│ │
|
||||
├─ OK? → создать статью ├─ OK? → создать модель
|
||||
│ автоматически │ + статья автоматически
|
||||
│ │
|
||||
└─ Данные целостны и готовы └─ Админ решает позже
|
||||
```
|
||||
|
||||
## Преимущества подхода
|
||||
|
||||
✓ **Единообразие**: 5 моделей работают по одному паттерну
|
||||
✓ **Автоматизация**: Статьи создаются сами, не нужна ручная работа
|
||||
✓ **Валидация**: Дубликаты и синонимы проверяются автоматически
|
||||
✓ **Контроль**: Админ видит конфликты и может принять решение
|
||||
✓ **Масштабируемость**: Легко добавить другие справочники (если нужны)
|
||||
✓ **SEO-дружественно**: Каждый справочник имеет URL через слаг
|
||||
✓ **История**: Изменения в названиях сохраняются через синонимы
|
||||
✓ **Готовность к парсерам**: Структура поддерживает будущую интеграцию
|
||||
|
||||
## Файлы реализации
|
||||
|
||||
- **Модели**: `lpon_site/frontend/models.py` (TbLabel, TbArtist, TbItem, TbMusicStyle, TbSeller)
|
||||
- **Валидаторы**: `lpon_site/frontend/utils_validators.py` (validate_for_duplicates)
|
||||
- **Хелперы**: `lpon_site/frontend/utils.py` (create_or_get_related_article, update_synonyms_in_metadata)
|
||||
- **Админка**: `lpon_site/frontend/admin.py` (LabelAdmin, ArtistAdmin и т.д.)
|
||||
- **Парсер** (будущее): `lpon_site/parser/models.py` (ValidatorQueue), `tasks.py` (Celery задачи)
|
||||
|
||||
## TODO: интеграция парсера
|
||||
|
||||
- [ ] Создать Celery tasks для парсинга Discogs/Excel
|
||||
- [ ] Реализовать ValidatorQueue с админ-интерфейсом
|
||||
- [ ] Добавить поддержку очереди конфликтов в моделях
|
||||
- [ ] Интегрировать сбор метаданных из Discogs API
|
||||
- [ ] Документировать API для парсера
|
||||
- [ ] Написать тесты для валидации и парсинга
|
||||
Reference in New Issue
Block a user