Дашборд

Главная страница — это набор виджетов, которые вы сами компонуете. Графики отрисовываются сервером — без библиотек для графиков, без canvas, и каждый из них указывает, во сколько запросов он обходится.

Доступно с версии v0.4.0

Что вы получаете без какой-либо настройки

Количество строк каждой модели, сгруппированное так же, как их группирует боковая панель, и график новых аккаунтов за последние тридцать дней. Это два виджета — Signups() и Counts() — именно таким дашборд был всегда.

Ничего из того, что ниже, не обязательно. Обращайтесь к этому, когда главная страница должна отвечать на вопрос о вашей предметной области, а не о схеме данных.

Компоновка

from fastfort.admin import Breakdown, Counts, Metric, Recent, Trend

fort.set_dashboard(
    Metric(Shipment, title="Shipments booked", days=14, icon="truck"),
    Metric(Invoice, title="Invoices raised", days=14, icon="credit-card"),
    Trend(Shipment, title="Shipments per day", days=30),
    Breakdown(Shipment, on="status"),
    Recent(Shipment, limit=6),
    Counts(),
)

Виджеты отрисовываются в заданном порядке. fort.set_dashboard() без аргументов — это дашборд, на котором ничего нет; иногда это именно то, что нужно, но получить это случайно — только запутает, поэтому это единственный способ так сказать.

Пять встроенных виджетов

ВиджетЧто рисуетВо что обходится
Metric(model)Число, дельта со знаком и спарклайнОдин запрос на день
Trend(model)Тот же ряд, но в увеличенном виде — как график с областью или как столбцы — с итогом за период, самым загруженным днём и средним значением в деньОдин запрос на день
Breakdown(model, on="status")Один индикатор на каждое значение столбцаОдин запрос на значение
Recent(model)Самые новые строки со ссылкамиОдин запрос
Counts()Количество строк каждой зарегистрированной модели, сгруппированное по приложениюОдин запрос на модель

Каждый виджет принимает title=, span= (THIRD, HALF или FULL), а там, где он строит график по времени, — days= и on=, столбец, фиксирующий момент появления строки. Если on не указан, распознаются обычные имена: created_at, date_joined, registered_at и другие.

Столбец стоимости — не украшение. Дашборд — самая часто открываемая страница в любой админке, и легко допустить ошибку, когда карточка незаметно выполняет сотню запросов. Trend(model, days=90) — это девяносто индексированных подсчётов при каждой загрузке страницы; days=14 — четырнадцать. Все виджеты на странице используют единый unit of work запроса, поэтому счёт идёт на запросы, а не на соединения.

Виджет, которому нечего сказать, ничего не отрисовывает

Несуществующий столбец, модель без столбца с датой, разбивка по свободному тексту, dashboard_days=0 — в каждом из этих случаев карточка просто не отображается, а не выбрасывает исключение. Опечатка в конфигурационном файле должна стоить одной карточки, но никогда — страницы, которую все открывают первой.

Именно поэтому Breakdown отказывается работать со столбцом без фиксированного набора значений: подсчёт различных значений столбца со свободным текстом означает полное сканирование таблицы.

Графики отрисовываются сервером

SVG-путь, несколько <line>, и прямоугольники с заданной высотой. Никакой библиотеки для графики, никакого <canvas>, никакого второго запроса и ни единого лишнего байта JavaScript сверх того, что уже есть в админке.

Это значит: график присутствует уже при первой отрисовке страницы, он печатается, использует цвета текущей темы — как светлой, так и тёмной, — и переживает отключение скриптов. Скрипт добавляет только всплывающие подсказки и ничего больше. Рядом с каждым графиком те же данные представлены в виде таблицы — скрытой визуально, но доступной программам чтения с экрана.

Каждый график использует только один цвет, потому что вся палитра происходит от одного тона. Тренд — это один ряд данных, а разбивка сравнивает столбцы по длине — единственному визуальному каналу, который люди считывают точно, — с числом и долей в тексте рядом с каждым столбцом.

Написание собственного виджета

from fastfort.admin.dashboard import Card, Widget

class OpenTickets(Widget):
    span = 1

    async def resolve(self, context):
        adapter = context.adapter_for(Ticket)
        count = await adapter.count(ListQuery(filters=(Filter("state", FilterOperator.EXACT, "open"),)))
        if not count:
            return None
        return Card(
            template="dashboard/open_tickets.html",
            context={"count": count},
            span=self.span,
        )

fort.set_dashboard(OpenTickets(), Counts())

Card.template разрешается через рендерер, причём собственная директория шаблонов проекта проверяется раньше директории пакета — так что достаточно разместить dashboard/open_tickets.html в вашей собственной директории шаблонов. На странице нет никакой особой обработки для встроенных виджетов; ваш виджет отрисовывается точно так же, как и они.

context предоставляет unit of work запроса (adapter_for, spec_for), реестр (key_for, url_for, title_for) и настроенное окно (days). Обращаться в обход него напрямую к ORM — это именно то, чего следует избегать: виджет, написанный против протокола адаптера, продолжает работать при смене бэкенда проекта.