12 KiB
Спецификация: 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/, каталоги форматов) рассматриваются как частный случай хаба:
- Вместо написания отдельной вьюхи (например,
txt_articles_list) в БД создаётся статья со слагомinfoи типомHUB. - В её DSL-конфигурации прописываются требуемые блоки (например,
[["info-articles", "list", 50]]). - Рендеринг выполняют не отдельные функции-представления, а единый универсальный диспетчер хабов (
HubView). - Каноничность URL элементов: Внутри шаблонов визуальных блоков ссылки на карточки и статьи выводится строго через
href="{{ item.get_absolute_url }}". Модель статьи/товара сама определяет свой канонический адрес по типу (например,/info/privacy-policyили/item/album-slug), исключая путаницу префиксов в ссылках независимо от того, в каком хабе опубликован блок.
2. Синтаксис и структура JSON DSL
Массив hub представляет собой список позиционных кортежей (массивов). Порядок элементов в кортеже фиксирован, при этом все элементы, кроме первого (slug), являются опциональными.
Позиционный порядок параметров: [ "slug", "view_mode", limit, "sort", extra ]
Правила синтаксиса:
{
"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 ]
Параметры (позиционные):
slug(str, обязательный): Уникальный слаг TbMusicStyle, TbArticle (Категория/Тег/Статья) или идентификатор формата (например,cassettes-studio,vinyl-rock).view_mode(str, опциональный, default зависит отArticleType): Компонент рендеринга (шаблон):
carousel→ Горизонтальная карусель / слайдер.grid→ Стандартная сетка карточек.list→ Компактный вертикальный список.teaser→ Тизер статьи с кнопкой «Читать далее».full→ Полный HTML-контент статьи, встроенный inline.- Примечание: Если
view_modeне указан, шаблон выбирается автоматически в соответствии с типом сущности (ArticleTypeмоделиTbArticle), за которой закреплен слаг. У каждогоArticleTypeесть свой дефолтный шаблон.
limit(int, опциональный, default=10): Количество выводимых элементов (например, 5, 20).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 (сначала дорогие)
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-плагина, а не поиском статьи/категории в БД.
Параметры:
slug(str, обязательный): Уникальный идентификатор с префиксом!, зарегистрированный в PluginRegistry (например,!promo-mandarin,!cart-bonus).extra(dict, опциональный, default={}): Словарь дополнительных параметров, передаваемых в модуль.
Пример:
["!promo-mandarin"]["!cart-bonus", {"threshold": 5000}]
4. Полный пример конфига (j_article_metadata)
{
"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:
- Защита от зацикливания и бесконечной рекурсии:
- Передавать множество
visited_slugsпри рендере страницы. - Если вложенный блок пытается загрузить
slug, который уже есть вvisited_slugs, пропускать блок или выводить отладочный HTML-комментарий (<!-- DSL Error: Circular reference detected for slug -->).
- Ограничение глубины вложенности:
- Жестко ограничить максимальную глубину рекурсии:
MAX_RECURSION_DEPTH = 2(устанавливается вsettings.py).
- Отказоустойчивость (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 -->.
- Изоляция данных:
- Внутри DSL запрещено использовать сырые имена полей БД (например, k_offer_to_item__s_item). Все выборки данных маппятся строго через
slugи внутренние обработчики запросов (Query Handlers).
- Валидация slug
- Добавить в валидатор админки статей (TbArticle) проверку на несовпадение slug с существующими роутами сайта из
urls.py(чтобы не было конфликта URL-ов).