215 lines
17 KiB
Markdown
215 lines
17 KiB
Markdown
# Спецификация: LPON Store Hub DSL (Компактный конструктор интерфейсов на кортежах)
|
||
|
||
драфт
|
||
|
||
## 1. Обзор и цели
|
||
Мы реализуем гибкий, управляемый данными (Data-Driven) конструктор страниц внутри бэкенда Django для **LPON.RU**.
|
||
Вместо написания отдельных вьюх (Views) и шаблонов под каждый формат или раздел (например, `/catalog/cassettes/`, `/catalog/vinyl/`), мы формируем динамические **Страницы-Хабы (Hub Pages)** с помощью компактного JSON Tuple DSL, который хранится прямо в базе данных.
|
||
|
||
Система завязана на модель `TbArticle`. Конфигурация хранится в JSON-поле метаданных статьи (`j_article_metadata['hub']`). Механизм HUB может использоваться как для чистых хаб-страниц (`l_article_type = 'HUB'`), так и в качестве опционального блока **в любых других статьях/сущностях** для подключения динамических витрин и модулей.
|
||
|
||
### Генерализация списков страниц (Отказ от списочных View/Templates)
|
||
Все списочные страницы (например, список текстовых материалов `/info/`, список новостей `/blog/`, каталоги форматов) рассматриваются как **частный случай хаба**:
|
||
1. Вместо написания отдельной вьюхи (например, `txt_articles_list`) в БД создаётся статья со слагом `info` и типом `HUB`.
|
||
2. В её DSL-конфигурации прописываются требуемые блоки (например, `[["info-articles", "list", 50]]`).
|
||
3. Рендеринг выполняют не отдельные функции-представления, а единый универсальный диспетчер хабов (`HubView`).
|
||
4. **Каноничность URL элементов:** Внутри шаблонов визуальных блоков ссылки на карточки и статьи выводится строго через `href="{{ item.get_absolute_url }}"`. Модель статьи/товара сама определяет свой канонический адрес по типу (например, `/info/privacy-policy` или `/item/album-slug`), исключая путаницу префиксов в ссылках независимо от того, в каком хабе опубликован блок.
|
||
|
||
---
|
||
|
||
## 2. Синтаксис и структура JSON DSL
|
||
|
||
Массив `hub` представляет собой список позиционных кортежей (массивов). Порядок элементов в кортеже строго фиксирован. Все элементы, кроме первого (`slug` / `source`), являются опциональными и имеют безопасные значения по умолчанию.
|
||
|
||
Позиционный порядок параметров: `[ "slug", "view_mode", limit, "sort", extra ]`
|
||
|
||
### Описание позиций кортежа:
|
||
|
||
1. **`slug` / `source` (str, 1-я позиция, обязательный)**:
|
||
- Слаг точечной статьи/категории (например, `"cassettes-studio"`).
|
||
- Динамическая выборка с двоеточием (например, `"offers:artist:the-beatles"`, `"articles:type:info"`).
|
||
- Модуль/плагин с префиксом `!` (например, `"!promo-mandarin"`).
|
||
|
||
2. **`view_mode` (str, 2-я позиция, опциональный, default зависит от `ArticleType` или `"grid"`)**:
|
||
- Название компонента рендеринга (шаблона): `carousel`, `grid`, `list`, `teaser`, `full`, `slider`.
|
||
- Поддерживает составной синтаксис через `+` (например, `"teaser+list"`).
|
||
|
||
3. **`limit` (int | str, 3-я позиция, опциональный, default=`10`)**:
|
||
- Количество выбираемых элементов (например, `10`, `50`).
|
||
- Для составных шаблонов задаётся через `+` (например, `"2+15"`).
|
||
|
||
4. **`sort` (str, 4-я позиция, опциональный, default=`"new+"`)**:
|
||
- Правило сортировки элементов выборки (см. правила ниже: `new+`, `price+`, `price-`, `random` и т.д.).
|
||
|
||
5. **`extra` (dict, 5-я позиция, опциональный, default=`{}`)**:
|
||
- Произвольный словарь с дополнительными настройками блока (например, `{"title": "Кастомный заголовок"}`).
|
||
|
||
### Правила синтаксиса:
|
||
|
||
```json
|
||
{
|
||
"hub": [
|
||
["source-slug", "view_mode"],
|
||
["source-slug", "view_mode", limit],
|
||
["source-slug", "view_mode", limit, "sort_rule"],
|
||
["source-slug", "view_mode", limit, "sort_rule", {"title": "Кастомный заголовок"}],
|
||
["articles:type:info", "teaser+list", "2+15", "new+"],
|
||
["!module-slug"]
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Спецификация блоков и Мета-язык
|
||
|
||
### Специальные символы синтаксиса DSL
|
||
|
||
| Символ | Назначение | Пример | Описание |
|
||
|:--------|:-------------------------------------|:--------------------------------------------------|:-----------------------------------------------------------|
|
||
| **`!`** | Модуль / Плагин | `"!promo-mandarin"`, `"!cart-bonus"` | Запуск зарегистрированного Python-модуля |
|
||
| **`:`** | Динамическая выборка (Namespace) | `"articles:type:info"`, `"offers:artist:beatles"` | Выборка из БД (офферы или статьи) по критерию |
|
||
| **`+`** | Составной режим отображения и лимита | `"teaser+list"`, `"2+15"` | Один SQL-запрос с разбиением результатов на разные шаблоны |
|
||
|
||
---
|
||
|
||
### Правила сортировки (`sort`)
|
||
|
||
Для параметра `sort` (4-я позиция) используются короткие наглядные обозначения направления сортировки (`+` — по убыванию/свежие/популярные, `-` — по возрастанию/старые/дешевые):
|
||
|
||
| Значение `sort` | Смысл сортировки | Эквивалент поля в БД |
|
||
| :--- | :--- | :--- |
|
||
| **`new+`** *(default)* | Сначала новые (новинки) | `-t_created` / `-t_article_created` |
|
||
| **`new-`** | Сначала старые | `t_created` / `t_article_created` |
|
||
| **`price+`** | Сначала дешевые | `f_offer_price` (по возрастанию) |
|
||
| **`price-`** | Сначала дорогие | `-f_offer_price` (по убыванию) |
|
||
| **`view+`** | Самые просматриваемые (хиты) | `-i_views` / `-i_article_views` |
|
||
| **`view-`** | Наименее просматриваемые | `i_views` / `i_article_views` |
|
||
| **`favorite+`** | Часто в избранном | `-i_favorites` / `-i_article_favorites` |
|
||
| **`favorite-`** | Редко в избранном | `i_favorites` / `i_article_favorites` |
|
||
| **`random`** | Случайная выборка | `?` (`order_by('?')`) |
|
||
|
||
*Примечание:* Для обратной совместимости и отказоустойчивости парсер бэкенда также принимает устаревшие текстовые значения (`newest` $\rightarrow$ `new+`, `popular` $\rightarrow$ `view+`, `price_asc` $\rightarrow$ `price+`, `price_desc` $\rightarrow$ `price-`).
|
||
|
||
---
|
||
|
||
### Тип A: Конкретная точечная сущность (Обычный slug)
|
||
|
||
Загружает конкретную статью, категорию или жанр по их собственному первичному слагу в БД.
|
||
|
||
Формат: `[ "slug", "view_mode", limit, "sort", extra ]`
|
||
|
||
Примеры:
|
||
- `["cassettes-studio"]` — конкретная категория/статья (дефолтные view_mode по `ArticleType`, limit=10, sort="new+")
|
||
- `["guide-how-to-clean-heads", "teaser"]` — тизер конкретной статьи
|
||
|
||
---
|
||
|
||
### Тип B: Динамическая выборка / Запрос к БД (Префикс с двоеточием `:`)
|
||
|
||
Позволяет формировать динамические списки офферов (`offers:...`) или статей (`articles:...`) по заданным критериям без создания отдельных вьюх.
|
||
|
||
Формат источника (`source`):
|
||
- `offers:artist:<slug>` — офферы конкретного исполнителя (например, `offers:artist:the-beatles`)
|
||
- `offers:format:<slug>` — офферы конкретного формата (например, `offers:format:vinyl`)
|
||
- `offers:style:<slug>` — офферы музыкального стиля (например, `offers:style:jazz`)
|
||
- `articles:type:<type>` — статьи определенного типа `ArticleType` (например, `articles:type:info`, `articles:type:blog`)
|
||
- `articles:artist:<slug>` — статьи, связанные с исполнителем (например, `articles:artist:the-beatles`)
|
||
|
||
Примеры:
|
||
- `["articles:type:info", "list", 50]` — до 50 статей типа `INFO` в виде списка
|
||
- `["offers:artist:the-beatles", "carousel", 10, "view+"]` — 10 популярных релизов The Beatles в карусели
|
||
|
||
---
|
||
|
||
### Составной режим отображения (`view_mode` + `limit` через `+`)
|
||
|
||
Позволяет вывести результаты одного SQL-запроса через комбинацию нескольких шаблонов (например, первые 2 элемента крупными тизерами, а остальные 15 элементов — компактным списком снизу) без дублирования блоков.
|
||
|
||
Синтаксис: `"teaser+list"`, `"2+15"`
|
||
|
||
Пример:
|
||
- `["articles:type:info", "teaser+list", "2+15", "new+", {"title": "Инструкции и справка"}]`
|
||
- Выполняется 1 SQL-запрос с `LIMIT = 2 + 15 = 17`.
|
||
- Первые 2 статьи передаются в шаблон `teaser`.
|
||
- Следующие 15 статей передаются в шаблон `list`.
|
||
|
||
---
|
||
|
||
### Тип C: Динамический модуль / Плагин (Префикс `!`)
|
||
|
||
Запускает кастомный Python-модуль, зарегистрированный в системе (например, интерактивные виджеты, спецпредложения корзины, акции).
|
||
|
||
Формат: `[ "!module-slug", extra ]`
|
||
|
||
Отличительный признак: `slug` начинается с символа **`!`** (восклицательный знак). Так как знак `!` недопустим в обычных URL-слагах Django, парсер однозначно и безошибочно определяет, что данный блок является вызовом Python-плагина.
|
||
|
||
Пример:
|
||
- `["!promo-mandarin"]`
|
||
- `["!cart-bonus", {"threshold": 5000}]`
|
||
|
||
---
|
||
|
||
## 4. Полный пример конфига (j_article_metadata)
|
||
|
||
```json
|
||
{
|
||
"hub": [
|
||
["hero-cassettes-2026", "slider"],
|
||
["articles:type:info", "teaser+list", "2+15", "new+", {"title": "Инструкции и справочные материалы"}],
|
||
["offers:artist:the-beatles", "carousel", 10, "view+", {"title": "Релизы The Beatles в наличии"}],
|
||
["cassettes-studio", "carousel", 6, "view+"],
|
||
["!promo-mandarin"],
|
||
["offers:format:vinyl", "grid", 20, "price+"],
|
||
["!cart-bonus", {"threshold": 5000}]
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Защита и логика бэкенда (Требования к Python / Django)
|
||
|
||
При разработке парсера DSL:
|
||
|
||
1. Защита от зацикливания и бесконечной рекурсии:
|
||
- Передавать множество `visited_slugs` при рендере страницы.
|
||
- Если вложенный блок пытается загрузить `slug`, который уже есть в `visited_slugs`, пропускать блок или выводить отладочный HTML-комментарий (`<!-- DSL Error: Circular reference detected for slug -->`).
|
||
2. Ограничение глубины вложенности:
|
||
- Жестко ограничить максимальную глубину рекурсии: `MAX_RECURSION_DEPTH = 2` (устанавливается в `settings.py`).
|
||
3. Отказоустойчивость (Graceful Degradation):
|
||
- Если `limit` передан не числом или `sort` содержит неизвестную строку, парсер должен тихо применить безопасные дефолты (автовыбор `view_mode` по `ArticleType` с фолбэком на `'grid'`, `limit=10`, `sort='new+'`). Ошибка синтаксиса в JSON никогда не должна приводить к `500 Server Error`.
|
||
- Если `slug` (без `!`) не найден в базе, блок пропускается, а в HTML выводится комментарий: `<!-- DSL Error: Slug not found -->`.
|
||
- Если модуль `!slug` не зарегистрирован в PluginRegistry, выводится комментарий: `<!-- DSL Error: Module not found -->`.
|
||
4. Изоляция данных:
|
||
- Внутри DSL запрещено использовать сырые имена полей БД (например, k_offer_to_item__s_item). Все выборки данных маппятся строго через `slug` и внутренние обработчики запросов (Query Handlers).
|
||
5. Валидация slug
|
||
- Добавить в валидатор админки статей (TbArticle) проверку на несовпадение slug с существующими роутами сайта из `urls.py` (чтобы не было конфликта URL-ов).
|
||
|
||
---
|
||
|
||
## 6. Архитектура исполнения DSL и Реестр обработчиков (DSL Query Registry)
|
||
|
||
Для предотвращения написания «макаронного» кода во вьюхах и обеспечения высокой расширяемости система исполнения DSL строится на основе паттерна **«Реестр обработчиков» (Registry Pattern)**.
|
||
|
||
### Принцип разделения ответственности:
|
||
|
||
1. **Вьюха `hub_detail` (`HubView`):**
|
||
- Выступает исключительно в роли входных ворот.
|
||
- Ищет объект `TbArticle` по слагу и передаёт его в контекст шаблона `content/hub.html`.
|
||
- Не содержит хардкода выборок, разбора параметров и формирования SQL-запросов.
|
||
|
||
2. **Template Tag `{% render_hub_blocks article %}`:**
|
||
- Читает массив `j_article_metadata['hub']` из полученной статьи.
|
||
- Итерируется по кортежам DSL и передаёт их в ядро исполнения DSL.
|
||
- Вызывает шаблоны визуальных компонентов (`carousel.html`, `grid.html`, `list.html`, `teaser.html`) с контекстом, подготовленным обработчиками.
|
||
|
||
3. **Реестр обработчиков выборок (`DSLQueryRegistry`):**
|
||
- Модуль, содержащий карту функций-обработчиков (Query Handlers), зарегистрированных через декоратор `@DSLQueryRegistry.register("prefix")`.
|
||
- Каждое пространство имён 1-го позиционного параметра (`articles:type`, `offers:artist`, `offers:format`, `offers:style`, `single_slug`, `!module`) имеет свой изолированный обработчик на 3–5 строк кода.
|
||
|
||
### Принцип обработки автоматических пресетов для товаров (`ArticleType.ITEM`):
|
||
- Если для конкретного релиза/товара `j_article_metadata['hub']` пуст, ядро DSL формирует динамический виртуальный массив блоков на основе Foreign Key связей объекта (`k_item_to_artist`, `k_offer_to_label`, `k_style`).
|
||
- Виртуальные блоки прогоняются через тот же самый реестр `DSLQueryRegistry`, обеспечивая единообразие вывода контента без загромождения БД и дублирования логики.
|
||
|
||
film0069t-2001.08.xx |