mod: описание DSL + синтаксис для фильтрации и комбинированных блоков

This commit is contained in:
2026-08-08 22:13:07 +03:00
parent d3f64843b2
commit 3ce7214515
+98 -42
View File
@@ -19,10 +19,31 @@
## 2. Синтаксис и структура JSON DSL
Массив `hub` представляет собой список позиционных кортежей (массивов). Порядок элементов в кортеже фиксирован, при этом все элементы, кроме первого (`slug`), являются опциональными.
Массив `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
@@ -32,6 +53,7 @@
["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"]
]
}
@@ -39,56 +61,88 @@
---
## 3. Спецификация блоков
## 3. Спецификация блоков и Мета-язык
### Тип A: Блок подборки товаров или контентной статьи (обычный slug)
### Специальные символы синтаксиса DSL
Выводит подборку товаров (TbOffer), полученную на основе категории, тега формата или слаг-коллекции, либо встроенную статью/баннер.
| Символ | Назначение | Пример | Описание |
|:--------|:-------------------------------------|:--------------------------------------------------|:-----------------------------------------------------------|
| **`!`** | Модуль / Плагин | `"!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"]` — тизер конкретной статьи
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": "Переопределенный заголовок"}`).
---
### Тип 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`)
Примеры:
- `["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": "Хиты продаж"}]` — с переопределенным заголовком
- `["articles:type:info", "list", 50]` — до 50 статей типа `INFO` в виде списка
- `["offers:artist:the-beatles", "carousel", 10, "view+"]` — 10 популярных релизов The Beatles в карусели
Замечания: `view_mode` — это название зарегистрированного шаблона в системе. Если `view_mode` не указан или пуст, вид отображения выбирается автоматически в соответствии с `ArticleType` найденной сущности. При невозможности определить `ArticleType` или при незнакомом названии шаблона используется системный фолбэк (`"grid"`).
---
Полный список доступных видов отображения (`view_mode`) и поддерживаемых ими шаблонов ведет отдельно (в виде внешней документации компонентов либо будет добавлен ниже в этом документе по мере появления новых визуальных блоков).
### Составной режим отображения (`view_mode` + `limit` через `+`)
### Тип B: Динамический модуль / Плагин (Префикс `!`)
Позволяет вывести результаты одного 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-плагина, а не поиском статьи/категории в БД.
Параметры:
1. `slug` (str, обязательный): Уникальный идентификатор с префиксом `!`, зарегистрированный в PluginRegistry (например, `!promo-mandarin`, `!cart-bonus`).
2. `extra` (dict, опциональный, default=`{}`): Словарь дополнительных параметров, передаваемых в модуль.
Отличительный признак: `slug` начинается с символа **`!`** (восклицательный знак). Так как знак `!` недопустим в обычных URL-слагах Django, парсер однозначно и безошибочно определяет, что данный блок является вызовом Python-плагина.
Пример:
- `["!promo-mandarin"]`
@@ -102,12 +156,12 @@
{
"hub": [
["hero-cassettes-2026", "slider"],
["cassettes-studio", "carousel", 6, "views"],
["cassettes-studio", "grid", 20, "newest"],
["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"],
["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]
["offers:format:vinyl", "grid", 20, "price+"],
["!cart-bonus", {"threshold": 5000}]
]
}
```
@@ -124,10 +178,12 @@
2. Ограничение глубины вложенности:
- Жестко ограничить максимальную глубину рекурсии: `MAX_RECURSION_DEPTH = 2` (устанавливается в `settings.py`).
3. Отказоустойчивость (Graceful Degradation):
- Если `limit` передан не числом или `sort` содержит неизвестную строку, парсер должен тихо применить безопасные дефолты (автовыбор `view_mode` по `ArticleType` с фолбэком на `'grid'`, `limit=10`, `sort='newest'`). Ошибка синтаксиса в JSON никогда не должна приводить к `500 Server Error`.
- Если `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-ов).
- Добавить в валидатор админки статей (TbArticle) проверку на несовпадение slug с существующими роутами сайта из `urls.py` (чтобы не было конфликта URL-ов).
film0069t-2001.08.xx