mod: описание DSL (draft 2)
This commit is contained in:
+62
-50
@@ -3,16 +3,18 @@
|
|||||||
драфт
|
драфт
|
||||||
|
|
||||||
## 1. Обзор и цели
|
## 1. Обзор и цели
|
||||||
Мы реализуем гибкий, управленный данными (Data-Driven) конструктор страниц внутри бэкенда Django для **LPON.RU**.
|
Мы реализуем гибкий, управляемый данными (Data-Driven) конструктор страниц внутри бэкенда Django для **LPON.RU**.
|
||||||
Вместо написания отдельных вьюх (Views) и шаблонов под каждый формат или раздел (например, `/catalog/cassettes/`, `/catalog/vinyl/`), мы формируем динамические **Страницы-Хабы (Hub Pages)** с помощью компактного JSON Tuple DSL, который хранится прямо в базе данных.
|
Вместо написания отдельных вьюх (Views) и шаблонов под каждый формат или раздел (например, `/catalog/cassettes/`, `/catalog/vinyl/`), мы формируем динамические **Страницы-Хабы (Hub Pages)** с помощью компактного JSON Tuple DSL, который хранится прямо в базе данных.
|
||||||
|
|
||||||
Система завязана на модель `TbArticle` с типом `l_article_type = 'HUB'`. Конфигурация хранится в JSON-поле метаданных статьи (`j_article_metadata['hub']`).
|
Система завязана на модель `TbArticle`. Конфигурация хранится в JSON-поле метаданных статьи (`j_article_metadata['hub']`). Механизм HUB может использоваться как для чистых хаб-страниц (`l_article_type = 'HUB'`), так и в качестве опционального блока в любых других статьях/сущностях для подключения динамических витрин и модулей.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Синтаксис и структура JSON DSL
|
## 2. Синтаксис и структура JSON DSL
|
||||||
|
|
||||||
Массив `hub` представляет собой список позиционных кортежей (массивов). Движок определяет тип блока на основе длины массива и типов переданных значений.
|
Массив `hub` представляет собой список позиционных кортежей (массивов). Порядок элементов в кортеже фиксирован, при этом все элементы, кроме первого (`slug`), являются опциональными.
|
||||||
|
|
||||||
|
Позиционный порядок параметров: `[ "slug", "view_mode", limit, "sort", extra ]`
|
||||||
|
|
||||||
### Правила синтаксиса:
|
### Правила синтаксиса:
|
||||||
|
|
||||||
@@ -20,80 +22,90 @@
|
|||||||
{
|
{
|
||||||
"hub": [
|
"hub": [
|
||||||
["source-slug", "view_mode"],
|
["source-slug", "view_mode"],
|
||||||
["source-slug", limit, "sort_rule", "view_mode"],
|
["source-slug", "view_mode", limit],
|
||||||
["module-or-article-slug"]
|
["source-slug", "view_mode", limit, "sort_rule"],
|
||||||
|
["source-slug", "view_mode", limit, "sort_rule", {"title": "Кастомный заголовок"}],
|
||||||
|
["!module-slug"]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 3. Спецификация блоков
|
## 3. Спецификация блоков
|
||||||
|
|
||||||
### Тип A: Блок предложений товаров (по умолчанию Длина = 4)
|
### Тип A: Блок подборки товаров или контентной статьи (обычный slug)
|
||||||
|
|
||||||
Выводит подборку товаров (TbOffer), полученную на основе категории, тега формата или слаг-коллекции.
|
Выводит подборку товаров (TbOffer), полученную на основе категории, тега формата или слаг-коллекции, либо встроенную статью/баннер.
|
||||||
|
|
||||||
Формат: `[ "slug", limit, "sort", "view_mode" ]`
|
Формат: `[ "slug", "view_mode", limit, "sort", extra ]`
|
||||||
|
|
||||||
Параметры:
|
Параметры (позиционные):
|
||||||
|
|
||||||
1. `slug` (str): Уникальный слаг TbMusicStyle, TbArticle (Категория/Тег) или идентификатор формата (например, cassettes-studio, cassettes-used-blank).
|
1. `slug` (str, обязательный): Уникальный слаг TbMusicStyle, TbArticle (Категория/Тег/Статья) или идентификатор формата (например, `cassettes-studio`, `vinyl-rock`).
|
||||||
2. `limit` (int): Количество выводимых товаров (например, 5, 20). Фолбэк при ошибке: 10.
|
2. `view_mode` (str, опциональный, default зависит от `ArticleType`): Компонент рендеринга (шаблон):
|
||||||
3. `sort` (str): Правило сортировки:
|
|
||||||
- `views` → -i_offer_views (самые просматриваемые)
|
|
||||||
- `favorites` → -i_offer_favorites (самые популярные / в избранном)
|
|
||||||
- `newest` → -t_offer_created (новинки)
|
|
||||||
- `price_asc` → f_offer_price (сначала дешевые)
|
|
||||||
- `price_desc` → -f_offer_price (сначала дорогие)
|
|
||||||
4. `view_mode` (str): Компонент рендеринга (шаблон):
|
|
||||||
- `carousel` → Горизонтальная карусель / слайдер.
|
- `carousel` → Горизонтальная карусель / слайдер.
|
||||||
- `grid` → Стандартная сетка карточек.
|
- `grid` → Стандартная сетка карточек.
|
||||||
- `list` → Компактный вертикальный список.
|
- `list` → Компактный вертикальный список.
|
||||||
|
|
||||||
Пример: ["cassettes-studio", 5, "views", "carousel"]
|
|
||||||
|
|
||||||
Замечания: `view_mode` -- это название зарегистрированного шаблона в системе. Если передан неизвестный `view_mode` или он отсутствует, используется дефолтный `list`.
|
|
||||||
|
|
||||||
### Тип B: Баннер / Вложенная статья / Вложенный хаб (Длина = 2)
|
|
||||||
|
|
||||||
Встраивает существующую статью, рекламный баннер или массив вложенного хаба.
|
|
||||||
|
|
||||||
Формат: [ "slug", "view_mode" ]
|
|
||||||
|
|
||||||
Параметры:
|
|
||||||
|
|
||||||
1. `slug` (str): Слаг целевой TbArticle.
|
|
||||||
2. `view_mode` (str): Режим отображения:
|
|
||||||
- `slider` / `hero` → Главный баннер / промо-слайдер.
|
|
||||||
- `teaser` → Тизер статьи с кнопкой «Читать далее».
|
- `teaser` → Тизер статьи с кнопкой «Читать далее».
|
||||||
- `full` → Полный HTML-контент статьи, встроенный inline.
|
- `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": "Переопределенный заголовок"}`).
|
||||||
|
|
||||||
Пример: ["hero-banner-cassettes", "slider"]
|
Примеры:
|
||||||
|
- `["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": "Хиты продаж"}]` — с переопределенным заголовком
|
||||||
|
|
||||||
### Тип C: Динамический модуль / Плагин (Длина = 1)
|
Замечания: `view_mode` — это название зарегистрированного шаблона в системе. Если `view_mode` не указан или пуст, вид отображения выбирается автоматически в соответствии с `ArticleType` найденной сущности. При невозможности определить `ArticleType` или при незнакомом названии шаблона используется системный фолбэк (`"grid"`).
|
||||||
|
|
||||||
Запускает кастомный Python-модуль, зарегистрированный в системе (например, расчет скидок, интерактивные виджеты, реклама).
|
Полный список доступных видов отображения (`view_mode`) и поддерживаемых ими шаблонов ведет отдельно (в виде внешней документации компонентов либо будет добавлен ниже в этом документе по мере появления новых визуальных блоков).
|
||||||
|
|
||||||
Формат: [ "module-slug" ]
|
### Тип B: Динамический модуль / Плагин (Префикс `!`)
|
||||||
|
|
||||||
|
Запускает кастомный Python-модуль, зарегистрированный в системе (например, интерактивные виджеты, спецпредложения корзины, акции).
|
||||||
|
|
||||||
|
Формат: `[ "!module-slug", extra ]`
|
||||||
|
|
||||||
|
Отличительный признак: `slug` начинается с символа **`!`** (восклицательный знак). Так как знак `!` недопустим в обычных URL-слагах Django, парсер однозначно и безошибочно определяет, что данный блок является вызовом Python-плагина, а не поиском статьи/категории в БД.
|
||||||
|
|
||||||
Параметры:
|
Параметры:
|
||||||
|
|
||||||
1. `slug` (str): Уникальный идентификатор, зарегистрированный в PluginRegistry (например, module-promo-mandarin, module-ad-banner-1).
|
1. `slug` (str, обязательный): Уникальный идентификатор с префиксом `!`, зарегистрированный в PluginRegistry (например, `!promo-mandarin`, `!cart-bonus`).
|
||||||
|
2. `extra` (dict, опциональный, default=`{}`): Словарь дополнительных параметров, передаваемых в модуль.
|
||||||
|
|
||||||
Пример: ["module-promo-mandarin"]
|
Пример:
|
||||||
|
- `["!promo-mandarin"]`
|
||||||
|
- `["!cart-bonus", {"threshold": 5000}]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 4. Полный пример конфига (j_article_metadata)
|
## 4. Полный пример конфига (j_article_metadata)
|
||||||
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
"hub": [
|
"hub": [
|
||||||
["hero-cassettes-2026", "slider"],
|
["hero-cassettes-2026", "slider"],
|
||||||
["cassettes-studio", 6, "views", "carousel"],
|
["cassettes-studio", "carousel", 6, "views"],
|
||||||
["cassettes-studio", 20, "newest", "grid"],
|
["cassettes-studio", "grid", 20, "newest"],
|
||||||
["module-mandarin-promo"],
|
["!promo-mandarin"],
|
||||||
["cassettes-maxell-used-ud2-c90-ver2", 6, "popular", "carousel"],
|
["cassettes-maxell-used-ud2-c90-ver2", "carousel", 6, "popular"],
|
||||||
["cassettes-tdk-ned-cding2-c60", 20, "price_desc", "list"],
|
["cassettes-tdk-ned-cding2-c60", "list", 20, "price_desc"],
|
||||||
["guide-how-to-clean-heads", "teaser"]
|
["guide-how-to-clean-heads", "teaser", 1]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 5. Защита и логика бэкенда (Требования к Python / Django)
|
## 5. Защита и логика бэкенда (Требования к Python / Django)
|
||||||
|
|
||||||
@@ -105,10 +117,10 @@
|
|||||||
2. Ограничение глубины вложенности:
|
2. Ограничение глубины вложенности:
|
||||||
- Жестко ограничить максимальную глубину рекурсии: `MAX_RECURSION_DEPTH = 2` (устанавливается в `settings.py`).
|
- Жестко ограничить максимальную глубину рекурсии: `MAX_RECURSION_DEPTH = 2` (устанавливается в `settings.py`).
|
||||||
3. Отказоустойчивость (Graceful Degradation):
|
3. Отказоустойчивость (Graceful Degradation):
|
||||||
- Если limit передан не числом или sort содержит неизвестную строку, парсер должен тихо применить безопасные дефолты (`limit=10`, `sort='newest'`, `view_mode='grid'`). Ошибка синтаксиса в JSON никогда не должна приводить к `500 Server Error`.
|
- Если `limit` передан не числом или `sort` содержит неизвестную строку, парсер должен тихо применить безопасные дефолты (автовыбор `view_mode` по `ArticleType` с фолбэком на `'grid'`, `limit=10`, `sort='newest'`). Ошибка синтаксиса в JSON никогда не должна приводить к `500 Server Error`.
|
||||||
- Если `slug` не найден в базе, блок пропускается, а в HTML выводится комментарий: `<!-- DSL Error: Slug not found -->`.
|
- Если `slug` (без `!`) не найден в базе, блок пропускается, а в HTML выводится комментарий: `<!-- DSL Error: Slug not found -->`.
|
||||||
|
- Если модуль `!slug` не зарегистрирован в PluginRegistry, выводится комментарий: `<!-- DSL Error: Module not found -->`.
|
||||||
4. Изоляция данных:
|
4. Изоляция данных:
|
||||||
- Внутри DSL запрещено использовать сырые имена полей БД (например, k_offer_to_item__s_item). Все выборки данных маппятся строго через `slug` и внутренние обработчики запросов (Query Handlers).
|
- Внутри DSL запрещено использовать сырые имена полей БД (например, k_offer_to_item__s_item). Все выборки данных маппятся строго через `slug` и внутренние обработчики запросов (Query Handlers).
|
||||||
- Все ключи из json['hub'] должны быть объявлены заранее.
|
|
||||||
5. Валидация slug
|
5. Валидация slug
|
||||||
- Нужно добавить в валидатор админки статей (TbArticle) проверку на "не совпадение" slug с существующими роутами сайта из `'urls.py'` (чтобы не было конфликта URL-ов).
|
- Добавить в валидатор админки статей (TbArticle) проверку на несовпадение slug с существующими роутами сайта из `urls.py` (чтобы не было конфликта URL-ов).
|
||||||
Reference in New Issue
Block a user