# Спецификация: 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'`), так и в качестве опционального блока в любых других статьях/сущностях для подключения динамических витрин и модулей. --- ## 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-комментарий (``). 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 выводится комментарий: ``. - Если модуль `!slug` не зарегистрирован в PluginRegistry, выводится комментарий: ``. 4. Изоляция данных: - Внутри DSL запрещено использовать сырые имена полей БД (например, k_offer_to_item__s_item). Все выборки данных маппятся строго через `slug` и внутренние обработчики запросов (Query Handlers). 5. Валидация slug - Добавить в валидатор админки статей (TbArticle) проверку на несовпадение slug с существующими роутами сайта из `urls.py` (чтобы не было конфликта URL-ов).