From 6ef11d4f64e7a861f4551524424efce51bfea740 Mon Sep 17 00:00:00 2001 From: erjemin Date: Mon, 19 Oct 2026 07:51:58 +0300 Subject: [PATCH] =?UTF-8?q?doc:=20=D0=BA=D0=B0=D0=BA=20=D1=81=D0=BE=D0=B2?= =?UTF-8?q?=D0=BC=D0=B5=D1=81=D1=82=D0=B8=D1=82=D1=8C=20=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=B1=D0=BE=D0=BC=D1=8B=20=D0=B8=20=D0=BA=D0=B0=D1=81=D1=81?= =?UTF-8?q?=D0=B5=D1=82=D0=BD=D1=8B=D0=B5=20=D1=81=D0=B5=D1=80=D0=B8=D0=B8?= =?UTF-8?q?=20(draft=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _prj_blueprint/_planstep_for_cassettes.md | 145 ++++++++++++++++++++++ _prj_blueprint/metadata_architecture.md | 141 +++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 _prj_blueprint/_planstep_for_cassettes.md create mode 100644 _prj_blueprint/metadata_architecture.md diff --git a/_prj_blueprint/_planstep_for_cassettes.md b/_prj_blueprint/_planstep_for_cassettes.md new file mode 100644 index 0000000..1a36469 --- /dev/null +++ b/_prj_blueprint/_planstep_for_cassettes.md @@ -0,0 +1,145 @@ +# План реализации кассетного каталога, серий и DSL-витрины + +**Дата и время создания/обновления:** 2026-10-11 17:30 +**Проект:** LPON.RU (Django 6.0+, Python 3.12) +**Статус:** Согласованный рабочий план +**Архитектурная спецификация метаданных:** `metadata_architecture.md` + +--- + +### Архитектурные принципы и ключевые решения + +#### 1. Серии, коллекции и раритетные записи +* **Концепция:** Для тематических архивов (например, радиопередачи Oobu Joobu, японские бутлеги, циклы фолк-концертов) создается обзорная статья `TbArticle` (тип `HUB` или `COLLECTION`). +* **Контент:** Предыстория архива, контекст записей, аппаратура оцифровки/записи, сканы оригинальных вкладышей. +* **DSL-витрина серии:** Внутрь статьи встраивается DSL-блок, динамически выбирающий кассеты этой серии по тегу или источнику (например, `["offers:series:oobu-joobu", "grid"]`). +* **Покупка:** Поддерживается покупка отдельных выпусков/кассет поштучно, а также покупка всего сета через сборный оффер-комплект. + +#### 2. Двухуровневая медиатека фотографий +* **Уровень 1 — Эталонные фото модели (`TbItem` / связанная `TbArticle`):** + * Фотографируются один раз на модель: цвет и фактура ленты (Fe, CrO2, Metal), металлизированный ракорд, люминесцентное свечение (TDK BEAM), керамические блоки, сканы каталогов тех лет. + * Автоматически подтягиваются во все офферы данной модели кассеты. +* **Уровень 2 — Фото конкретного лота (`TbOffer` через `TbImageMetadata`):** + * 2-3 честных фото продаваемого экземпляра: реальные потертости корпуса, состояние надписей на вкладыше, целостность язычков защиты от записи. +* **Отображение в карточке:** В основном блоке показывается конкретный продаваемый экземпляр, ниже выводится блок «Детали и макросъемка модели». + +#### 3. Музейный каталог (по типу c-90.org / ez647.sk) и архивные лоты +* **Сохранение проданных позиций:** Офферы со статусом `quantity = 0` не удаляются и не закрываются ошибкой 404. Они получают статус «Архивный лот / Продано», оставаясь доступными для SEO, просмотра истории и изучения записей. +* **Матричная структура справочника (без жесткого дерева папок):** + * Ось брендов: `/cassettes/brands/` (TDK, Maxell, Sony, BASF, AXIA, Denon); + * Хронологические линейки: разбивка внутри бренда по модельным годам (например, модельный ряд TDK 1985, 1988, 1990 годов с группировкой по типам ленты Normal / High / Metal); + * Ось хронологии: `/cassettes/timeline/` (разбивка по эпохам: 70-е, 80-е, 90-е, 2000-е). + +--- + +### Ближний горизонт (Пошаговый план разработки) + +#### Шаг 1. Стандартизация схемы метаданных для кассет и всех форматов +* Спецификация полностью зафиксирована в `_prj_blueprint/metadata_architecture.md`. +* Модуль констант `lpon_site/lpon_site/constants.py` создан и подключен: + * Единый реестр перечислений: `MediaType`, `Market`, `TapeType`, `MediaLength`, `ShellColor`, `WriteProtectTabs`, `InlayCondition`, `Packaging`, `OfferExtra`, `Condition`, `Format`. + * Режим дефолтных шаблонов с подсказками через `|` (Inline Choice Placeholder) для защиты от ленивого заполнения. +* Структура полей `j_item_metadata` (для `TbItem`): + * `media_type`: `blank_media`, `music_album`, `audio_recording`, `equipment`; + * `tape_type`: `type_1`, `type_2`, `type_3`, `type_4`; + * `length`: `46`, `50`, `54`, `60`, `74`, `80`, `90`, `100`, `110`, `120`; + * `release_year`: год или диапазон годов выпуска; + * `market`: `jp`, `us`, `eu`, `uk`, `su`, `global`; + * `gallery_model`: список ID эталонных фото модели в `django-filer`. +* Структура полей `j_offer_metadata` (для `TbOffer`): + * `packaging`: `sealed`, `opened`; + * `is_recorded`: флаг наличия уникальной аудиозаписи; + * `recording_info`: краткое резюме записи (или ссылка на отдельную статью `k_offer_to_article`); + * `write_protect_tabs`: `intact`, `broken`; + * `inlay_condition`: `clean`, `pencil`, `written`, `missing`; + * `series_tag`: слаг серии/коллекции (при наличии); + * `extras`: словарь опций комплектации (`obi`, `insert`, `poster`, `case_original`, `stickers_sheet` и др.). + +#### Шаг 2. Ядро DSL и реестр обработчиков (`DSLQueryRegistry`) +* Создать модуль реестра обработчиков (Query Handlers), разбирающий кортежи формата: + `["source", "view_mode", limit, "sort", extra]` +* Реализовать базовые обработчики источников: + * Одиночные сущности по слагу (`single_slug`); + * Выборки статей (`articles:type:`, `articles:collection:`); + * Выборки офферов (`offers:format:cassette`, `offers:label:`, `offers:tape_type:`, `offers:series:`); +* Реализовать сортировки: + * `new+`, `new-`, `price+`, `price-`, `year+`, `year-`, `view+`, `random`; +* Поддержка составных режимов и лимитов (`view_mode = "teaser+list"`, `limit = "2+15"`). +* Защита от зацикливания (`visited_slugs`, `MAX_RECURSION_DEPTH = 2`) и безопасная деградация при ошибках в JSON. + +#### Шаг 3. Шаблоны компонентов и кастомный Template Tag +* Реализовать template tag `{% render_hub_blocks article %}` (или фильтр рендеринга хабов). +* Разработать базовую карточку кассеты (`cassette_card.html`): + * Фото экземпляра, бейджи типа ленты, хронометража и года, плашка состояния («Под перезапись» или «Запись: Название»), цена/статус наличия. +* Разработать шаблоны раскладки для разных `view_mode`: + * `grid.html` (адаптивная сетка карточек); + * `carousel.html` (горизонтальная прокрутка); + * `list.html` (компактный табличный список); + * `timeline.html` (раскладка по годам и линейкам для музейного каталога); + * `teaser.html` (промо-блок статьи/серии). + +#### Шаг 4. Шаблоны страниц и виртуальные хабы +* Шаблон детальной страницы товара/кассеты: + * Основная галерея фото конкретного лота + ТТХ и состояние; + * Блок «Детали и макросъемка модели» (эталонные фото из `TbItem`); + * Блок описания аудиозаписи с треклистом (если привязана статья к офферу); + * Автоматический виртуальный DSL-блок («Другие кассеты этого бренда», «Другие кассеты Type II»). +* Шаблон страницы серии/коллекции: + * Вводная статья + встроенная DSL-сетка кассет серии + блок заказа всей подборки. +* Подключение рендеринга блоков в `content/hub.html` и `content/article_detail.html`. + +#### Шаг 5. Пилотная партия и тестирование витрины +* Внести в базу 10-15 реальных кассет разных брендов и категорий: + * 3 чистые кассеты Type I (TDK D, Sony HF); + * 3 кассеты Type II (Maxell XL-II, TDK SA); + * 1 топовая кассета Type IV (Metal) или спец-серия со светящимся корпусом (TDK BEAM); + * 1 мини-серия из 3-4 кассет с редкими записями (Oobu Joobu / британский фолк) с оформленной статьей-хабом. +* Собрать главный хаб кассетного раздела `/cassettes/`. +* Проверить визуальное отображение, фильтрацию и мобильную верстку. + +--- + +### Среднесрочный горизонт + +#### 6. Бесконтактная корзина и анонимные заказы (Stateless Orders) +* Клиентский модуль `!cart-app` (хранение кодов офферов `s_offer_code` в LocalStorage/Cookies). +* Интеграция виджета доставки (СДЭК) и генерация анонимных трек-номеров без сбора ПДн в БД сайта (152-ФЗ compliance). + +#### 7. История слагов и SEO-редиректы +* Модель `TbArticleAlias` для автоматического сохранения старых слагов при переименовании статей и отдачи `301 Permanent Redirect`. + +--- + +### Дальний горизонт + +#### 8. Парсеры и фоновые очереди +* Интеграция Celery / Redis воркеров для разбора внешних источников данных. +* Реализация интерфейса `ValidatorQueue` в админке для разрешения коллизий синонимов и дубликатов перед записью в основную базу. + +--- +```text +[ TbArtist ] ──(M2M)──► [ TbItem ] ──────► [ TbOffer ] +(Донован) (Релиз / Модель) (Конкретный лот на складе) + │ │ + ▼ ▼ + [ TbArticle ] [ TbArticle ] + (Эталонная статья) (История записи / лота) +``` + +| Сущность | Что представляет | Источник наполнения | Связь с артистом | +|:-------------------------------|:------------------------------------|:-----------------------------|:---------------------------------| +| Винил (LP) | Музыкальный альбом на виниле | Парсер Discogs / MusicBrainz | Да (TbItem.k_item_to_artist) | +| Компакт-диск (CD) | Музыкальный альбом на CD | Парсер Discogs / MusicBrainz | Да (TbItem.k_item_to_artist) | +| Кассета с записью / Бутлег | Редкий концерт / оцифровка / альбом | Ручной ввод / CSV | Да (TbItem.k_item_to_artist) | +| Чистая кассета / MD под запись | Модель носителя (TDK, Maxell, Sony) | Музейный каталог кассет | Нет (привязана к TbLabel бренда) | +| Серия / Архив (Oobu Joobu) | Коллекция связанных кассет | Статья-хаб TbArticle | Да (через тег серии и артиста) | + +--- + +### Технический долг и текущие замечания по коду +1. **Импорты во `frontend/views.py`:** Заменить `from lpon_site.settings import *` на `from django.conf import settings` и удалить неиспользуемый `import json`. +2. **Временные вьюхи:** Заменить временные статические функции `catalog()` и `info_articles_list()` на единый диспетчер хабов по мере готовности DSL. +3. **Заглушка `get_hub_context`:** Реализовать полноценный вызов `DSLQueryRegistry` вместо отдачи сырого JSON. + + + diff --git a/_prj_blueprint/metadata_architecture.md b/_prj_blueprint/metadata_architecture.md new file mode 100644 index 0000000..2f5ad21 --- /dev/null +++ b/_prj_blueprint/metadata_architecture.md @@ -0,0 +1,141 @@ +# Архитектура метаданных и схема каталога товаров (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`).