Files
2018-lpon-site/_prj_blueprint/DSL.md
T

133 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Спецификация: 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`), являются опциональными.
Позиционный порядок параметров: `[ "slug", "view_mode", limit, "sort", extra ]`
### Правила синтаксиса:
```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": "Кастомный заголовок"}],
["!module-slug"]
]
}
```
---
## 3. Спецификация блоков
### Тип A: Блок подборки товаров или контентной статьи (обычный slug)
Выводит подборку товаров (TbOffer), полученную на основе категории, тега формата или слаг-коллекции, либо встроенную статью/баннер.
Формат: `[ "slug", "view_mode", limit, "sort", extra ]`
Параметры (позиционные):
1. `slug` (str, обязательный): Уникальный слаг TbMusicStyle, TbArticle (Категория/Тег/Статья) или идентификатор формата (например, `cassettes-studio`, `vinyl-rock`).
2. `view_mode` (str, опциональный, default зависит от `ArticleType`): Компонент рендеринга (шаблон):
- `carousel` → Горизонтальная карусель / слайдер.
- `grid` → Стандартная сетка карточек.
- `list` → Компактный вертикальный список.
- `teaser` → Тизер статьи с кнопкой «Читать далее».
- `full` → Полный HTML-контент статьи, встроенный inline.
- *Примечание:* Если `view_mode` не указан, шаблон выбирается автоматически в соответствии с типом сущности (`ArticleType` модели `TbArticle`), за которой закреплен слаг. У каждого `ArticleType` есть свой дефолтный шаблон.
3. `limit` (int, опциональный, default=`10`): Количество выводимых элементов (например, 5, 20).
4. `sort` (str, опциональный, default=`"newest"`): Правило сортировки:
- `newest` → -t_offer_created (новинки)
- `views` → -i_offer_views (самые просматриваемые)
- `favorites` → -i_offer_favorites (самые популярные / в избранном)
- `price_asc` → f_offer_price (сначала дешевые)
- `price_desc` → -f_offer_price (сначала дорогие)
5. `extra` (dict, опциональный, default=`{}`): Дополнительные произвольные настройки блока (например, `{"title": "Переопределенный заголовок"}`).
Примеры:
- `["cassettes-studio"]` — подборка кассет (дефолтные view_mode по `ArticleType`, limit=10, sort="newest")
- `["cassettes-studio", "carousel"]` — карусель студийных кассет (дефолтные limit=10, sort="newest")
- `["cassettes-studio", "carousel", 6]` — карусель из 6 студийных кассет
- `["cassettes-studio", "carousel", 6, "views"]` — 6 самых просматриваемых кассет в карусели
- `["cassettes-studio", "carousel", 6, "views", {"title": "Хиты продаж"}]` — с переопределенным заголовком
Замечания: `view_mode` — это название зарегистрированного шаблона в системе. Если `view_mode` не указан или пуст, вид отображения выбирается автоматически в соответствии с `ArticleType` найденной сущности. При невозможности определить `ArticleType` или при незнакомом названии шаблона используется системный фолбэк (`"grid"`).
Полный список доступных видов отображения (`view_mode`) и поддерживаемых ими шаблонов ведет отдельно (в виде внешней документации компонентов либо будет добавлен ниже в этом документе по мере появления новых визуальных блоков).
### Тип B: Динамический модуль / Плагин (Префикс `!`)
Запускает кастомный Python-модуль, зарегистрированный в системе (например, интерактивные виджеты, спецпредложения корзины, акции).
Формат: `[ "!module-slug", extra ]`
Отличительный признак: `slug` начинается с символа **`!`** (восклицательный знак). Так как знак `!` недопустим в обычных URL-слагах Django, парсер однозначно и безошибочно определяет, что данный блок является вызовом Python-плагина, а не поиском статьи/категории в БД.
Параметры:
1. `slug` (str, обязательный): Уникальный идентификатор с префиксом `!`, зарегистрированный в PluginRegistry (например, `!promo-mandarin`, `!cart-bonus`).
2. `extra` (dict, опциональный, default=`{}`): Словарь дополнительных параметров, передаваемых в модуль.
Пример:
- `["!promo-mandarin"]`
- `["!cart-bonus", {"threshold": 5000}]`
---
## 4. Полный пример конфига (j_article_metadata)
```json
{
"hub": [
["hero-cassettes-2026", "slider"],
["cassettes-studio", "carousel", 6, "views"],
["cassettes-studio", "grid", 20, "newest"],
["!promo-mandarin"],
["cassettes-maxell-used-ud2-c90-ver2", "carousel", 6, "popular"],
["cassettes-tdk-ned-cding2-c60", "list", 20, "price_desc"],
["guide-how-to-clean-heads", "teaser", 1]
]
}
```
---
## 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='newest'`). Ошибка синтаксиса в 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-ов).