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

189 lines
15 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` / `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-ов).
film0069t-2001.08.xx