142 lines
9.1 KiB
Markdown
142 lines
9.1 KiB
Markdown
# Архитектура метаданных и схема каталога товаров (LPON Blueprint)
|
||
|
||
**Дата и время создания/обновления:** 2026-10-18 20:30
|
||
**Проект:** LPON.RU (Django 6.0+, Python 3.12)
|
||
**Статус:** Согласованная архитектурная концепция
|
||
**Связанный документ:** `_planstep_for_cassettes.md`
|
||
|
||
---
|
||
|
||
### 1. Фундаментальный принцип: Трехуровневая модель данных
|
||
|
||
Для обеспечения масштабируемости каталога на тысячи позиций разных форматов (Кассеты, Минидиски, Винил, CD, Аудиотехника) данные разделены на три уровня:
|
||
|
||
```text
|
||
[ Уровень 1: Реляционная БД ] ──► Базовые ключи (ID, Цены, Связи, Количество, Статусы)
|
||
[ Уровень 2: Универсальные JSON ] ──► Формат, Рынок, Год, Состояние, Упаковка, Комплектация
|
||
[ Уровень 3: Специфика формата ] ──► Тип ленты, Длина (Кассеты/MD), Скорость/Вес (Винил), Шторка (MD)
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Разделение сущностей: «Заводской паспорт» vs «Физический экземпляр»
|
||
|
||
#### Тест-вопрос: «Это с конвейера или от жизни?»
|
||
|
||
#### Упрощенная типизация `media_type`:
|
||
* **`album`** — Музыкальный альбом / мастер-релиз (The Doors, Pink Floyd, Кино).
|
||
* **`media`** — Физический носитель под запись (кассеты, минидиски, бобины — чистые или б/у с записями).
|
||
|
||
---
|
||
|
||
### Симметрия сущностей: Музыкальный альбом vs Модель носителя
|
||
|
||
Чтобы не было путаницы между уровнями, в каталоге соблюдается идеальная симметрия:
|
||
|
||
```text
|
||
[ МУЗЫКАЛЬНЫЙ МИР ] [ МИР НОСИТЕЛЕЙ (КАССЕТЫ / MD) ]
|
||
1. TbArtist: The Doors 1. TbLabel: Sony / TDK / Maxell
|
||
│ │
|
||
▼ ▼
|
||
2. TbItem (Альбом): L.A. Woman (1971) 2. TbItem (Модель): Sony UX 90 (1986, JP)
|
||
• Название, автор, треклист • Тип ленты: Type II (High)
|
||
• Год первого релиза • Хронометраж: 90 мин
|
||
• Обзорная статья • Год ревизии модели: 1986
|
||
│ │
|
||
▼ ▼
|
||
3. TbOffer (Издание в продаже): 3. TbOffer (Экземпляр в продаже):
|
||
• Винил LP US Elektra 1971 (VG+/VG+, OBI) • Экземпляр А: Запечатанный (Sealed), 2200 ₽
|
||
• CD Japan 1991 (M/NM) • Экземпляр Б: Б/У с записью Doors, 350 ₽
|
||
```
|
||
|
||
#### А. Модель товара (`TbItem` → `j_item_metadata`)
|
||
* **Что хранит:** Неизменяемые характеристики альбома или заводской модели носителя.
|
||
* **Для альбомов (`media_type = 'album'`):** музыкальная суть (треклист, авторы, год оригинального релиза).
|
||
* **Для носителей (`media_type = 'media'`):** заводские параметры конкретной модели (`tape_type`, `length`, `release_year`, `market`, `shell_color`, `shell_screws`, `window_type`).
|
||
|
||
#### Б. Коммерческий лот (`TbOffer` → `j_offer_metadata`)
|
||
* **Что хранит:** Индивидуальные свойства конкретного физического экземпляра у продавца.
|
||
* **Примеры:**
|
||
* `packaging`: сохранность упаковки (`sealed` — запечатан, `opened` — вскрыт/б-у);
|
||
* `is_recorded`: признак аудиозаписи (`true`/`false`);
|
||
* `recording_info`: аннотация к аудиозаписи (радиоэфир, альбом, бутлег);
|
||
* `write_protect_tabs`: статус язычков защиты от записи (`intact` — целы, `broken` — выломаны);
|
||
* `inlay_condition`: состояние вкладыша J-Card (`clean`, `pencil`, `written`, `missing`);
|
||
* `series_tag`: слаг серии или тематической коллекции (например, `oobu-joobu`);
|
||
* `extras`: словарь комплектации и опций издания (наличие и грейд компонентов).
|
||
|
||
---
|
||
|
||
### 3. Комплектация и опции издания (`extras` / `OfferExtra`)
|
||
|
||
В `j_offer_metadata['extras']` хранится словарь кодовых ключей из `OfferExtra`:
|
||
|
||
```json
|
||
{
|
||
"extras": {
|
||
"obi": {"condition": "vg", "qty": 1, "note": "Японское оригинальное OBI"},
|
||
"insert": {"condition": "clean"},
|
||
"case_original": {"condition": "vg"},
|
||
"stickers_sheet": {}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Допустимые коды комплектации:
|
||
* `obi` — ОБИ-лента (OBI strip);
|
||
* `insert` — вкладыш / лирика / статья (Lyric sheet / Insert);
|
||
* `poster` — оригинальный постер;
|
||
* `stickers_sheet` — чистый лист наклеек;
|
||
* `stickers_applied` — наклейки нанесены на носитель;
|
||
* `postcards` — коллекционные открытки / карточки;
|
||
* `booklet` — буклет / книжка;
|
||
* `case_original` — оригинальный родной футляр / подкассетник;
|
||
* `case_generic` — стандартный / неоригинальный футляр;
|
||
* `no_case` — без футляра;
|
||
* `slipcase` — картонный слипкейс / O-Card;
|
||
* `box_set` — подарочный бокс / коробка;
|
||
* `inner_sleeve` — оригинальный иллюстрированный внутренний конверт (для винила);
|
||
* `outer_sleeve` — внешний защитный пластиковый пакет;
|
||
* `autograph` — автограф исполнителя / музыканта.
|
||
|
||
---
|
||
|
||
### 4. Концепция ввода данных: Шаблоны с подсказками через «трубу» (`|`) и защита от лени
|
||
|
||
Для удобства оператора и 100% защиты от невалидных данных используется механика **«Inline Choice Placeholder»**:
|
||
|
||
1. **Генерация заготовки:** При создании новой записи в поле JSON подставляется готовый шаблон, где для каждого ключа через символ `|` перечислены все допустимые варианты:
|
||
```json
|
||
{
|
||
"media_type": "blank_media",
|
||
"tape_type": "type_1|type_2|type_3|type_4",
|
||
"length": "46|54|60|74|90|100|120",
|
||
"market": "jp|us|eu|global"
|
||
}
|
||
```
|
||
2. **Правка оператором:** Оператор стирает лишние варианты, оставляя строго один: `"type_2"`.
|
||
3. **Строгая валидация (`clean()`):** Если запись сохранена с невыбранными вариантами (содержит `|`), валидатор блокирует сохранение и выводит понятную ошибку.
|
||
4. **Хранение в БД:** В базе данных всегда сохраняется только чистый, валидный код без лишних символов.
|
||
|
||
---
|
||
|
||
### 5. Каскадное наследование свойств (Fallback Cascade)
|
||
|
||
Оффер `TbOffer` автоматически наследует эталонные параметры от родительского `TbItem`. Если лот имеет индивидуальную особенность, значение в `j_offer_metadata` перекрывает заводское значение:
|
||
|
||
```python
|
||
def get_tape_type(self):
|
||
"""Возвращает тип ленты лота или эталонный тип ленты из модели Item."""
|
||
return self.j_offer_metadata.get('tape_type') or (
|
||
self.k_offer_to_item.j_item_metadata.get('tape_type')
|
||
if self.k_offer_to_item else None
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
### 6. Единый источник истины в коде
|
||
|
||
Все константы, перечисления (`models.TextChoices`, `models.IntegerChoices`), ключи метаданных и функции дефолтных шаблонов зафиксированы в модуле:
|
||
* **`lpon_site/lpon_site/constants.py`** (с реэкспортом в `settings.py` и `frontend/models.py`).
|